Compare commits

...

22 Commits

Author SHA1 Message Date
alnyan 895fa74c4d term: add cursor rendering 2025-07-16 10:22:08 +03:00
alnyan 0c9eb93d6b colors: better config structure 2025-07-15 10:47:37 +03:00
alnyan d52eaabe25 colors: add basic configuration 2025-07-15 10:30:57 +03:00
alnyan dbbfb39164 maint: fix warnings 2025-07-15 10:18:33 +03:00
alnyan 4c13a90799 colors: add cursor rendering 2025-07-15 10:18:21 +03:00
alnyan c425130aca colors: add surface resize event 2025-07-14 17:17:08 +03:00
alnyan 622d11ee05 WIP usb: better driver structure, hid mouse driver 2025-07-14 17:17:08 +03:00
alnyan c63fa5517a usb: add basic userspace lsusb + usb sysfs 2025-07-14 17:17:08 +03:00
alnyan cac1350b13 shell: fix signals not being delivered to children 2025-07-14 17:17:08 +03:00
alnyan 11ebda95b1 riscv64: fix build 2025-07-14 17:17:08 +03:00
alnyan 05c73735ba netutils/ping: implement dns queries 2025-07-14 17:17:08 +03:00
alnyan c59be9de5c net/igbe: support more Intel GbE NICs 2025-07-14 17:17:08 +03:00
alnyan bc795904bd netutils/http: follow redirects, http AutoConnector 2025-07-14 17:17:08 +03:00
alnyan 5a0a22bd69 WIP term: add more ctlseq stubs 2025-07-14 16:59:38 +03:00
alnyan b487e51fc4 terminal: implement nl to crnl 2025-07-14 16:57:08 +03:00
alnyan 9b172a6b08 rsh: fix broken aes256cbc, fix incorrect pidfd polling 2025-07-11 16:11:27 +03:00
alnyan 15f33c33dc colors: rewrite colors-bar 2025-07-11 13:35:56 +03:00
alnyan dbcefe14fc WIP 2025-07-11 11:02:09 +03:00
alnyan cf93403375 WIP: Implement more display attributes 2025-07-11 10:36:18 +03:00
alnyan 970e2d796c colors: update winit usage 2025-07-11 10:03:28 +03:00
alnyan 8616a62fec WIP: Add scrolling 2025-07-10 18:17:34 +03:00
alnyan d7ec7f29c2 WIP: begin terminal rewrite 2025-06-30 19:49:34 +03:00
123 changed files with 5742 additions and 3470 deletions
Generated
+534 -367
View File
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct UsbRoute {
bus: u16,
ports: [u8; 8],
len: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct UsbBusAddress {
pub bus: u16,
pub device: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct UsbInterfaceAddress {
pub device: UsbBusAddress,
pub interface: u8,
}
impl UsbBusAddress {
pub fn with_interface(self, interface: u8) -> UsbInterfaceAddress {
UsbInterfaceAddress {
device: self,
interface,
}
}
}
impl fmt::Display for UsbBusAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<Bus {} Device {}>", self.bus, self.device)
}
}
impl fmt::Display for UsbInterfaceAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"<Bus {} Device {} Interface {}>",
self.device.bus, self.device.device, self.interface
)
}
}
impl fmt::Display for UsbRoute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-", self.bus)?;
for (i, &port) in self.ports[..self.len as usize].iter().enumerate() {
if i != 0 {
write!(f, ".")?;
}
write!(f, "{port}")?;
}
Ok(())
}
}
+33 -7
View File
@@ -1,34 +1,56 @@
use core::sync::atomic::{AtomicU16, Ordering};
use alloc::{collections::BTreeMap, sync::Arc};
use libk_util::{queue::UnboundedMpmcQueue, sync::spin_rwlock::IrqSafeRwLock};
use libk_util::{queue::UnboundedMpmcQueue, sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
use crate::{
address::UsbBusAddress,
class_driver,
device::{UsbBusAddress, UsbDeviceAccess},
device::UsbDeviceAccess,
sysfs::{self, UsbBusKObject},
UsbHostController,
};
pub struct UsbBusWrapper {
pub(crate) hc: Arc<dyn UsbHostController>,
pub(crate) index: u16,
kobject: OneTimeInit<UsbBusKObject>,
}
pub struct UsbBusManager {
busses: IrqSafeRwLock<BTreeMap<u16, Arc<dyn UsbHostController>>>,
busses: IrqSafeRwLock<BTreeMap<u16, Arc<UsbBusWrapper>>>,
devices: IrqSafeRwLock<BTreeMap<UsbBusAddress, Arc<UsbDeviceAccess>>>,
last_bus_address: AtomicU16,
}
impl UsbBusWrapper {
pub fn kobject(&self) -> &UsbBusKObject {
self.kobject.get()
}
}
impl UsbBusManager {
pub fn register_bus(hc: Arc<dyn UsbHostController>) -> u16 {
pub fn register_bus(hc: Arc<dyn UsbHostController>) -> (u16, Arc<UsbBusWrapper>) {
let i = BUS_MANAGER.last_bus_address.fetch_add(1, Ordering::AcqRel);
BUS_MANAGER.busses.write().insert(i, hc);
i
let wrapper = Arc::new(UsbBusWrapper {
hc,
index: i,
kobject: OneTimeInit::new(),
});
BUS_MANAGER.busses.write().insert(i, wrapper.clone());
wrapper.kobject.init(sysfs::register_bus_kobject(&wrapper));
(i, wrapper)
}
pub fn register_device(device: Arc<UsbDeviceAccess>) {
log::info!("usb: register device {}", device.bus_address());
BUS_MANAGER
.devices
.write()
.insert(device.bus_address(), device.clone());
device.kobject.init(sysfs::register_device_kobject(&device));
QUEUE.push_back(device);
}
@@ -51,7 +73,11 @@ pub async fn bus_handler() {
new_device.bus_address()
);
class_driver::spawn_driver(new_device).await.ok();
let address = new_device.bus_address();
if let Err(error) = class_driver::setup_device(new_device).await {
log::warn!("USB device {address} setup error: {error:?}",);
}
// class_driver::spawn_driver(new_device).await.ok();
}
}
@@ -4,9 +4,12 @@ use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
use crate::{device::UsbDeviceAccess, error::UsbError, info::UsbDeviceClass};
use super::{UsbClassInfo, UsbDriver};
use crate::{
class_driver::{UsbInterfaceClass, UsbInterfaceDriver},
device::UsbDeviceAccess,
error::UsbError,
info::UsbInterfaceInfo,
};
pub struct UsbHidKeyboardDriver;
@@ -125,10 +128,14 @@ impl KeyboardState {
}
#[async_trait]
impl UsbDriver for UsbHidKeyboardDriver {
async fn run(self: Arc<Self>, device: Arc<UsbDeviceAccess>) -> Result<(), UsbError> {
impl UsbInterfaceDriver for UsbHidKeyboardDriver {
async fn run(
self: Arc<Self>,
device: Arc<UsbDeviceAccess>,
_interface: UsbInterfaceInfo,
) -> Result<(), UsbError> {
// TODO not sure whether to use boot protocol (easy) or GetReport
let config = device.select_configuration(|_| true).await?.unwrap();
let config = device.current_configuration().unwrap();
log::info!("Setup HID keyboard");
let pipe = device
@@ -156,7 +163,7 @@ impl UsbDriver for UsbHidKeyboardDriver {
for &event in events {
log::trace!("Generic Keyboard: {:?}", event);
ygg_driver_input::send_event(event);
ygg_driver_input::send_keyboard_event(event);
}
}
}
@@ -165,12 +172,18 @@ impl UsbDriver for UsbHidKeyboardDriver {
"USB HID Keyboard"
}
fn probe(&self, class: &UsbClassInfo, _device: &UsbDeviceAccess) -> bool {
log::info!(
"class = {:?}, subclass = {:02x}",
class.class,
class.subclass
);
class.class == UsbDeviceClass::Hid && (class.subclass == 0x00 || class.subclass == 0x01)
fn probe(
&self,
device: &UsbDeviceAccess,
interface: &UsbInterfaceInfo,
class: UsbInterfaceClass,
) -> bool {
let _ = (device, interface);
class
== UsbInterfaceClass {
class: 3,
subclass: 1,
protocol: 1,
}
}
}
@@ -0,0 +1,62 @@
use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use yggdrasil_abi::io::{ButtonMask, MouseEvent};
use crate::{
class_driver::{UsbInterfaceClass, UsbInterfaceDriver},
device::UsbDeviceAccess,
error::UsbError,
info::UsbInterfaceInfo,
};
pub struct UsbHidMouseDriver;
#[async_trait]
impl UsbInterfaceDriver for UsbHidMouseDriver {
async fn run(
self: Arc<Self>,
device: Arc<UsbDeviceAccess>,
_interface: UsbInterfaceInfo,
) -> Result<(), UsbError> {
let config = device.current_configuration().unwrap();
log::info!("Setup HID mouse");
let pipe = device
.open_interrupt_in_pipe(1, config.endpoints[0].max_packet_size as u16)
.await?;
let mut buffer = [0; 16];
loop {
let len = pipe.read(&mut buffer).await?;
if len < 4 {
continue;
}
let event = MouseEvent {
buttons: ButtonMask(buffer[0]),
dx: (buffer[1] as i8) as i32,
dy: (buffer[2] as i8) as i32,
};
ygg_driver_input::send_mouse_event(event);
}
}
fn name(&self) -> &'static str {
"USB HID Mouse"
}
fn probe(
&self,
_device: &UsbDeviceAccess,
_interface: &UsbInterfaceInfo,
class: UsbInterfaceClass,
) -> bool {
class
== UsbInterfaceClass {
class: 3,
subclass: 1,
protocol: 2,
}
}
}
@@ -1,273 +1,273 @@
use core::mem::MaybeUninit;
use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use bytemuck::{Pod, Zeroable};
use libk::{
dma::{DmaBuffer, DmaSliceMut},
error::Error,
};
use ygg_driver_scsi::{transport::ScsiTransport, ScsiEnclosure};
use crate::{
communication::UsbDirection,
device::{UsbDeviceAccess, UsbDeviceDetachHandler},
error::UsbError,
info::{UsbDeviceClass, UsbEndpointType},
pipe::{
control::{ControlTransferSetup, UsbClassSpecificRequest},
normal::{UsbBulkInPipeAccess, UsbBulkOutPipeAccess},
},
};
use super::{UsbClassInfo, UsbDriver};
pub struct UsbMassStorageDriverBulkOnly;
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
struct Cbw {
signature: u32, // 0x00
tag: u32, // 0x04
transfer_length: u32, // 0x08
flags: u8, // 0x0C
lun: u8, // 0x0D
cb_length: u8, // 0x0E
cb_data: [u8; 16], // 0x0F
// Not sent
_0: u8,
}
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
struct Csw {
signature: u32,
tag: u32,
data_residue: u32,
status: u8,
_0: [u8; 3],
}
struct Bbb {
#[allow(unused)]
device: Arc<UsbDeviceAccess>,
in_pipe: UsbBulkInPipeAccess,
out_pipe: UsbBulkOutPipeAccess,
last_tag: u32,
}
struct DetachHandler(Arc<ScsiEnclosure>);
impl Bbb {
pub fn new(
device: Arc<UsbDeviceAccess>,
in_pipe: UsbBulkInPipeAccess,
out_pipe: UsbBulkOutPipeAccess,
) -> Result<Self, UsbError> {
Ok(Self {
device,
in_pipe,
out_pipe,
last_tag: 0,
})
}
}
impl Bbb {
async fn send_cbw(
&mut self,
lun: u8,
host_to_dev: bool,
command: &[u8],
response_len: usize,
) -> Result<u32, Error> {
self.last_tag = self.last_tag.wrapping_add(1);
let flags = if !host_to_dev { 1 << 7 } else { 0 };
let tag = self.last_tag;
let mut cbw_bytes = [0; 32];
let cbw = bytemuck::from_bytes_mut::<Cbw>(&mut cbw_bytes);
cbw.signature = 0x43425355;
cbw.transfer_length = response_len as u32;
cbw.flags = flags;
cbw.tag = tag;
cbw.lun = lun;
cbw.cb_length = command.len() as u8;
cbw.cb_data[..command.len()].copy_from_slice(command);
self.out_pipe
.write(&cbw_bytes[..31])
.await
.inspect_err(|error| log::error!("msc: CBW send error: {error:?}"))?;
Ok(tag)
}
async fn read_csw(&mut self, tag: u32) -> Result<(), Error> {
let mut csw_bytes = [0; 16];
self.in_pipe
.read_exact(&mut csw_bytes[..13])
.await
.inspect_err(|error| log::error!("msc: CSW receive error: {error:?}"))?;
let csw = bytemuck::from_bytes::<Csw>(&csw_bytes);
if csw.signature != 0x53425355 {
log::warn!("msc: invalid csw signature");
return Err(Error::InvalidArgument);
}
if csw.tag != tag {
let csw_tag = csw.tag;
log::warn!("msc: invalid csw tag (got {}, expected {tag})", csw_tag);
return Err(Error::InvalidArgument);
}
if csw.status != 0x00 {
return Err(Error::InvalidArgument);
}
Ok(())
}
async fn read_response_data(
&mut self,
buffer: DmaSliceMut<'_, MaybeUninit<u8>>,
) -> Result<usize, Error> {
if buffer.len() == 0 {
return Ok(0);
}
let len = self
.in_pipe
.read_dma(buffer)
.await
.inspect_err(|error| log::error!("msc: DMA read error: {error:?}"))?;
Ok(len)
}
}
#[async_trait]
impl ScsiTransport for Bbb {
fn allocate_buffer(&self, size: usize) -> Result<DmaBuffer<[MaybeUninit<u8>]>, Error> {
Ok(self.in_pipe.allocate_dma_buffer(size)?)
}
async fn perform_request_raw(
&mut self,
lun: u8,
request_data: &[u8],
response_buffer: DmaSliceMut<'_, MaybeUninit<u8>>,
) -> Result<usize, Error> {
if request_data.len() > 16 || response_buffer.len() > self.max_bytes_per_request() {
return Err(Error::InvalidArgument);
}
let tag = self
.send_cbw(lun, false, request_data, response_buffer.len())
.await?;
let response_len = self.read_response_data(response_buffer).await?;
self.read_csw(tag).await?;
Ok(response_len)
}
fn max_bytes_per_request(&self) -> usize {
32768
}
}
impl UsbDeviceDetachHandler for DetachHandler {
fn handle_device_detach(&self) {
log::info!("Mass storage detached");
self.0.detach();
}
}
#[derive(Debug, Pod, Zeroable, Clone, Copy)]
#[repr(C)]
pub struct BulkOnlyMassStorageReset;
#[derive(Debug, Pod, Zeroable, Clone, Copy)]
#[repr(C)]
pub struct GetMaxLun;
impl UsbClassSpecificRequest for BulkOnlyMassStorageReset {
const BM_REQUEST_TYPE: u8 = 0b00100001;
const B_REQUEST: u8 = 0b11111111;
}
impl UsbClassSpecificRequest for GetMaxLun {
const BM_REQUEST_TYPE: u8 = 0b10100001;
const B_REQUEST: u8 = 0b11111110;
}
#[async_trait]
impl UsbDriver for UsbMassStorageDriverBulkOnly {
async fn run(self: Arc<Self>, device: Arc<UsbDeviceAccess>) -> Result<(), UsbError> {
// TODO filter to only accept BBB config
let config = device.select_configuration(|_| true).await?.unwrap();
// Bulk-in, bulk-out
assert_eq!(config.endpoints.len(), 2);
let control_pipe = device.control_pipe();
let (in_index, in_info) = config
.find_endpoint(|ep| ep.is(UsbEndpointType::Bulk, UsbDirection::In))
.ok_or(UsbError::InvalidConfiguration)?;
let (out_index, out_info) = config
.find_endpoint(|ep| ep.is(UsbEndpointType::Bulk, UsbDirection::Out))
.ok_or(UsbError::InvalidConfiguration)?;
let in_pipe = device
.open_bulk_in_pipe(in_index, in_info.max_packet_size as u16)
.await?;
let out_pipe = device
.open_bulk_out_pipe(out_index, out_info.max_packet_size as u16)
.await?;
// Perform a Bulk-Only Mass Storage Reset
// TODO interface id?
control_pipe
.control_transfer(ControlTransferSetup {
bm_request_type: BulkOnlyMassStorageReset::BM_REQUEST_TYPE,
b_request: BulkOnlyMassStorageReset::B_REQUEST,
w_value: 0,
w_index: 0,
w_length: 0,
})
.await?;
// Get max LUN
// TODO on devices which do not support multiple LUNs, this command may STALL
let mut buffer = [MaybeUninit::uninit()];
let len = control_pipe
.control_transfer_in(
ControlTransferSetup {
bm_request_type: GetMaxLun::BM_REQUEST_TYPE,
b_request: GetMaxLun::B_REQUEST,
w_value: 0,
w_index: 0,
w_length: 1,
},
&mut buffer,
)
.await?;
let max_lun = if len < 1 {
0
} else {
unsafe { buffer[0].assume_init() }
};
let bbb = Bbb::new(device.clone(), in_pipe, out_pipe)?;
let scsi = ScsiEnclosure::setup(Box::new(bbb), max_lun as usize + 1)
.await
.inspect_err(|error| log::error!("msc: scsi error {error:?}"))
.map_err(|_| UsbError::DriverError)?;
let detach = DetachHandler(scsi.clone());
device.set_detach_handler(Arc::new(detach));
Ok(())
}
fn name(&self) -> &'static str {
"USB Mass Storage"
}
fn probe(&self, class: &UsbClassInfo, _device: &UsbDeviceAccess) -> bool {
// TODO support other protocols
class.class == UsbDeviceClass::MassStorage && class.interface_protocol_number == 0x50
}
}
// use core::mem::MaybeUninit;
//
// use alloc::{boxed::Box, sync::Arc};
// use async_trait::async_trait;
// use bytemuck::{Pod, Zeroable};
// use libk::{
// dma::{DmaBuffer, DmaSliceMut},
// error::Error,
// };
// use ygg_driver_scsi::{transport::ScsiTransport, ScsiEnclosure};
//
// use crate::{
// communication::UsbDirection,
// device::{UsbDeviceAccess, UsbDeviceDetachHandler},
// error::UsbError,
// info::{UsbDeviceClass, UsbEndpointType},
// pipe::{
// control::{ControlTransferSetup, UsbClassSpecificRequest},
// normal::{UsbBulkInPipeAccess, UsbBulkOutPipeAccess},
// },
// };
//
// use super::{UsbClassInfo, UsbDriver};
//
// pub struct UsbMassStorageDriverBulkOnly;
//
// #[derive(Debug, Clone, Copy, Zeroable, Pod)]
// #[repr(C)]
// struct Cbw {
// signature: u32, // 0x00
// tag: u32, // 0x04
// transfer_length: u32, // 0x08
// flags: u8, // 0x0C
// lun: u8, // 0x0D
// cb_length: u8, // 0x0E
// cb_data: [u8; 16], // 0x0F
// // Not sent
// _0: u8,
// }
//
// #[derive(Debug, Clone, Copy, Zeroable, Pod)]
// #[repr(C)]
// struct Csw {
// signature: u32,
// tag: u32,
// data_residue: u32,
// status: u8,
// _0: [u8; 3],
// }
//
// struct Bbb {
// #[allow(unused)]
// device: Arc<UsbDeviceAccess>,
// in_pipe: UsbBulkInPipeAccess,
// out_pipe: UsbBulkOutPipeAccess,
// last_tag: u32,
// }
//
// struct DetachHandler(Arc<ScsiEnclosure>);
//
// impl Bbb {
// pub fn new(
// device: Arc<UsbDeviceAccess>,
// in_pipe: UsbBulkInPipeAccess,
// out_pipe: UsbBulkOutPipeAccess,
// ) -> Result<Self, UsbError> {
// Ok(Self {
// device,
// in_pipe,
// out_pipe,
// last_tag: 0,
// })
// }
// }
//
// impl Bbb {
// async fn send_cbw(
// &mut self,
// lun: u8,
// host_to_dev: bool,
// command: &[u8],
// response_len: usize,
// ) -> Result<u32, Error> {
// self.last_tag = self.last_tag.wrapping_add(1);
//
// let flags = if !host_to_dev { 1 << 7 } else { 0 };
// let tag = self.last_tag;
// let mut cbw_bytes = [0; 32];
// let cbw = bytemuck::from_bytes_mut::<Cbw>(&mut cbw_bytes);
//
// cbw.signature = 0x43425355;
// cbw.transfer_length = response_len as u32;
// cbw.flags = flags;
// cbw.tag = tag;
// cbw.lun = lun;
// cbw.cb_length = command.len() as u8;
// cbw.cb_data[..command.len()].copy_from_slice(command);
//
// self.out_pipe
// .write(&cbw_bytes[..31])
// .await
// .inspect_err(|error| log::error!("msc: CBW send error: {error:?}"))?;
//
// Ok(tag)
// }
//
// async fn read_csw(&mut self, tag: u32) -> Result<(), Error> {
// let mut csw_bytes = [0; 16];
// self.in_pipe
// .read_exact(&mut csw_bytes[..13])
// .await
// .inspect_err(|error| log::error!("msc: CSW receive error: {error:?}"))?;
// let csw = bytemuck::from_bytes::<Csw>(&csw_bytes);
//
// if csw.signature != 0x53425355 {
// log::warn!("msc: invalid csw signature");
// return Err(Error::InvalidArgument);
// }
// if csw.tag != tag {
// let csw_tag = csw.tag;
// log::warn!("msc: invalid csw tag (got {}, expected {tag})", csw_tag);
// return Err(Error::InvalidArgument);
// }
// if csw.status != 0x00 {
// return Err(Error::InvalidArgument);
// }
// Ok(())
// }
//
// async fn read_response_data(
// &mut self,
// buffer: DmaSliceMut<'_, MaybeUninit<u8>>,
// ) -> Result<usize, Error> {
// if buffer.len() == 0 {
// return Ok(0);
// }
// let len = self
// .in_pipe
// .read_dma(buffer)
// .await
// .inspect_err(|error| log::error!("msc: DMA read error: {error:?}"))?;
// Ok(len)
// }
// }
//
// #[async_trait]
// impl ScsiTransport for Bbb {
// fn allocate_buffer(&self, size: usize) -> Result<DmaBuffer<[MaybeUninit<u8>]>, Error> {
// Ok(self.in_pipe.allocate_dma_buffer(size)?)
// }
//
// async fn perform_request_raw(
// &mut self,
// lun: u8,
// request_data: &[u8],
// response_buffer: DmaSliceMut<'_, MaybeUninit<u8>>,
// ) -> Result<usize, Error> {
// if request_data.len() > 16 || response_buffer.len() > self.max_bytes_per_request() {
// return Err(Error::InvalidArgument);
// }
//
// let tag = self
// .send_cbw(lun, false, request_data, response_buffer.len())
// .await?;
// let response_len = self.read_response_data(response_buffer).await?;
// self.read_csw(tag).await?;
// Ok(response_len)
// }
//
// fn max_bytes_per_request(&self) -> usize {
// 32768
// }
// }
//
// impl UsbDeviceDetachHandler for DetachHandler {
// fn handle_device_detach(&self) {
// log::info!("Mass storage detached");
// self.0.detach();
// }
// }
//
// #[derive(Debug, Pod, Zeroable, Clone, Copy)]
// #[repr(C)]
// pub struct BulkOnlyMassStorageReset;
//
// #[derive(Debug, Pod, Zeroable, Clone, Copy)]
// #[repr(C)]
// pub struct GetMaxLun;
//
// impl UsbClassSpecificRequest for BulkOnlyMassStorageReset {
// const BM_REQUEST_TYPE: u8 = 0b00100001;
// const B_REQUEST: u8 = 0b11111111;
// }
//
// impl UsbClassSpecificRequest for GetMaxLun {
// const BM_REQUEST_TYPE: u8 = 0b10100001;
// const B_REQUEST: u8 = 0b11111110;
// }
//
// #[async_trait]
// impl UsbDriver for UsbMassStorageDriverBulkOnly {
// async fn run(self: Arc<Self>, device: Arc<UsbDeviceAccess>) -> Result<(), UsbError> {
// // TODO filter to only accept BBB config
// let config = device.select_configuration(|_| true).await?.unwrap();
// // Bulk-in, bulk-out
// assert_eq!(config.endpoints.len(), 2);
// let control_pipe = device.control_pipe();
// let (in_index, in_info) = config
// .find_endpoint(|ep| ep.is(UsbEndpointType::Bulk, UsbDirection::In))
// .ok_or(UsbError::InvalidConfiguration)?;
// let (out_index, out_info) = config
// .find_endpoint(|ep| ep.is(UsbEndpointType::Bulk, UsbDirection::Out))
// .ok_or(UsbError::InvalidConfiguration)?;
// let in_pipe = device
// .open_bulk_in_pipe(in_index, in_info.max_packet_size as u16)
// .await?;
// let out_pipe = device
// .open_bulk_out_pipe(out_index, out_info.max_packet_size as u16)
// .await?;
//
// // Perform a Bulk-Only Mass Storage Reset
// // TODO interface id?
// control_pipe
// .control_transfer(ControlTransferSetup {
// bm_request_type: BulkOnlyMassStorageReset::BM_REQUEST_TYPE,
// b_request: BulkOnlyMassStorageReset::B_REQUEST,
// w_value: 0,
// w_index: 0,
// w_length: 0,
// })
// .await?;
//
// // Get max LUN
// // TODO on devices which do not support multiple LUNs, this command may STALL
// let mut buffer = [MaybeUninit::uninit()];
// let len = control_pipe
// .control_transfer_in(
// ControlTransferSetup {
// bm_request_type: GetMaxLun::BM_REQUEST_TYPE,
// b_request: GetMaxLun::B_REQUEST,
// w_value: 0,
// w_index: 0,
// w_length: 1,
// },
// &mut buffer,
// )
// .await?;
// let max_lun = if len < 1 {
// 0
// } else {
// unsafe { buffer[0].assume_init() }
// };
//
// let bbb = Bbb::new(device.clone(), in_pipe, out_pipe)?;
// let scsi = ScsiEnclosure::setup(Box::new(bbb), max_lun as usize + 1)
// .await
// .inspect_err(|error| log::error!("msc: scsi error {error:?}"))
// .map_err(|_| UsbError::DriverError)?;
// let detach = DetachHandler(scsi.clone());
// device.set_detach_handler(Arc::new(detach));
//
// Ok(())
// }
//
// fn name(&self) -> &'static str {
// "USB Mass Storage"
// }
//
// fn probe(&self, class: &UsbClassInfo, _device: &UsbDeviceAccess) -> bool {
// // TODO support other protocols
// class.class == UsbDeviceClass::MassStorage && class.interface_protocol_number == 0x50
// }
// }
+76 -85
View File
@@ -4,114 +4,105 @@ use libk::task::runtime;
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
use crate::{
device::UsbDeviceAccess,
error::UsbError,
info::{UsbDeviceClass, UsbDeviceProtocol},
address::UsbInterfaceAddress, device::UsbDeviceAccess, error::UsbError, info::UsbInterfaceInfo,
};
pub mod hid_keyboard;
pub mod mass_storage;
pub mod hid_mouse;
// pub mod mass_storage;
#[derive(Debug)]
pub struct UsbClassInfo {
pub class: UsbDeviceClass,
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UsbInterfaceClass {
pub class: u8,
pub subclass: u8,
pub protocol: UsbDeviceProtocol,
pub device_protocol_number: u8,
pub interface_protocol_number: u8,
pub protocol: u8,
}
#[async_trait]
pub trait UsbDriver: Send + Sync {
async fn run(self: Arc<Self>, device: Arc<UsbDeviceAccess>) -> Result<(), UsbError>;
pub trait UsbInterfaceDriver: Send + Sync {
async fn run(
self: Arc<Self>,
device: Arc<UsbDeviceAccess>,
interface: UsbInterfaceInfo,
) -> Result<(), UsbError>;
fn name(&self) -> &'static str;
fn probe(&self, class: &UsbClassInfo, device: &UsbDeviceAccess) -> bool;
fn probe(
&self,
device: &UsbDeviceAccess,
interface: &UsbInterfaceInfo,
class: UsbInterfaceClass,
) -> bool;
}
async fn extract_class_info(device: &UsbDeviceAccess) -> Result<Option<UsbClassInfo>, UsbError> {
if device.info.num_configurations != 1 {
return Ok(None);
}
let device_info = &device.info;
let config_info = device.query_configuration_info(0).await?;
if config_info.interfaces.len() >= 1 {
let if_info = &config_info.interfaces[0];
let class = if device_info.device_class == UsbDeviceClass::FromInterface {
if_info.interface_class
} else {
device_info.device_class
};
let subclass = if device_info.device_subclass == 0 {
if_info.interface_subclass
} else {
device_info.device_subclass
};
let protocol = if device_info.device_protocol == UsbDeviceProtocol::FromInterface {
if_info.interface_protocol
} else {
device_info.device_protocol
};
Ok(Some(UsbClassInfo {
class,
subclass,
protocol,
interface_protocol_number: if_info.interface_protocol_number,
device_protocol_number: device_info.device_protocol_number,
}))
} else {
Ok(None)
}
}
async fn pick_driver(
device: &UsbDeviceAccess,
) -> Result<Option<Arc<dyn UsbDriver + 'static>>, UsbError> {
let Some(class) = extract_class_info(device).await? else {
return Ok(None);
async fn setup_interface(
device: &Arc<UsbDeviceAccess>,
address: UsbInterfaceAddress,
interface: &UsbInterfaceInfo,
) -> Result<(), UsbError> {
let class = UsbInterfaceClass {
class: interface.interface_class,
subclass: interface.interface_subclass,
protocol: interface.interface_protocol,
};
for driver in USB_DEVICE_DRIVERS.read().iter() {
if driver.probe(&class, device) {
return Ok(Some(driver.clone()));
}
}
Ok(None)
}
pub async fn spawn_driver(device: Arc<UsbDeviceAccess>) -> Result<(), UsbError> {
if let Some(driver) = pick_driver(&device).await? {
runtime::spawn(async move {
let name = driver.name();
match driver.run(device).await {
e @ Err(UsbError::DeviceDisconnected) => {
log::warn!(
"Driver {:?} did not exit cleanly: device disconnected",
name,
);
e
let drivers = USB_INTERFACE_DRIVERS.read();
for driver in drivers.iter() {
if driver.probe(device, interface, class) {
let driver = driver.clone();
let device = device.clone();
let interface = interface.clone();
runtime::spawn(async move {
let name = driver.name();
match driver.run(device, interface).await {
e @ Err(UsbError::DeviceDisconnected) => {
log::warn!("{address} did not exit cleanly: device disconnected ({name})");
e
}
e => e,
}
e => e,
}
})
.map_err(UsbError::SystemError)?;
})
.map_err(UsbError::SystemError)?;
break;
}
}
Ok(())
}
pub fn register_driver(driver: Arc<dyn UsbDriver + 'static>) {
pub async fn setup_device(device: Arc<UsbDeviceAccess>) -> Result<(), UsbError> {
// If device has only one configuration available, use it
// TODO support devices with multiple configurations
let address = device.bus_address();
log::info!("Setup USB device @ {address}");
let Some(config_info) = device.use_default_configuration().await? else {
log::warn!("{address} has multiple configurations, not supported yet",);
return Ok(());
};
// Setup drivers for interfaces
log::info!("{address}: {config_info:#?}");
// TODO device-level drivers
for interface in config_info.interfaces.iter() {
let address = address.with_interface(interface.number);
if let Err(error) = setup_interface(&device, address, interface).await {
log::error!("{}: {:?}", address, error);
}
}
Ok(())
}
pub fn register_driver(driver: Arc<dyn UsbInterfaceDriver + 'static>) {
// TODO check for duplicates
USB_DEVICE_DRIVERS.write().push(driver);
USB_INTERFACE_DRIVERS.write().push(driver);
}
pub fn register_default_class_drivers() {
register_driver(Arc::new(hid_keyboard::UsbHidKeyboardDriver));
register_driver(Arc::new(mass_storage::UsbMassStorageDriverBulkOnly));
register_driver(Arc::new(hid_mouse::UsbHidMouseDriver));
// register_driver(Arc::new(mass_storage::UsbMassStorageDriverBulkOnly));
}
static USB_DEVICE_DRIVERS: IrqSafeRwLock<Vec<Arc<dyn UsbDriver + 'static>>> =
static USB_INTERFACE_DRIVERS: IrqSafeRwLock<Vec<Arc<dyn UsbInterfaceDriver + 'static>>> =
IrqSafeRwLock::new(Vec::new());
+22 -21
View File
@@ -1,10 +1,7 @@
use bytemuck::{Pod, Zeroable};
use crate::{
communication::UsbDirection,
device::UsbSpeed,
error::UsbError,
info::{UsbDeviceClass, UsbDeviceProtocol, UsbEndpointType, UsbVersion},
communication::UsbDirection, device::UsbSpeed, error::UsbError, info::UsbEndpointType,
};
#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
@@ -91,15 +88,15 @@ pub struct UsbOtherSpeedConfiguration {
pub max_power: u8,
}
impl UsbInterfaceDescriptor {
pub fn class(&self) -> UsbDeviceClass {
UsbDeviceClass::try_from(self.interface_class).unwrap_or(UsbDeviceClass::Unknown)
}
pub fn protocol(&self) -> UsbDeviceProtocol {
UsbDeviceProtocol::try_from(self.interface_protocol).unwrap_or(UsbDeviceProtocol::Unknown)
}
}
// impl UsbInterfaceDescriptor {
// pub fn class(&self) -> UsbDeviceClass {
// UsbDeviceClass::try_from(self.interface_class).unwrap_or(UsbDeviceClass::Unknown)
// }
//
// pub fn protocol(&self) -> UsbDeviceProtocol {
// UsbDeviceProtocol::try_from(self.interface_protocol).unwrap_or(UsbDeviceProtocol::Unknown)
// }
// }
impl UsbEndpointDescriptor {
pub fn direction(&self) -> UsbDirection {
@@ -127,16 +124,16 @@ impl UsbEndpointDescriptor {
}
impl UsbDeviceDescriptor {
pub fn class(&self) -> UsbDeviceClass {
UsbDeviceClass::try_from(self.device_class).unwrap_or(UsbDeviceClass::Unknown)
}
// pub fn class(&self) -> UsbDeviceClass {
// UsbDeviceClass::try_from(self.device_class).unwrap_or(UsbDeviceClass::Unknown)
// }
pub fn protocol(&self) -> UsbDeviceProtocol {
UsbDeviceProtocol::try_from(self.device_protocol).unwrap_or(UsbDeviceProtocol::Unknown)
}
// pub fn protocol(&self) -> UsbDeviceProtocol {
// UsbDeviceProtocol::try_from(self.device_protocol).unwrap_or(UsbDeviceProtocol::Unknown)
// }
pub fn max_packet_size(&self, version: UsbVersion, speed: UsbSpeed) -> Result<usize, UsbError> {
match (version.is_version_3(), speed, self.max_packet_size_0) {
pub fn max_packet_size(&self, version: u16, speed: UsbSpeed) -> Result<usize, UsbError> {
match (is_version_3(version), speed, self.max_packet_size_0) {
(true, UsbSpeed::Super, 9) => Ok(1 << 9),
(true, _, _) => todo!("Non-GenX speed USB3+ maxpacketsize0"),
(false, _, 8) => Ok(8),
@@ -147,3 +144,7 @@ impl UsbDeviceDescriptor {
}
}
}
pub fn is_version_3(version: u16) -> bool {
version & 0xFF00 == 0x300
}
+75 -64
View File
@@ -1,14 +1,15 @@
use core::{fmt, ops::Deref};
use core::ops::Deref;
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use async_trait::async_trait;
use libk_util::sync::spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard};
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
use crate::{
address::UsbBusAddress,
bus::UsbBusWrapper,
error::UsbError,
info::{
UsbConfigurationInfo, UsbDeviceInfo, UsbEndpointInfo, UsbEndpointType, UsbInterfaceInfo,
UsbVersion,
},
pipe::{
control::{ConfigurationDescriptorEntry, UsbControlPipeAccess},
@@ -17,21 +18,26 @@ use crate::{
UsbNormalPipeOut,
},
},
sysfs::UsbDeviceKObject,
UsbHostController,
};
// High-level structures for info provided through descriptors
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct UsbBusAddress {
pub bus: u16,
pub device: u8,
}
// #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
// pub struct UsbBusAddress {
// pub bus: u16,
// pub device: u8,
// }
pub struct UsbDeviceAccess {
pub device: Arc<dyn UsbDevice>,
pub bus: Arc<UsbBusWrapper>,
pub info: UsbDeviceInfo,
pub current_configuration: IrqSafeRwLock<Option<UsbConfigurationInfo>>,
pub configurations: Vec<UsbConfigurationInfo>,
current_configuration: IrqSafeRwLock<Option<usize>>,
pub(crate) kobject: OneTimeInit<UsbDeviceKObject>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
@@ -69,7 +75,7 @@ pub trait UsbDevice: Send + Sync {
fn port_number(&self) -> u8;
fn bus_address(&self) -> UsbBusAddress;
fn speed(&self) -> UsbSpeed;
fn controller_ref(&self) -> &dyn UsbHostController;
fn host_controller(&self) -> Arc<dyn UsbHostController>;
fn set_detach_handler(&self, handler: Arc<dyn UsbDeviceDetachHandler>);
fn handle_detach(&self);
@@ -84,47 +90,43 @@ impl UsbDeviceAccess {
/// * Device is not yet configured
/// * Control pipe for the device has been properly set up
/// * Device has been assigned a bus address
pub async fn setup(raw: Arc<dyn UsbDevice>) -> Result<Self, UsbError> {
pub async fn setup(bus: Arc<UsbBusWrapper>, raw: Arc<dyn UsbDevice>) -> Result<Self, UsbError> {
let control = raw.control_pipe();
let device_desc = control.query_device_descriptor().await?;
let bcd_usb = device_desc.bcd_usb;
let usb_version = UsbVersion::from_bcd_usb(device_desc.bcd_usb)
.ok_or(UsbError::InvalidDescriptorField)
.inspect_err(|_| {
log::error!(
"{}: unsupported/invalid USB version: {:#x}",
raw.bus_address(),
bcd_usb
)
})?;
let manufacturer = control.query_string(device_desc.manufacturer_str).await?;
let product = control.query_string(device_desc.product_str).await?;
// Query device
let info = UsbDeviceInfo {
manufacturer,
product,
usb_version,
usb_version: device_desc.bcd_usb,
id_vendor: device_desc.id_vendor,
id_product: device_desc.id_product,
device_class: device_desc.class(),
device_class: device_desc.device_class,
device_subclass: device_desc.device_subclass,
device_protocol: device_desc.protocol(),
device_protocol_number: device_desc.device_protocol,
device_protocol: device_desc.device_protocol,
num_configurations: device_desc.num_configurations,
max_packet_size: device_desc.max_packet_size(usb_version, raw.speed())?,
max_packet_size: device_desc.max_packet_size(device_desc.bcd_usb, raw.speed())?,
};
let configurations =
Self::query_configurations(control, device_desc.num_configurations).await?;
Ok(Self {
device: raw,
bus,
info,
current_configuration: IrqSafeRwLock::new(None),
configurations,
kobject: OneTimeInit::new(),
})
}
@@ -164,45 +166,54 @@ impl UsbDeviceAccess {
Ok(UsbBulkOutPipeAccess(pipe))
}
pub fn read_current_configuration(
&self,
) -> IrqSafeRwLockReadGuard<'_, Option<UsbConfigurationInfo>> {
self.current_configuration.read()
pub fn current_configuration(&self) -> Option<&UsbConfigurationInfo> {
let index = (*self.current_configuration.read())?;
Some(&self.configurations[index])
}
pub async fn select_configuration<F: Fn(&UsbConfigurationInfo) -> bool>(
pub async fn use_default_configuration(
&self,
predicate: F,
) -> Result<Option<UsbConfigurationInfo>, UsbError> {
let mut current_config = self.current_configuration.write();
let control_pipe = self.control_pipe();
for i in 0..self.info.num_configurations {
let info = self.query_configuration_info(i).await?;
if predicate(&info) {
log::debug!("Selected configuration: {:#?}", info);
let config = current_config.insert(info);
control_pipe
.set_configuration(config.config_value as _)
.await?;
return Ok(Some(config.clone()));
}
if self.configurations.len() != 1 {
return Ok(None);
}
Ok(None)
self.set_configuration(0).await.map(Some)
}
pub async fn query_configuration_info(
&self,
index: u8,
) -> Result<UsbConfigurationInfo, UsbError> {
if index >= self.info.num_configurations {
pub async fn set_configuration(&self, index: usize) -> Result<UsbConfigurationInfo, UsbError> {
if index >= self.configurations.len() {
return Err(UsbError::InvalidConfiguration);
}
let mut current = self.current_configuration.write();
let control_pipe = self.control_pipe();
let info = self.configurations[index].clone();
control_pipe
.set_configuration(info.config_value as _)
.await?;
*current = Some(index);
Ok(info)
}
async fn query_configurations(
control_pipe: &UsbControlPipeAccess,
num_configurations: u8,
) -> Result<Vec<UsbConfigurationInfo>, UsbError> {
let mut configurations = Vec::new();
for i in 0..num_configurations {
let configuration = Self::query_configuration(control_pipe, i).await?;
configurations.push(configuration);
}
Ok(configurations)
}
async fn query_configuration(
control_pipe: &UsbControlPipeAccess,
index: u8,
) -> Result<UsbConfigurationInfo, UsbError> {
let query = control_pipe.query_configuration_descriptor(index).await?;
let configuration_name = control_pipe
@@ -228,10 +239,9 @@ impl UsbDeviceAccess {
name,
number: iface.interface_number,
interface_class: iface.class(),
interface_class: iface.interface_class,
interface_subclass: iface.interface_subclass,
interface_protocol: iface.protocol(),
interface_protocol_number: iface.interface_protocol,
interface_protocol: iface.interface_protocol,
});
}
_ => (),
@@ -248,6 +258,13 @@ impl UsbDeviceAccess {
Ok(info)
}
// pub async fn query_configuration_info(
// &self,
// index: u8,
// ) -> Result<UsbConfigurationInfo, UsbError> {
// let control_pipe = self.control_pipe();
// }
pub fn set_detach_handler(&self, handler: Arc<dyn UsbDeviceDetachHandler>) {
self.device.set_detach_handler(handler);
}
@@ -260,9 +277,3 @@ impl Deref for UsbDeviceAccess {
&*self.device
}
}
impl fmt::Display for UsbBusAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.bus, self.device)
}
}
+8 -67
View File
@@ -1,7 +1,4 @@
use core::fmt;
use alloc::{string::String, vec::Vec};
use yggdrasil_abi::primitive_enum;
use crate::communication::UsbDirection;
@@ -29,41 +26,18 @@ pub enum UsbUsageType {
Reserved,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum UsbVersion {
Usb11,
Usb20,
Usb21,
Usb30,
Usb31,
Usb32,
}
primitive_enum! {
pub enum UsbDeviceClass: u8 {
FromInterface = 0x00,
Hid = 0x03,
MassStorage = 0x08,
Unknown = 0xFF,
}
}
primitive_enum! {
pub enum UsbDeviceProtocol: u8 {
FromInterface = 0x00,
Unknown = 0xFF,
}
}
pub const CLASS_FROM_INTERFACE: u8 = 0x00;
pub const CLASS_HID: u8 = 0x03;
pub const CLASS_MASS_STORAGE: u8 = 0x08;
#[derive(Debug, Clone)]
pub struct UsbInterfaceInfo {
pub name: String,
pub number: u8,
pub interface_class: UsbDeviceClass,
pub interface_class: u8,
pub interface_subclass: u8,
pub interface_protocol: UsbDeviceProtocol,
pub interface_protocol_number: u8,
pub interface_protocol: u8,
}
#[derive(Debug, Clone)]
@@ -87,15 +61,14 @@ pub struct UsbDeviceInfo {
pub manufacturer: String,
pub product: String,
pub usb_version: UsbVersion,
pub usb_version: u16,
pub id_vendor: u16,
pub id_product: u16,
pub device_class: UsbDeviceClass,
pub device_class: u8,
pub device_subclass: u8,
pub device_protocol: UsbDeviceProtocol,
pub device_protocol_number: u8,
pub device_protocol: u8,
/// Max packet size for endpoint zero
pub max_packet_size: usize,
@@ -103,38 +76,6 @@ pub struct UsbDeviceInfo {
pub num_configurations: u8,
}
impl UsbVersion {
pub fn is_version_3(&self) -> bool {
matches!(self, Self::Usb30 | Self::Usb31 | Self::Usb32)
}
pub fn from_bcd_usb(value: u16) -> Option<Self> {
match value {
0x110 => Some(UsbVersion::Usb11),
0x200..=0x20F => Some(UsbVersion::Usb20),
0x210..=0x21F => Some(UsbVersion::Usb21),
0x300 => Some(UsbVersion::Usb30),
0x310 => Some(UsbVersion::Usb31),
0x320 => Some(UsbVersion::Usb32),
_ => None,
}
}
}
impl fmt::Display for UsbVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = match self {
Self::Usb11 => "USB1.1",
Self::Usb20 => "USB2.0",
Self::Usb21 => "USB2.1",
Self::Usb30 => "USB3.0",
Self::Usb31 => "USB3.1",
Self::Usb32 => "USB3.2",
};
f.write_str(string)
}
}
impl UsbEndpointInfo {
pub fn is(&self, ty: UsbEndpointType, dir: UsbDirection) -> bool {
self.ty == ty && self.direction == dir
+1
View File
@@ -0,0 +1 @@
pub struct UsbInterface {}
+10 -1
View File
@@ -8,15 +8,20 @@
maybe_uninit_fill
)]
use crate::sysfs::UsbBusKObject;
extern crate alloc;
pub mod address;
pub mod bus;
pub mod communication;
pub mod descriptor;
pub mod device;
pub mod error;
pub mod info;
pub mod interface;
pub mod pipe;
pub mod sysfs;
pub mod util;
pub mod class_driver;
@@ -25,4 +30,8 @@ pub mod class_driver;
pub trait UsbEndpoint: Sync {}
pub trait UsbHostController: Sync + Send {}
pub trait UsbHostController: Sync + Send {
fn register_sysfs_properties(&self, kobject: &UsbBusKObject) {
let _ = kobject;
}
}
@@ -37,6 +37,7 @@ pub trait UsbDeviceRequest: Sized + Pod {
pub trait UsbClassSpecificRequest: Sized + Pod {
const BM_REQUEST_TYPE: u8;
const B_REQUEST: u8;
const W_VALUE: u16 = 0;
}
pub trait UsbDescriptorRequest: UsbDeviceRequest {
+129
View File
@@ -0,0 +1,129 @@
use alloc::{format, sync::Arc};
use libk::{
error::Error,
fs::sysfs::{
self,
attribute::{IntegerAttribute, IntegerAttributeFormat, IntegerAttributeOps},
object::KObject,
},
};
use libk_util::OneTimeInit;
use crate::{bus::UsbBusWrapper, device::UsbDeviceAccess};
pub type UsbBusKObject = Arc<KObject<Arc<UsbBusWrapper>>>;
pub type UsbDeviceKObject = Arc<KObject<Arc<UsbDeviceAccess>>>;
pub(crate) fn register_bus_kobject(bus: &Arc<UsbBusWrapper>) -> UsbBusKObject {
let root = sysfs_usb_root();
let bus_kobject = KObject::new(bus.clone());
bus.hc.register_sysfs_properties(&bus_kobject);
root.add_object(format!("{}", bus.index), bus_kobject.clone())
.ok();
bus_kobject
}
pub(crate) fn register_device_kobject(device: &Arc<UsbDeviceAccess>) -> UsbDeviceKObject {
struct Class;
struct Subclass;
struct Protocol;
struct Version;
struct IdVendor;
struct IdProduct;
impl IntegerAttributeOps<u8> for Class {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "class";
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Hex;
fn read(state: &Self::Data) -> Result<u8, Error> {
Ok(state.info.device_class)
}
}
impl IntegerAttributeOps<u8> for Subclass {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "subclass";
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Hex;
fn read(state: &Self::Data) -> Result<u8, Error> {
Ok(state.info.device_subclass)
}
}
impl IntegerAttributeOps<u8> for Protocol {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "protocol";
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Hex;
fn read(state: &Self::Data) -> Result<u8, Error> {
Ok(state.info.device_protocol)
}
}
impl IntegerAttributeOps<u16> for Version {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "version";
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Hex;
fn read(state: &Self::Data) -> Result<u16, Error> {
Ok(state.info.usb_version)
}
}
impl IntegerAttributeOps<u16> for IdVendor {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "vendor";
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Hex;
fn read(state: &Self::Data) -> Result<u16, Error> {
Ok(state.info.id_vendor)
}
}
impl IntegerAttributeOps<u16> for IdProduct {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "product";
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Hex;
fn read(state: &Self::Data) -> Result<u16, Error> {
Ok(state.info.id_product)
}
}
let bus_kobject = device.bus.kobject();
let device_kobject = KObject::new(device.clone());
device_kobject
.add_attribute(IntegerAttribute::from(Class))
.ok();
device_kobject
.add_attribute(IntegerAttribute::from(Subclass))
.ok();
device_kobject
.add_attribute(IntegerAttribute::from(Protocol))
.ok();
device_kobject
.add_attribute(IntegerAttribute::from(Version))
.ok();
device_kobject
.add_attribute(IntegerAttribute::from(IdVendor))
.ok();
device_kobject
.add_attribute(IntegerAttribute::from(IdProduct))
.ok();
let address = device.bus_address();
bus_kobject
.add_object(format!("{}", address.device), device_kobject.clone())
.ok();
device_kobject
}
fn sysfs_usb_root() -> &'static Arc<KObject<()>> {
static USB_ROOT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
USB_ROOT.or_init_with(|| {
let bus_object = sysfs::bus().expect("bus object");
let usb_object = KObject::new(());
bus_object.add_object("usb", usb_object.clone()).ok();
usb_object
})
}
+67 -8
View File
@@ -9,14 +9,20 @@ use async_trait::async_trait;
use device_api::device::Device;
use libk::{device::char::CharDevice, vfs::FileReadiness};
use libk_util::{ring::LossyRingQueue, OneTimeInit};
use yggdrasil_abi::{error::Error, io::KeyboardKeyEvent};
use yggdrasil_abi::{
abi_serde::wire,
error::Error,
io::{KeyboardKeyEvent, MouseEvent},
};
#[derive(Clone, Copy)]
pub struct KeyboardDevice;
#[derive(Clone, Copy)]
pub struct MouseDevice;
impl FileReadiness for KeyboardDevice {
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
INPUT_QUEUE.poll_readable(cx).map(Ok)
KEYBOARD_INPUT_QUEUE.poll_readable(cx).map(Ok)
}
}
@@ -33,7 +39,7 @@ impl CharDevice for KeyboardDevice {
return Ok(0);
}
let ev = INPUT_QUEUE.read().await;
let ev = KEYBOARD_INPUT_QUEUE.read().await;
buf[..4].copy_from_slice(&ev.as_bytes());
@@ -45,7 +51,7 @@ impl CharDevice for KeyboardDevice {
return Ok(0);
}
let ev = INPUT_QUEUE.try_read().ok_or(Error::WouldBlock)?;
let ev = KEYBOARD_INPUT_QUEUE.try_read().ok_or(Error::WouldBlock)?;
buf[..4].copy_from_slice(&ev.as_bytes());
@@ -68,15 +74,68 @@ impl CharDevice for KeyboardDevice {
}
}
static INPUT_QUEUE: LossyRingQueue<KeyboardKeyEvent> = LossyRingQueue::with_capacity(32);
impl FileReadiness for MouseDevice {
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
MOUSE_INPUT_QUEUE.poll_readable(cx).map(Ok)
}
}
impl Device for MouseDevice {
fn display_name(&self) -> &str {
"Mouse input pseudo-device"
}
}
#[async_trait]
impl CharDevice for MouseDevice {
async fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
let ev = MOUSE_INPUT_QUEUE.read().await;
let len = wire::to_slice(&ev, buf)?;
Ok(len)
}
fn read_nonblocking(&self, buf: &mut [u8]) -> Result<usize, Error> {
let ev = MOUSE_INPUT_QUEUE.try_read().ok_or(Error::WouldBlock)?;
let len = wire::to_slice(&ev, buf)?;
Ok(len)
}
fn is_writeable(&self) -> bool {
false
}
fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
let _ = option;
let _ = buffer;
let _ = len;
Err(Error::InvalidOperation)
}
fn is_terminal(&self) -> bool {
false
}
}
static KEYBOARD_INPUT_QUEUE: LossyRingQueue<KeyboardKeyEvent> = LossyRingQueue::with_capacity(32);
static KEYBOARD_DEVICE: OneTimeInit<Arc<KeyboardDevice>> = OneTimeInit::new();
pub fn setup() -> Arc<KeyboardDevice> {
static MOUSE_INPUT_QUEUE: LossyRingQueue<MouseEvent> = LossyRingQueue::with_capacity(32);
static MOUSE_DEVICE: OneTimeInit<Arc<MouseDevice>> = OneTimeInit::new();
pub fn setup_keyboard() -> Arc<KeyboardDevice> {
KEYBOARD_DEVICE
.or_init_with(|| Arc::new(KeyboardDevice))
.clone()
}
pub fn send_event(ev: KeyboardKeyEvent) {
INPUT_QUEUE.write(ev);
pub fn setup_mouse() -> Arc<MouseDevice> {
MOUSE_DEVICE.or_init_with(|| Arc::new(MouseDevice)).clone()
}
pub fn send_keyboard_event(ev: KeyboardKeyEvent) {
KEYBOARD_INPUT_QUEUE.write(ev);
}
pub fn send_mouse_event(ev: MouseEvent) {
MOUSE_INPUT_QUEUE.write(ev);
}
+17 -10
View File
@@ -125,24 +125,31 @@ impl<'a, M: MdioBus> PhyAccess<'a, M> {
})
}
pub fn setup_link(&self, have_pause: bool, force_gbesr: GBESR) -> Result<(), Error> {
pub fn setup_link(&self, have_pause: bool, force_gbesr: Option<GBESR>) -> Result<(), Error> {
let bmsr = BMSR::from(self.read_reg(REG_BMSR)?);
let mut gbesr = if bmsr.contains(BMSR::EXT_STATUS_1000BASET) {
GBESR::from(self.read_reg(REG_GBESR)?)
let gbesr = if let Some(force_gbesr) = force_gbesr {
let mut gbesr = if bmsr.contains(BMSR::EXT_STATUS_1000BASET) {
GBESR::from(self.read_reg(REG_GBESR)?)
} else {
GBESR::empty()
};
gbesr |= force_gbesr;
Some(gbesr)
} else {
GBESR::empty()
None
};
gbesr |= force_gbesr;
let mut anar = ANAR::from_capabilities(bmsr);
if have_pause {
anar |= ANAR::HAVE_PAUSE | ANAR::ASM_DIR;
}
let mut gbcr = GBCR::empty();
if gbesr.contains(GBESR::HAVE_1000BASET_HALF) {
gbcr |= GBCR::HAVE_1000BASET_HALF;
}
if gbesr.contains(GBESR::HAVE_1000BASET_FULL) {
gbcr |= GBCR::HAVE_1000BASET_FULL;
if let Some(gbesr) = gbesr {
if gbesr.contains(GBESR::HAVE_1000BASET_HALF) {
gbcr |= GBCR::HAVE_1000BASET_HALF;
}
if gbesr.contains(GBESR::HAVE_1000BASET_FULL) {
gbcr |= GBCR::HAVE_1000BASET_FULL;
}
}
self.write_reg(REG_ANAR, anar.bits())?;
+22 -3
View File
@@ -26,12 +26,15 @@ use ygg_driver_pci::{
};
use yggdrasil_abi::net::{link::LinkState, MacAddress};
use crate::regs::Revision;
extern crate alloc;
mod regs;
mod ring;
struct Igbe {
chip: Revision,
regs: IrqSafeSpinlock<Regs>,
dma: Arc<dyn DmaAllocator>,
pci: PciDeviceInfo,
@@ -43,8 +46,9 @@ struct Igbe {
}
impl Igbe {
pub fn new(dma: Arc<dyn DmaAllocator>, regs: Regs, pci: PciDeviceInfo) -> Self {
pub fn new(dma: Arc<dyn DmaAllocator>, regs: Regs, chip: Revision, pci: PciDeviceInfo) -> Self {
Self {
chip,
dma,
pci,
mac: OneTimeInit::new(),
@@ -74,7 +78,7 @@ impl Device for Igbe {
regs.reset(Duration::from_millis(200))?;
// Intel 8257x manuals say an additional interrupt disable is needed after a global reset
regs.disable_interrupts();
regs.set_link_up()?;
regs.set_link_up(self.chip)?;
// Initialize Rx
regs.initialize_receiver(&rx_ring);
@@ -175,6 +179,10 @@ impl NetworkDevice for Igbe {
pci_driver! {
matches: [
device (0x8086:0x100E), // 82540EM (E1000)
device (0x8086:0x100C), // 82544GC (E1000)
device (0x8086:0x100F), // 82545EM (E1000)
device (0x8086:0x10D3), // 82574L (E1000E) [[BROKEN]]
device (0x8086:0x10C9), // 82576 GbE
device (0x8086:0x1502), // 82579LM GbE (Lewisville)
],
@@ -197,11 +205,22 @@ pci_driver! {
}
};
let chip = match info.device_id {
0x100E | 0x100C | 0x100F => Revision::I8254x,
0x10D3 => Revision::I82574L,
0x10C9 => Revision::I82576,
0x1502 => Revision::I82579LM,
id => {
log::error!("Invalid igbe chip variant: {id:#04x}");
return Err(Error::InvalidOperation)
},
};
info.init_interrupts(PreferredInterruptMode::Msi(true))?;
info.set_command(true, use_mmio, !use_mmio, true);
let regs = unsafe { Regs::map(base) }?;
let device = Igbe::new(dma.clone(), regs, info.clone());
let device = Igbe::new(dma.clone(), regs, chip, info.clone());
Ok(Arc::new(device))
}
+21 -3
View File
@@ -42,6 +42,14 @@ pub trait Reg {
const OFFSET: u16;
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Revision {
I8254x,
I82574L,
I82576,
I82579LM,
}
register_bitfields! {
u32,
pub CTRL [
@@ -329,6 +337,7 @@ impl MdioBus for Regs {
let mdic = self.inner.extract();
if mdic.matches_all(MDIC::E::SET) {
log::warn!("MDIO read error: phyaddr={phyaddr:#x}, regaddr={regaddr:#x}");
return Err(Error::InvalidOperation);
}
@@ -350,6 +359,9 @@ impl MdioBus for Regs {
)?;
if self.inner.matches_all(MDIC::E::SET) {
log::warn!(
"MDIO write error: phyaddr={phyaddr:#x}, regaddr={regaddr:#x}, value={value:#x}"
);
return Err(Error::InvalidOperation);
}
@@ -403,7 +415,7 @@ impl Regs {
})
}
pub fn set_link_up(&mut self) -> Result<(), Error> {
pub fn set_link_up(&mut self, chip: Revision) -> Result<(), Error> {
self.inner
.modify(CTRL::SLU::SET + CTRL::RFCE::SET + CTRL::TFCE::SET);
@@ -412,8 +424,14 @@ impl Regs {
let (id0, id1) = phy.id()?;
log::info!("PHY {:04x}:{:04x}", id0, id1);
phy.reset(Duration::from_millis(200))?;
phy.setup_link(true, GBESR::empty())?;
phy.reset(Duration::from_millis(200))
.inspect_err(|e| log::error!("PHY reset error {e:?}"))?;
let force_gbesr = match chip {
Revision::I82576 | Revision::I82579LM => Some(GBESR::empty()),
_ => None,
};
phy.setup_link(true, force_gbesr)
.inspect_err(|e| log::error!("PHY setup error: {e:?}"))?;
Ok(())
}
+1 -1
View File
@@ -498,7 +498,7 @@ impl Regs {
phy.write_reg(0x0E, 0x00)?;
phy.reset(timeout)?;
phy.setup_link(true, GBESR::empty())?;
phy.setup_link(true, Some(GBESR::empty()))?;
psleep(Duration::from_millis(100));
+1 -1
View File
@@ -337,7 +337,7 @@ impl Device for Stmmac {
let (id0, id1) = phy.id()?;
log::info!("stmmac: PHY {id0:04x}:{id1:04x}");
phy.reset(Duration::from_millis(100))?;
phy.setup_link(true, GBESR::empty())?;
phy.setup_link(true, Some(GBESR::empty()))?;
self.inner.init(Inner {
regs: IrqSafeSpinlock::new(regs),
+14 -11
View File
@@ -25,10 +25,11 @@ use tock_registers::{
};
use ygg_driver_pci::{device::PciDeviceInfo, PciConfigurationSpace};
use ygg_driver_usb::{
bus::UsbBusManager,
device::{UsbBusAddress, UsbDeviceAccess, UsbSpeed},
address::UsbBusAddress,
bus::{UsbBusManager, UsbBusWrapper},
descriptor,
device::{UsbDeviceAccess, UsbSpeed},
error::UsbError,
info::UsbVersion,
pipe::control::UsbControlPipeAccess,
UsbHostController,
};
@@ -67,7 +68,7 @@ struct ScratchpadArray {
}
struct RootHubPort {
version: UsbVersion,
version: u16,
slot_type: u8,
}
@@ -90,6 +91,7 @@ pub struct Xhci {
pub(crate) slots: Vec<IrqSafeRwLock<Option<Arc<XhciBusDevice>>>>,
pub(crate) port_slot_map: Vec<AtomicU8>,
bus_index: OneTimeInit<u16>,
bus: OneTimeInit<Arc<UsbBusWrapper>>,
port_event_map: EventBitmap,
}
@@ -146,9 +148,7 @@ impl Xhci {
for cap in regs.extended_capabilities.iter() {
match cap {
ExtendedCapability::ProtocolSupport(support) => {
let Some(version) = support.usb_revision() else {
continue;
};
let version = support.usb_revision();
for port in support.port_range() {
log::info!("* Port {port}: {version}");
@@ -195,6 +195,7 @@ impl Xhci {
root_hub_ports,
bus_index: OneTimeInit::new(),
bus: OneTimeInit::new(),
endpoints: IrqSafeRwLock::new(BTreeMap::new()),
slots,
port_slot_map,
@@ -282,7 +283,7 @@ impl Xhci {
.as_ref()
.ok_or(UsbError::PortInitFailed)?;
let need_reset = !root_hub_port.version.is_version_3();
let need_reset = !descriptor::is_version_3(root_hub_port.version);
if need_reset {
self.reset_port(regs).await?;
@@ -341,7 +342,8 @@ impl Xhci {
device: bus_address,
});
let device = UsbDeviceAccess::setup(slot).await?;
let bus = self.bus.get();
let device = UsbDeviceAccess::setup(bus.clone(), slot).await?;
UsbBusManager::register_device(device.into());
Ok(())
@@ -523,8 +525,9 @@ impl Device for Xhci {
op.wait_usbsts_bit(USBSTS::CNR::CLEAR, 100000000)?;
let bus = UsbBusManager::register_bus(self.clone());
self.bus_index.init(bus);
let (bus_index, bus) = UsbBusManager::register_bus(self.clone());
self.bus_index.init(bus_index);
self.bus.init(bus);
runtime::spawn(self.clone().port_handler_task()).ok();
+4 -3
View File
@@ -6,8 +6,9 @@ use libk_util::{
};
use xhci_lib::context;
use ygg_driver_usb::{
address::UsbBusAddress,
communication::UsbDirection,
device::{UsbBusAddress, UsbDevice, UsbDeviceDetachHandler, UsbSpeed},
device::{UsbDevice, UsbDeviceDetachHandler, UsbSpeed},
error::UsbError,
info::UsbEndpointType,
pipe::{
@@ -63,8 +64,8 @@ impl UsbDevice for XhciBusDevice {
*self.detach_handler.lock() = Some(handler);
}
fn controller_ref(&self) -> &dyn UsbHostController {
self.xhci.as_ref()
fn host_controller(&self) -> Arc<dyn UsbHostController> {
self.xhci.clone()
}
fn debug(&self) {}
+3 -3
View File
@@ -7,7 +7,7 @@ use alloc::vec::Vec;
use libk::error::Error;
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
use ygg_driver_usb::{error::UsbError, info::UsbVersion};
use ygg_driver_usb::error::UsbError;
pub struct ProtocolSupport {
words: [u32; 4],
@@ -69,8 +69,8 @@ impl ExtendedCapability {
}
impl ProtocolSupport {
pub fn usb_revision(&self) -> Option<UsbVersion> {
UsbVersion::from_bcd_usb((self.words[0] >> 16) as u16)
pub fn usb_revision(&self) -> u16 {
(self.words[0] >> 16) as u16
}
pub fn slot_type(&self) -> u8 {
@@ -0,0 +1,232 @@
use core::{any::Any, marker::PhantomData, sync::atomic::AtomicBool};
use alloc::{sync::Arc, vec::Vec};
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
use yggdrasil_abi::{
error::Error,
io::{FileMode, OpenOptions},
};
use crate::{
fs::sysfs::object::KObject,
vfs::{CommonImpl, Filename, InstanceData, Metadata, Node, NodeFlags, NodeRef, RegularImpl},
};
use super::Attribute;
pub trait IntegerAttributeValue: Copy + Sync + Send + 'static {
fn to_bytes(&self, format: IntegerAttributeFormat) -> Vec<u8>;
}
pub enum IntegerAttributeFormat {
Decimal,
Octal,
Hex,
}
macro_rules! impl_integer_value {
($($ty:ty),+) => {
$(
impl IntegerAttributeValue for $ty {
fn to_bytes(&self, format: IntegerAttributeFormat) -> Vec<u8> {
match format {
IntegerAttributeFormat::Decimal => alloc::format!("{self}"),
IntegerAttributeFormat::Octal => alloc::format!("{self:o}"),
IntegerAttributeFormat::Hex => alloc::format!("{self:x}"),
}.into_bytes()
}
}
)+
};
}
impl_integer_value!(u8, u16, u32, u64);
pub trait IntegerAttributeOps<T: IntegerAttributeValue>: Sync + Send + 'static {
type Data: Send + 'static = ();
const WRITEABLE: bool = false;
const NAME: &'static str;
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Decimal;
fn read(state: &Self::Data) -> Result<T, Error> {
let _ = state;
Err(Error::NotImplemented)
}
fn write(state: &Self::Data, value: T) -> Result<(), Error> {
let _ = state;
let _ = value;
Err(Error::ReadOnly)
}
}
pub struct IntegerAttribute<T: IntegerAttributeValue, V: IntegerAttributeOps<T>>(
PhantomData<(T, V)>,
);
struct IntegerAttributeNode<T: IntegerAttributeValue, V: IntegerAttributeOps<T>> {
object: Arc<KObject<V::Data>>,
_pd: PhantomData<V>,
}
struct IntegerAttributeState<T: IntegerAttributeValue> {
value: IrqSafeRwLock<Vec<u8>>,
modified: AtomicBool,
_pd: PhantomData<T>,
}
impl<T: IntegerAttributeValue, V: IntegerAttributeOps<T>> CommonImpl
for IntegerAttributeNode<T, V>
{
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
Ok(0)
}
fn as_any(&self) -> &dyn Any {
self as _
}
}
impl<T: IntegerAttributeValue, V: IntegerAttributeOps<T>> RegularImpl
for IntegerAttributeNode<T, V>
{
fn open(
&self,
_node: &NodeRef,
opts: OpenOptions,
) -> Result<(u64, Option<InstanceData>), Error> {
if opts.contains(OpenOptions::WRITE) && !V::WRITEABLE {
return Err(Error::ReadOnly);
}
let mut value = V::read(self.object.data())?.to_bytes(V::FORMAT);
value.push(b'\n');
let instance = IntegerAttributeState {
value: IrqSafeRwLock::new(value),
modified: AtomicBool::new(false),
_pd: PhantomData::<T>,
};
Ok((0, Some(Arc::new(instance))))
}
fn close(&self, _node: &NodeRef, instance: Option<&InstanceData>) -> Result<(), Error> {
if V::WRITEABLE {
let instance = instance.ok_or(Error::InvalidFile)?;
let instance = instance
.downcast_ref::<IntegerAttributeState<T>>()
.ok_or(Error::InvalidFile)?;
let _ = &instance.modified;
todo!()
// if instance.modified.load(Ordering::Acquire) {
// let value = instance.value.read();
// let value_str =
// core::str::from_utf8(&value[..]).map_err(|_| Error::InvalidArgument)?;
// // Trim whitespace and newlines
// V::write(&self.object.data, value_str.trim())?;
// }
}
Ok(())
}
fn read(
&self,
_node: &NodeRef,
instance: Option<&InstanceData>,
pos: u64,
buf: &mut [u8],
) -> Result<usize, Error> {
let instance = instance.ok_or(Error::InvalidFile)?;
let instance = instance
.downcast_ref::<IntegerAttributeState<T>>()
.ok_or(Error::InvalidFile)?;
let value = instance.value.read();
let len = value.len();
if pos >= len as u64 {
return Ok(0);
}
let pos = pos as usize;
let amount = (len - pos).min(buf.len());
buf[..amount].copy_from_slice(&value[pos..pos + amount]);
Ok(amount)
}
fn write(
&self,
_node: &NodeRef,
_instance: Option<&InstanceData>,
_pos: u64,
_buf: &[u8],
) -> Result<usize, Error> {
todo!()
// if !V::WRITEABLE {
// return Err(Error::InvalidFile);
// }
// let instance = instance.ok_or(Error::InvalidFile)?;
// let instance = instance
// .downcast_ref::<StringAttributeState>()
// .ok_or(Error::InvalidFile)?;
// let mut value = instance.value.write();
// let pos: usize = pos.try_into().map_err(|_| Error::InvalidFile)?;
// if pos > value.len() {
// return Err(Error::InvalidArgument);
// }
// if pos + buf.len() > V::LIMIT {
// return Err(Error::InvalidArgument);
// }
// let amount_copy = (value.len() - pos).min(buf.len());
// value[pos..pos + amount_copy].copy_from_slice(&buf[..amount_copy]);
// if amount_copy < buf.len() {
// value.extend_from_slice(&buf[amount_copy..]);
// }
// instance.modified.store(true, Ordering::Release);
// Ok(buf.len())
}
fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> {
Ok(())
}
}
impl<T: IntegerAttributeValue, V: IntegerAttributeOps<T>> From<V> for IntegerAttribute<T, V> {
fn from(_value: V) -> Self {
Self(PhantomData)
}
}
impl<T: IntegerAttributeValue, V: IntegerAttributeOps<T>> Attribute<V::Data>
for IntegerAttribute<T, V>
{
fn instantiate(&self, parent: &Arc<KObject<V::Data>>) -> Result<Arc<Node>, Error> {
let mode = match V::WRITEABLE {
false => FileMode::new(0o444),
true => FileMode::new(0o644),
};
Ok(Node::regular(
IntegerAttributeNode {
object: parent.clone(),
_pd: PhantomData::<V>,
},
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(mode, 0)),
None,
))
}
// TODO implement this properly
fn name(&self) -> &Filename {
unsafe { Filename::from_str_unchecked(V::NAME) }
}
}
@@ -6,6 +6,7 @@ use crate::vfs::{Filename, NodeRef};
use super::object::KObject;
mod bytes;
mod integer;
mod string;
pub trait Attribute<D>: Sync + Send {
@@ -14,4 +15,7 @@ pub trait Attribute<D>: Sync + Send {
}
pub use bytes::{BytesAttribute, BytesAttributeOps};
pub use integer::{
IntegerAttribute, IntegerAttributeFormat, IntegerAttributeOps, IntegerAttributeValue,
};
pub use string::{StringAttribute, StringAttributeOps};
+1
View File
@@ -396,6 +396,7 @@ impl SymlinkImpl for FixedPathSymlink {
buf[..self.target.len()].copy_from_slice(self.target.as_bytes());
Ok(self.target.len())
} else {
log::warn!("FixedPathSymlink: BufferTooSmall");
Err(Error::BufferTooSmall)
}
}
+5
View File
@@ -94,6 +94,11 @@ impl PidFile {
pub fn read(&self, buf: &mut [u8], _non_blocking: bool) -> Result<usize, Error> {
if buf.len() < size_of::<u32>() + size_of::<i32>() {
log::warn!(
"PidFd: BufferTooSmall (need {}, got {})",
size_of::<u32>() + size_of::<i32>(),
buf.len()
);
return Err(Error::BufferTooSmall);
}
match self {
+15 -3
View File
@@ -111,12 +111,22 @@ impl<O: TerminalOutput> Terminal<O> {
}
pub fn putc_to_output(&self, byte: u8) -> Result<(), Error> {
let config = self.config.read().output;
if byte == b'\n' && config.contains(TerminalOutputOptions::NL_TO_CRNL) {
self.output.write(b'\r').ok();
}
self.output.write(byte)
}
pub fn write_to_output(&self, buffer: &[u8]) -> Result<usize, Error> {
// TODO handle options
self.output.write_multiple(buffer)
let config = self.config.read().output;
for &byte in buffer {
if byte == b'\n' && config.contains(TerminalOutputOptions::NL_TO_CRNL) {
self.output.write(b'\r').ok();
}
self.output.write(byte)?;
}
Ok(buffer.len())
}
pub fn write_to_input(&self, mut byte: u8) {
@@ -162,7 +172,9 @@ impl<O: TerminalOutput> Terminal<O> {
if config.line.contains(TerminalLineOptions::SIGNAL) {
self.output.notify_readers();
if let Some(group_id) = *self.input.signal_pgroup.read() {
let pgrp = *self.input.signal_pgroup.read();
log::info!("Send terminal SIGINT to {pgrp:?}");
if let Some(group_id) = pgrp {
Process::signal_group(None, group_id, Signal::Interrupted);
self.input.ready_ring.notify_all();
return;
+1
View File
@@ -35,6 +35,7 @@ impl TimerFile {
pub fn read(&self, buf: &mut [u8], non_blocking: bool) -> Result<usize, Error> {
if buf.len() < size_of::<u8>() {
log::warn!("TimerFile: BufferTooSmall");
return Err(Error::BufferTooSmall);
}
if non_blocking {
+1 -1
View File
@@ -140,7 +140,7 @@ impl I686 {
TerminalInput::with_capacity(256)?,
ConsoleWrapper(textfb),
));
let keyboard_input = ygg_driver_input::setup();
let keyboard_input = ygg_driver_input::setup_keyboard();
runtime::spawn(
textfb_console
+1 -1
View File
@@ -108,7 +108,7 @@ impl InterruptHandler for PS2Controller {
inner.e0 = false;
ygg_driver_input::send_event(event);
ygg_driver_input::send_keyboard_event(event);
}
count != 0
+3 -2
View File
@@ -191,8 +191,9 @@ impl DateTime {
)
.unwrap();
chrono::NaiveDateTime::new(date, time)
.signed_duration_since(chrono::NaiveDateTime::UNIX_EPOCH)
date.and_time(time)
.and_utc()
.signed_duration_since(chrono::DateTime::UNIX_EPOCH)
.num_seconds() as u64
}
}
+12 -3
View File
@@ -168,11 +168,20 @@ pub fn kernel_main() -> ! {
CPU_INIT_FENCE.wait_all(ArchitectureImpl::cpu_count());
// Add keyboard device
if let Err(error) =
devfs::add_named_char_device(ygg_driver_input::setup(), "kbd", FileMode::new(0o660))
{
if let Err(error) = devfs::add_named_char_device(
ygg_driver_input::setup_keyboard(),
"kbd",
FileMode::new(0o660),
) {
log::error!("Couldn't add keyboard device: {error:?}");
}
if let Err(error) = devfs::add_named_char_device(
ygg_driver_input::setup_mouse(),
"mouse",
FileMode::new(0o440),
) {
log::error!("Couldn't add pointer device: {error:?}");
}
task::init().expect("Failed to initialize the scheduler");
+39
View File
@@ -1,3 +1,5 @@
use abi_serde::{impl_newtype_serde, impl_struct_serde};
/// Describes a key pressed/released on a keyboard device
// Missing docs: self-explanatory names
#[allow(missing_docs)]
@@ -47,6 +49,43 @@ pub enum KeyboardKeyEvent {
Released(KeyboardKey),
}
/// Representation for button press state
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct ButtonMask(pub u8);
impl_newtype_serde!(ButtonMask);
/// Representation for mouse event
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MouseEvent {
/// Current button state
pub buttons: ButtonMask,
/// X delta
pub dx: i32,
/// Y delta
pub dy: i32,
}
impl_struct_serde!(MouseEvent: [
buttons,
dx,
dy
]);
impl MouseEvent {
/// Returns `true` if the event has corresponding button set
pub fn button(&self, index: usize) -> bool {
if index >= 8 {
false
} else {
self.buttons.0 & (1 << index) != 0
}
}
}
impl KeyboardKey {
/// Converts [KeyboardKey] to its related [KeyboardKeyCode]
pub const fn code(self) -> KeyboardKeyCode {
+1 -1
View File
@@ -13,7 +13,7 @@ pub use crate::generated::{
OpenOptions, PollControl, RawFd, RemoveFlags, TimerOptions, UnmountOptions, UserId,
};
pub use file::{FileMetadataUpdate, FileMetadataUpdateMode, FileTimesUpdate, SeekFrom};
pub use input::{KeyboardKey, KeyboardKeyCode, KeyboardKeyEvent};
pub use input::{ButtonMask, KeyboardKey, KeyboardKeyCode, KeyboardKeyEvent, MouseEvent};
pub use terminal::{
TerminalControlCharacters, TerminalInputOptions, TerminalLineOptions, TerminalOptions,
TerminalOutputOptions, TerminalSize,
+27 -5
View File
@@ -2,11 +2,26 @@ use std::{path::PathBuf, process::Command};
use crate::IntoArgs;
#[derive(Debug)]
pub enum IntelGigabitRev {
I82574L,
I82544GC,
I82545EM,
I82540EM,
}
#[derive(Debug)]
pub enum QemuNic {
VirtioPci { mac: Option<String> },
Rtl8139 { mac: Option<String> },
IntelGigabit { mac: Option<String> },
VirtioPci {
mac: Option<String>,
},
Rtl8139 {
mac: Option<String>,
},
IntelGigabit {
mac: Option<String>,
rev: Option<IntelGigabitRev>,
},
}
#[derive(Debug, PartialEq, Eq)]
@@ -58,9 +73,16 @@ impl IntoArgs for QemuNic {
}
command.arg(val);
}
Self::IntelGigabit { mac } => {
Self::IntelGigabit { mac, rev } => {
let name = match rev {
Some(IntelGigabitRev::I82574L) => "e1000e",
Some(IntelGigabitRev::I82544GC) => "e1000-82544gc",
Some(IntelGigabitRev::I82545EM) => "e1000-82545em",
Some(IntelGigabitRev::I82540EM) => "e1000",
None => "igb",
};
command.arg("-device");
let mut val = "igb,netdev=net0".to_owned();
let mut val = format!("{name},netdev=net0");
if let Some(mac) = mac {
val.push_str(",mac=");
val.push_str(mac);
+7
View File
@@ -0,0 +1,7 @@
use abi::error::Error;
pub use abi::io::{ButtonMask, MouseEvent};
use abi_serde::wire;
pub fn parse_mouse_event(buffer: &[u8]) -> Result<MouseEvent, Error> {
wire::from_slice(buffer).map_err(Error::from)
}
+14 -1
View File
@@ -13,13 +13,26 @@ pub use abi::option::OptionSizeHint;
pub mod device;
pub mod filesystem;
pub mod input;
pub mod paths;
pub mod terminal;
pub use paths::*;
use core::mem::MaybeUninit;
use abi::{error::Error, option::OptionValue};
use abi::{error::Error, option::OptionValue, process::ProcessId};
pub fn read_pid_fd(fd: RawFd) -> Result<(ProcessId, i32), Error> {
let mut buffer = [0; size_of::<u32>() + size_of::<i32>()];
let len = unsafe { crate::sys::read(fd, &mut buffer) }?;
assert_eq!(len, buffer.len());
let mut word = [0; size_of::<u32>()];
word.copy_from_slice(&buffer[0..4]);
let pid = unsafe { ProcessId::from_raw(u32::from_ne_bytes(word)) };
word.copy_from_slice(&buffer[4..8]);
let status = i32::from_ne_bytes(word);
Ok((pid, status))
}
pub fn remove_file(at: Option<RawFd>, path: &str) -> Result<(), Error> {
unsafe { crate::sys::remove(at, path, RemoveFlags::empty()) }
+10 -10
View File
@@ -293,11 +293,11 @@ fn setup_dtv(image: &TlsImage, tls_info: &TlsInfo) -> Result<(), Error> {
// NOTE if module 1 is specified again by the dynamic loader, it will be overriden with
// what dynamic loader says
if let Some(module0_offset) = tls_info.module0_offset {
crate::debug_trace!(
Info,
"DTV[1] = {:#x}",
tls_info.base + module0_offset + DTV_OFFSET
);
// crate::debug_trace!(
// Info,
// "DTV[1] = {:#x}",
// tls_info.base + module0_offset + DTV_OFFSET
// );
dtv.set(
1,
core::ptr::without_provenance_mut(tls_info.base + module0_offset + DTV_OFFSET),
@@ -309,11 +309,11 @@ fn setup_dtv(image: &TlsImage, tls_info: &TlsInfo) -> Result<(), Error> {
}
for &(module_id, module_offset) in image.module_offsets.iter() {
assert!(module_offset < image.full_size);
crate::debug_trace!(
Info,
"DTV[{module_id}] = {:#x}",
tls_info.base + module_offset + DTV_OFFSET
);
// crate::debug_trace!(
// Info,
// "DTV[{module_id}] = {:#x}",
// tls_info.base + module_offset + DTV_OFFSET
// );
dtv.set(
module_id,
core::ptr::with_exposed_provenance_mut(tls_info.base + module_offset + DTV_OFFSET),
+495 -309
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -51,7 +51,7 @@ raqote = { version = "0.8.3", default-features = false }
# Vendored/patched dependencies
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" }
rand_core = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" }
ring = { git = "https://git.alnyan.me/yggdrasil/ring.git", branch = "alnyan/yggdrasil" }
# ring = { git = "https://git.alnyan.me/yggdrasil/ring.git", branch = "alnyan/yggdrasil" }
rsa = { git = "https://git.alnyan.me/yggdrasil/rsa.git", branch = "alnyan/yggdrasil" }
rustls = { git = "https://git.alnyan.me/yggdrasil/rustls.git", branch = "alnyan/yggdrasil", default-features = false, features = ["std", "logging", "tls12", "custom-provider"] }
curve25519-dalek = { git = "https://git.alnyan.me/yggdrasil/curve25519-dalek.git", branch = "alnyan/yggdrasil" }
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
/bin/colors-bar
+5
View File
@@ -19,6 +19,9 @@ thiserror.workspace = true
log.workspace = true
clap.workspace = true
chrono.workspace = true
toml.workspace = true
png = { version = "0.17.6" }
[target.'cfg(target_os = "yggdrasil")'.dependencies]
yggdrasil-abi.workspace = true
@@ -31,6 +34,8 @@ softbuffer = "0.4.6"
[dev-dependencies]
winit = "0.30.9"
softbuffer = "0.4.6"
libcolors = { workspace = true, features = ["client"] }
runtime.workspace = true
[lints]
workspace = true
Binary file not shown.
+137 -105
View File
@@ -1,32 +1,117 @@
#![feature(yggdrasil_os, rustc_private)]
use std::{
os::fd::AsRawFd,
process::ExitCode,
sync::{
atomic::{AtomicU32, AtomicUsize, Ordering},
Arc,
},
time::Duration,
};
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
use std::{io, os::fd::AsRawFd, process::ExitCode, time::Duration};
use chrono::{DateTime, Timelike};
use cross::io::{Poll, TimerFd};
use libcolors::{
application::{window::Window, Application},
error::Error,
application::{
surface::{Surface, SurfaceLike},
window::{EventOutcome, WindowHandler},
Application, ApplicationHandler,
},
event::WindowManagementEvent,
message::{CreateWindowInfo, WindowType},
};
use libpsf::PcScreenFont;
use runtime::rt::time::get_real_time;
static WORKSPACE_ACTIVITY: AtomicU32 = AtomicU32::new(0);
static WORKSPACE_MAX: AtomicUsize = AtomicUsize::new(1);
static CURRENT_WORKSPACE: AtomicUsize = AtomicUsize::new(0);
const WHITE: u32 = 0xFFFFFFFF;
const BLACK: u32 = 0xFF000000;
const BAR_HEIGHT: u32 = 24;
const TAG_SPACING: usize = 4;
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("Window error: {0}")]
Window(#[from] libcolors::error::Error),
#[error("{0}")]
Io(#[from] io::Error),
}
struct State {
workspace_activity: u32,
workspace_max: usize,
current_workspace: usize,
font: PcScreenFont<'static>,
}
struct AppHandler {
window_id: u32,
}
impl ApplicationHandler<WindowManagementEvent> for AppHandler {
fn on_window_management_event(
&mut self,
application: &mut Application<WindowManagementEvent>,
event: WindowManagementEvent,
) -> EventOutcome {
application.push_window_event(self.window_id, event);
EventOutcome::None
}
}
impl WindowHandler for State {
type UserEvent = WindowManagementEvent;
fn on_redraw_requested(&mut self, surface: &mut Surface) {
surface.fill(0);
let (now_seconds, now_nanos) = cross::time::get_real_time().expect("read real time");
let width = surface.width();
let mut x = 0;
for index in 0..self.workspace_max.max(self.current_workspace + 1) {
let exists = self.workspace_activity & (1 << index) != 0;
let active = self.current_workspace == index;
if !exists && !active {
continue;
}
let text = format!("{}", index + 1);
let (fg, bg) = if active {
(BLACK, WHITE)
} else {
(WHITE, BLACK)
};
draw_string(surface, &self.font, 2 + x, 2, &text, width as usize, fg, bg);
x += (self.font.width() as usize * text.len() + TAG_SPACING) as u32;
}
if let Some(now) = DateTime::from_timestamp(now_seconds as _, now_nanos as _) {
let (pm, hour) = now.hour12();
let ampm = if pm { "PM" } else { "AM" };
let text = format!("{}:{:02} {ampm}", hour, now.minute());
let tw = string_width(&self.font, &text);
draw_string(
surface,
&self.font,
width - tw - 4,
2,
&text,
width as usize,
WHITE,
BLACK,
);
}
}
fn on_user_event(&mut self, event: Self::UserEvent) -> EventOutcome {
match event {
WindowManagementEvent::WorkspaceChanged { new, .. } => {
self.current_workspace = new;
EventOutcome::Redraw
}
WindowManagementEvent::WorkspaceActivity { mask, max } => {
self.workspace_activity = mask;
self.workspace_max = max.max(1);
EventOutcome::Redraw
}
_ => EventOutcome::None,
}
}
}
#[allow(clippy::too_many_arguments)]
fn draw_string(
dt: &mut [u32],
font: &PcScreenFont,
@@ -52,100 +137,48 @@ fn string_width(font: &PcScreenFont, text: &str) -> u32 {
(font.width() as usize * text.len()) as u32
}
fn redraw(dt: &mut [u32], font: &PcScreenFont, width: u32, _height: u32) {
const TAG_SPACING: usize = 4;
dt.fill(0);
let now = get_real_time().unwrap();
let current = CURRENT_WORKSPACE.load(Ordering::Relaxed);
let activity = WORKSPACE_ACTIVITY.load(Ordering::Relaxed);
let max = WORKSPACE_MAX.load(Ordering::Relaxed);
let mut x = 0;
for index in 0..max {
let exists = activity & (1 << index) != 0;
let active = current == index;
if !exists && !active {
continue;
}
let text = format!("{}", index + 1);
let (fg, bg) = if active {
(BLACK, WHITE)
} else {
(WHITE, BLACK)
};
draw_string(dt, font, 2 + x, 2, &text, width as usize, fg, bg);
x += (font.width() as usize * text.len() + TAG_SPACING) as u32;
}
if let Some(now) = DateTime::from_timestamp(now.seconds() as _, now.subsec_nanos() as _) {
let (pm, hour) = now.hour12();
let ampm = if pm { "PM" } else { "AM" };
let text = format!("{}:{:02} {ampm}", hour, now.minute());
let tw = string_width(font, &text);
draw_string(
dt,
font,
width - tw - 4,
2,
&text,
width as usize,
WHITE,
BLACK,
);
}
}
fn run() -> Result<ExitCode, Error> {
let font = Arc::new(PcScreenFont::default());
let info = CreateWindowInfo {
ty: WindowType::Reservation(BAR_HEIGHT),
};
let font = PcScreenFont::default();
let handler = State {
workspace_activity: 0,
workspace_max: 1,
current_workspace: 0,
font,
};
let mut application = Application::single_windowed_with_info(info, handler)?;
let application_fd = application.poll_fd();
let window_id = application.main_window_id().expect("main window id");
let mut poll = Poll::new()?;
let mut timer = TimerFd::new()?;
let mut app = Application::new()?;
app.subscribe_to_window_management_events()?;
let mut window = Window::new_with_info(
&app,
CreateWindowInfo {
ty: WindowType::Reservation(24),
},
)?;
window.set_on_redraw_requested(move |dt, width, height| {
redraw(dt, &font, width, height);
});
app.set_window_management_event_handler(|event| match event {
WindowManagementEvent::WorkspaceChanged { new, .. } => {
CURRENT_WORKSPACE.store(new, Ordering::Relaxed);
true
}
WindowManagementEvent::WorkspaceActivity { mask, max } => {
WORKSPACE_MAX.store(max, Ordering::Relaxed);
WORKSPACE_ACTIVITY.store(mask, Ordering::Relaxed);
true
}
_ => false,
});
poll.add(&application_fd)?;
poll.add(&timer)?;
app.add_window(window);
timer.start(Duration::from_millis(100))?;
// TODO signals, events in Application?
let app_fd = app.connection().lock().unwrap().as_raw_fd();
let timer_fd = timer.as_raw_fd();
poll.add(&app_fd)?;
poll.add(&timer_fd)?;
application.subscribe_to_window_management_events()?;
timer.start(Duration::from_secs(1))?;
let mut app_handler = AppHandler { window_id };
while app.is_running() {
let fd = poll.wait(None)?.unwrap();
if fd == app_fd {
app.poll_events()?;
} else if fd == timer_fd {
app.redraw()?;
timer.start(Duration::from_secs(1))?;
} else {
log::warn!("Unexpected poll event: {fd:?}");
while application.is_running() {
match poll.wait(None)? {
Some(fd) if fd == application_fd => {
if application.poll_events(&mut app_handler)? {
break;
}
}
Some(fd) if fd == timer.as_raw_fd() => {
application.redraw()?;
timer.start(Duration::from_millis(100))?;
}
None | Some(_) => unreachable!(),
}
}
@@ -154,11 +187,10 @@ fn run() -> Result<ExitCode, Error> {
fn main() -> ExitCode {
logsink::setup_logging(true);
log::info!("colors-bar starting");
match run() {
Ok(code) => code,
Err(error) => {
log::error!("colors-bar: {error}");
log::error!("Error: {error}");
ExitCode::FAILURE
}
}
+59
View File
@@ -0,0 +1,59 @@
use std::{
fs,
path::{Path, PathBuf},
sync::LazyLock,
};
use serde::Deserialize;
use crate::Error;
const CONFIG_PATH: &str = "/etc/colors.toml";
pub static CONFIG: LazyLock<Config> = LazyLock::new(|| match Config::load_or_default() {
Ok(config) => config,
Err(error) => {
log::error!("Couldn't load config {CONFIG_PATH}: {error}");
log::error!("Falling back to default settings");
Config::default()
}
});
#[derive(Debug, Deserialize)]
pub struct Config {
pub startup_script: Option<PathBuf>,
pub terminal: String,
pub cursor_size: u32,
}
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
impl Default for Config {
fn default() -> Self {
Self {
startup_script: Some("/etc/colors/startup.sh".into()),
terminal: "/bin/term".into(),
cursor_size: 24,
}
}
}
#[cfg(any(rust_analyzer, unix))]
impl Default for Config {
fn default() -> Self {
Self {
startup_script: Some("./util/colors.sh".into()),
terminal: "./util/term.sh".into(),
cursor_size: 24,
}
}
}
impl Config {
pub fn load_or_default() -> Result<Self, Error> {
if !Path::new(CONFIG_PATH).exists() {
return Ok(Self::default());
}
let data = fs::read_to_string(CONFIG_PATH)?;
let config = toml::from_str(&data)?;
Ok(config)
}
}
+171 -45
View File
@@ -1,37 +1,49 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
#![feature(map_many_mut, iter_chain)]
#![feature(if_let_guard, iter_chain, map_many_mut, let_chains, maybe_uninit_slice)]
use std::{
collections::{BTreeMap, HashMap, HashSet},
io,
marker::PhantomData,
os::fd::{AsRawFd, RawFd},
process::{Command, ExitCode},
};
use cross::mem::SharedMemory;
use input::InputState;
use libcolors::{
application::surface::{Surface, SurfaceLike},
event::{
EventData, KeyEvent, KeyModifiers, KeyboardKeyEvent, WindowEvent, WindowInfo,
WindowManagementEvent,
},
geometry::{Point, Rectangle},
input::Key,
message::{ClientMessage, CreateWindowInfo},
};
use sys::{Backend, DisplaySurface, FromClient, Point, ServerSender, WindowServer};
use sys::{Backend, DisplaySurface, FromClient, ServerSender, WindowServer};
use uipc::PeerAddress;
use window::Window;
use wm::{display::Display, layout::Direction};
use crate::{
config::CONFIG,
res::Resources,
sys::{DisplaySurfaceImpl, RawMouseEvent},
};
pub mod config;
pub mod input;
pub mod res;
pub mod sys;
pub mod window;
pub mod wm;
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
pub enum Error {
#[error("{0}")]
Backend(#[from] sys::Error),
Io(#[from] io::Error),
TomlDeserialize(#[from] toml::de::Error),
}
pub struct Server<'a> {
@@ -41,15 +53,24 @@ pub struct Server<'a> {
wm_clients: HashSet<PeerAddress>,
display: Display<u32>,
windows: BTreeMap<u32, Window<'a>>,
windows: BTreeMap<u32, Window>,
last_window_id: u32,
back_buffer: Surface<Vec<u32>>,
cursor: Point<u32>,
resources: Resources,
cursor_size: Option<u32>,
background: u32,
_pd: PhantomData<&'a ()>,
}
impl<'a> Server<'a> {
fn new() -> Result<Self, Error> {
let mut resources = Resources::load();
let cursor_size = resources.select_cursor(CONFIG.cursor_size);
Ok(Self {
input_state: InputState::default(),
@@ -61,15 +82,20 @@ impl<'a> Server<'a> {
windows: BTreeMap::new(),
display: Display::new(800, 600),
back_buffer: Surface::new_vec(800, 600),
last_window_id: 1,
cursor: Point { x: 0, y: 0 },
cursor_size,
resources,
_pd: PhantomData,
})
}
fn create_window(
&mut self,
surface: &mut DisplaySurface,
surface: &mut DisplaySurfaceImpl,
tx: &mut ServerSender,
peer: &PeerAddress,
info: CreateWindowInfo,
@@ -81,31 +107,15 @@ impl<'a> Server<'a> {
.create_window(wid, &info.ty)
.expect("TODO: handle create window failed");
let frame = self.display.window_frame(wid).unwrap();
let mapping_size = surface.width() * surface.height() * 4;
// let mapping_size = self.display.width() * self.display.height() * 4;
let surface_shm = SharedMemory::new(mapping_size).unwrap();
let fd = surface_shm.as_raw_fd();
let mut surface_mapping = surface_shm.map().unwrap();
let surface_data = unsafe {
std::slice::from_raw_parts_mut(
surface_mapping.as_mut_ptr() as *mut u32,
(frame.w * frame.h) as usize,
)
};
let window = Window {
wid,
peer: peer.clone(),
surface_mapping,
surface_data,
};
let mapping_size = (surface.width() * surface.height() * 4) as usize;
let window = Window::new(wid, peer.clone(), frame.w, frame.h, mapping_size);
let fd = window.surface.as_raw_fd();
self.windows.insert(wid, window);
let info = WindowInfo {
window_id: wid,
surface_stride: surface.width() * 4, // self.display.width() * 4,
surface_stride: surface.width() as usize * 4, // self.display.width() * 4,
surface_mapping_size: mapping_size,
width: frame.w,
height: frame.h,
@@ -167,7 +177,7 @@ impl<'a> Server<'a> {
fn move_window(
&mut self,
surface: &mut DisplaySurface,
surface: &mut DisplaySurfaceImpl,
tx: &mut ServerSender,
direction: Direction,
) {
@@ -288,21 +298,108 @@ impl<'a> Server<'a> {
},
);
}
fn resize(&mut self, width: u32, height: u32) {
self.display.resize(width, height);
self.back_buffer.resize(width, height).ok();
}
fn repaint_rectangle(&mut self, rect: Rectangle<u32>) {
for (wid, layout) in self.display.all_windows() {
let Some(window_rect) = layout.get() else {
continue;
};
let Some(dirty_rect) = window_rect.intersect(&rect) else {
continue;
};
let Some(window) = self.windows.get(&wid) else {
continue;
};
// Dirty rect within the window
let source_rect = Rectangle {
x: dirty_rect.x - window_rect.x,
y: dirty_rect.y - window_rect.y,
w: dirty_rect.w,
h: dirty_rect.h,
};
self.back_buffer
.copy_rect_from(&window.surface, source_rect, dirty_rect.origin());
}
}
fn unpaint_cursor(&mut self, x: u32, y: u32, size: u32) -> Rectangle<u32> {
let cursor_rect = Rectangle {
x,
y,
w: size,
h: size,
};
// Fill back buffer
self.back_buffer.fill_rect(cursor_rect, self.background);
// Iterate over "damaged" windows and repaint their stuff
self.repaint_rectangle(cursor_rect);
cursor_rect
}
fn paint_cursor(&mut self, x: u32, y: u32, size: u32) -> Rectangle<u32> {
let cursor_rect = Rectangle {
x,
y,
w: size,
h: size,
};
// Fill back buffer
self.back_buffer.fill_rect(cursor_rect, self.background);
// Iterate over "damaged" windows and repaint their stuff
self.repaint_rectangle(cursor_rect);
// Overlay the cursor
self.resources
.draw_cursor(&mut self.back_buffer, cursor_rect.origin());
// self.back_buffer.fill_rect(cursor_rect, 0xFFFFFFFF);
cursor_rect
}
fn repaint_with_damage(&mut self, mut surface: DisplaySurfaceImpl, damage: &[Rectangle<u32>]) {
for rect in damage {
surface.copy_rect_from(&self.back_buffer, *rect, rect.origin());
}
surface.present_with_damage(damage);
}
}
impl WindowServer for Server<'_> {
fn handle_initial(&mut self, mut surface: DisplaySurface) {
self.display
.resize(surface.width() as u32, surface.height() as u32);
fn handle_initial(&mut self, mut surface: DisplaySurfaceImpl) {
self.resize(surface.width() as u32, surface.height() as u32);
surface.fill(self.background);
surface.present();
Command::new("/bin/colors-bar").spawn().ok();
let Some(startup_script) = CONFIG.startup_script.as_ref() else {
return;
};
if let Err(error) = Command::new(startup_script).spawn() {
log::error!("Startup script error: {error}");
}
}
fn handle_display_resize(&mut self, tx: &mut ServerSender, mut surface: DisplaySurfaceImpl) {
log::info!("Display resize: {}x{}", surface.width(), surface.height());
self.resize(surface.width() as u32, surface.height() as u32);
surface.fill(self.background);
self.redraw_all(tx);
surface.present();
}
fn handle_exit(&mut self, tx: &mut ServerSender) {
log::info!("Exiting window server, kicking clients");
for (_, peer) in self.client_map.drain() {
tx.send_event(EventData::ServerExit, &peer);
}
}
fn handle_client_message(
&mut self,
mut surface: DisplaySurface,
mut surface: DisplaySurfaceImpl,
tx: &mut ServerSender,
message: FromClient,
) {
@@ -363,19 +460,23 @@ impl WindowServer for Server<'_> {
return;
}
let dst = Point(frame.x as _, frame.y as _);
let src = Point(x as _, y as _);
let dst = Point {
x: frame.x as _,
y: frame.y as _,
};
let src_rect = Rectangle { x, y, w, h };
log::trace!("Blit {src:?} {w}x{h} -> {dst:?}");
log::trace!("Blit {src_rect:?} {w}x{h} -> {dst:?}");
surface.blit_buffer(
window.surface_data,
dst,
src,
w as _,
h as _,
frame.w as usize,
);
if let Some(damage) =
self.back_buffer
.copy_rect_from(&window.surface, src_rect, dst)
{
if let Some(cursor_size) = self.cursor_size {
self.paint_cursor(self.cursor.x, self.cursor.y, cursor_size);
}
self.repaint_with_damage(surface, &[damage]);
}
}
}
ClientMessage::DestroyWindow(wid) => {
@@ -386,6 +487,7 @@ impl WindowServer for Server<'_> {
let (removed, workspace) = self.display.remove_window(wid);
if removed {
// TODO flicker
surface.fill(self.background);
self.flush_dirty_frames(tx);
self.redraw_all(tx);
@@ -419,9 +521,33 @@ impl WindowServer for Server<'_> {
}
}
fn handle_mouse_event(
&mut self,
surface: DisplaySurfaceImpl,
_tx: &mut ServerSender,
event: RawMouseEvent,
) {
// TODO buttons
let ox = self.cursor.x;
let oy = self.cursor.y;
let cx = (self.cursor.x as i32 + event.dx).clamp(0, surface.width() as i32) as u32;
let cy = (self.cursor.y as i32 + event.dy).clamp(0, surface.height() as i32) as u32;
self.cursor.x = cx;
self.cursor.y = cy;
if let Some(cursor_size) = self.cursor_size {
let r0 = self.unpaint_cursor(ox, oy, cursor_size);
let r1 = self.paint_cursor(cx, cy, cursor_size);
self.repaint_with_damage(surface, &[r0, r1]);
}
}
fn handle_keyboard_event(
&mut self,
mut surface: DisplaySurface,
mut surface: DisplaySurfaceImpl,
tx: &mut ServerSender,
event: KeyboardKeyEvent,
) {
@@ -435,7 +561,7 @@ impl WindowServer for Server<'_> {
match (input.modifiers, input.key) {
(KeyModifiers::ALT, Key::Enter) => {
// TODO do something with spawned child
Command::new("/bin/term").spawn().ok();
Command::new(&CONFIG.terminal).spawn().ok();
return;
}
_ => (),
+126
View File
@@ -0,0 +1,126 @@
use std::{fs::File, io, path::PathBuf};
use libcolors::{application::surface::SurfaceLike, geometry::Point};
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
const CURSORS_PATH: &str = "/etc/colors";
#[cfg(any(rust_analyzer, unix))]
const CURSORS_PATH: &str = "../../etc/colors";
const CURSOR_SIZES: &[u32] = &[16, 24, 32, 48, 64, 96];
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
enum ResourceError {
Io(#[from] io::Error),
Png(#[from] png::DecodingError),
InvalidResource(String),
}
struct CursorImage {
data: Vec<u8>,
size: u32,
}
pub struct Resources {
cursors: Vec<CursorImage>,
selected_cursor: Option<usize>,
}
impl Resources {
fn try_load_cursor(size: u32) -> Result<CursorImage, ResourceError> {
let path = PathBuf::from(CURSORS_PATH).join(format!("cursor{size}.png"));
let decoder = png::Decoder::new(File::open(path)?);
let mut reader = decoder.read_info()?;
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf)?;
if info.width != info.height {
return Err(ResourceError::InvalidResource(format!(
"cursor{size}.png is not a rectangle"
)));
}
if info.width != size {
return Err(ResourceError::InvalidResource(format!(
"cursor{size}.png is not {size}x{size}"
)));
}
Ok(CursorImage { data: buf, size })
}
pub fn load() -> Self {
let mut cursors = vec![];
for &size in CURSOR_SIZES {
match Self::try_load_cursor(size) {
Ok(cursor) => cursors.push(cursor),
Err(error) => {
log::warn!("Couldn't load cursor size {size}: {error}");
}
}
}
Self {
cursors,
selected_cursor: None,
}
}
pub fn select_cursor(&mut self, preferred: u32) -> Option<u32> {
let (index, cursor) = self
.cursors
.iter()
.enumerate()
.min_by_key(|(_, x)| x.size.abs_diff(preferred))?;
self.selected_cursor = Some(index);
Some(cursor.size)
}
pub fn draw_cursor<S: SurfaceLike>(&self, surface: &mut S, position: Point<u32>) {
let Some(cursor) = self.selected_cursor.and_then(|i| self.cursors.get(i)) else {
return;
};
for iy in 0..cursor.size {
let y = position.y + iy;
if y >= surface.height() {
break;
}
for ix in 0..cursor.size {
let x = position.x + ix;
if x >= surface.width() {
break;
}
let i = (iy * cursor.size + ix) as usize * 4;
let s_color = surface.get_pixel(x, y);
let d_color = u32::from_le_bytes([
cursor.data[i],
cursor.data[i + 1],
cursor.data[i + 2],
cursor.data[i + 3],
]);
let b_color = blend(s_color, d_color, (d_color >> 24) as u8);
surface.set_pixel(x, y, b_color);
}
}
}
}
fn blend(a: u32, b: u32, alpha: u8) -> u32 {
let ar = (a >> 16) & 0xFF;
let ag = (a >> 8) & 0xFF;
let ab = a & 0xFF;
let br = (b >> 16) & 0xFF;
let bg = (b >> 8) & 0xFF;
let bb = b & 0xFF;
let ialpha = 255 - alpha;
let xr = (ar * ialpha as u32 + br * alpha as u32) / 255;
let xg = (ag * ialpha as u32 + bg * alpha as u32) / 255;
let xb = (ab * ialpha as u32 + bb * alpha as u32) / 255;
0xFF000000 | (xr << 16) | (xg << 8) | xb
}
+39 -8
View File
@@ -1,7 +1,9 @@
use std::os::fd::{AsRawFd, OwnedFd};
use libcolors::{
application::surface::SurfaceLike,
event::{EventData, KeyboardKeyEvent},
geometry::Rectangle,
message::{ClientMessage, ServerMessage},
};
use uipc::{PeerAddress, Sender};
@@ -12,9 +14,9 @@ pub mod unix;
pub mod yggdrasil;
#[cfg(any(rust_analyzer, unix))]
pub use unix::{Backend, DisplaySurface, Error};
pub use unix::{Backend, DisplaySurfaceImpl, Error};
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
pub use yggdrasil::{Backend, DisplaySurface, Error};
pub use yggdrasil::{Backend, DisplaySurfaceImpl, Error};
#[derive(Debug)]
pub struct FromClient {
@@ -23,27 +25,56 @@ pub struct FromClient {
pub file: Option<OwnedFd>,
}
#[derive(Debug)]
pub struct RawMouseEvent {
pub button1: bool,
pub button2: bool,
pub button3: bool,
pub dx: i32,
pub dy: i32,
}
pub struct ServerSender {
sender: Sender<ServerMessage>,
}
#[derive(Debug)]
pub struct Point<T>(pub T, pub T);
pub trait WindowServer {
fn handle_initial(&mut self, surface: DisplaySurface);
fn handle_initial(&mut self, surface: DisplaySurfaceImpl);
fn handle_display_resize(&mut self, tx: &mut ServerSender, surface: DisplaySurfaceImpl);
fn handle_exit(&mut self, tx: &mut ServerSender);
fn handle_client_message(
&mut self,
surface: DisplaySurface,
surface: DisplaySurfaceImpl,
tx: &mut ServerSender,
message: FromClient,
);
fn handle_keyboard_event(
&mut self,
surface: DisplaySurface,
surface: DisplaySurfaceImpl,
tx: &mut ServerSender,
event: KeyboardKeyEvent,
);
fn handle_mouse_event(
&mut self,
surface: DisplaySurfaceImpl,
tx: &mut ServerSender,
event: RawMouseEvent,
);
}
pub trait DisplaySurface: Sized + SurfaceLike {
fn present_with_damage(self, damage: &[Rectangle<u32>]);
fn present(self) {
let width = self.width();
let height = self.height();
self.present_with_damage(&[Rectangle {
x: 0,
y: 0,
w: width,
h: height,
}]);
}
}
impl ServerSender {
+247 -129
View File
@@ -1,5 +1,6 @@
use std::{
cmp, fs, io,
fs, io,
mem::MaybeUninit,
num::NonZero,
ops::{Deref, DerefMut},
rc::Rc,
@@ -7,22 +8,23 @@ use std::{
use clap::Parser;
use libcolors::{
event::KeyboardKeyEvent,
input::Key,
message::{ClientMessage, ServerMessage},
application::surface::SurfaceLike, event::KeyboardKeyEvent, geometry::Rectangle, input::Key,
message::ClientMessage,
};
use softbuffer::{Buffer, Rect, SoftBufferError};
use uipc::{Receiver, Sender};
use softbuffer::{Buffer, Rect as SbRect, SoftBufferError};
use winit::{
application::ApplicationHandler,
dpi::LogicalSize,
error::{EventLoopError, OsError},
event::{ElementState, Event, WindowEvent},
event_loop::{EventLoop, EventLoopProxy},
event::{DeviceEvent, DeviceId, ElementState, WindowEvent},
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowAttributes},
window::{CursorGrabMode, Window, WindowId},
};
use super::{FromClient, Point, ServerSender, WindowServer};
use crate::sys::{DisplaySurface, RawMouseEvent};
use super::{FromClient, ServerSender, WindowServer};
#[derive(Debug, thiserror::Error)]
pub enum Error {
@@ -37,13 +39,23 @@ pub enum Error {
}
pub struct Backend<S: WindowServer> {
event_loop: EventLoop<FromClient>,
window: Rc<Window>,
tx: Sender<ServerMessage>,
tx: ServerSender,
rx: Option<uipc::Receiver<ClientMessage>>,
window: Option<Rc<Window>>,
context: Option<softbuffer::Context<Rc<Window>>>,
surface: Option<softbuffer::Surface<Rc<Window>, Rc<Window>>>,
initial: bool,
server: S,
args: Args,
input_grabbed: bool,
lalt: bool,
lctrl: bool,
key_g: bool,
}
pub struct DisplaySurface<'a> {
pub struct DisplaySurfaceImpl<'a> {
inner: Buffer<'a, Rc<Window>, Rc<Window>>,
width: usize,
height: usize,
@@ -57,57 +69,35 @@ struct Args {
height: u32,
}
impl DisplaySurface<'_> {
pub fn width(&self) -> usize {
self.width
impl SurfaceLike for DisplaySurfaceImpl<'_> {
fn width(&self) -> u32 {
self.width as u32
}
pub fn height(&self) -> usize {
self.height
}
pub fn blit_buffer(
mut self,
source: &[u32],
dst: Point<usize>,
src: Point<usize>,
w: usize,
h: usize,
src_stride: usize,
) {
let src_w = (self.width - src.0).min(w);
let dst_w = (self.width - dst.0).min(w);
let src_h = (self.height - src.1).min(h);
let dst_h = (self.height - dst.1).min(h);
let w = cmp::min(src_w, dst_w);
let h = cmp::min(src_h, dst_h);
for y in 0..h {
let dst_offset = (y + src.1 + dst.1) * self.width + dst.0 + src.0;
let src_offset = (y + src.1) * src_stride + src.0;
let src_chunk = &source[src_offset..src_offset + w];
let dst_chunk = &mut self[dst_offset..dst_offset + w];
dst_chunk.copy_from_slice(src_chunk);
}
self.inner
.present_with_damage(&[Rect {
x: dst.0 as _,
y: dst.1 as _,
width: NonZero::new(w as _).unwrap(),
height: NonZero::new(h as _).unwrap(),
}])
.unwrap();
}
pub fn present(self) {
self.inner.present().unwrap();
fn height(&self) -> u32 {
self.height as u32
}
}
impl Deref for DisplaySurface<'_> {
impl DisplaySurface for DisplaySurfaceImpl<'_> {
fn present_with_damage(self, damage: &[Rectangle<u32>]) {
let mut rects = [MaybeUninit::uninit(); 8];
for (i, src) in damage.iter().enumerate() {
rects[i].write(SbRect {
x: src.x,
y: src.y,
width: src.w.try_into().unwrap(),
height: src.h.try_into().unwrap(),
});
}
let rects = unsafe { MaybeUninit::slice_assume_init_ref(&rects[..damage.len()]) };
self.inner
.present_with_damage(rects)
.expect("present error");
}
}
impl Deref for DisplaySurfaceImpl<'_> {
type Target = [u32];
fn deref(&self) -> &Self::Target {
@@ -115,39 +105,192 @@ impl Deref for DisplaySurface<'_> {
}
}
impl DerefMut for DisplaySurface<'_> {
impl DerefMut for DisplaySurfaceImpl<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.deref_mut()
}
}
impl<S: WindowServer> ApplicationHandler<FromClient> for Backend<S> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = Window::default_attributes()
.with_title("colors")
.with_resizable(false)
.with_inner_size(LogicalSize::new(self.args.width, self.args.height));
let window = event_loop
.create_window(window_attributes)
.expect("create the window");
let window = Rc::new(window);
let context = softbuffer::Context::new(window.clone()).expect("create draw context");
let surface =
softbuffer::Surface::new(&context, window.clone()).expect("create draw surface");
self.window = Some(window);
self.context = Some(context);
self.surface = Some(surface);
self.grab_input(true);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
let (Some(window), Some(surface)) = (self.window.as_ref(), self.surface.as_mut()) else {
return;
};
match event {
WindowEvent::CloseRequested => {
self.server.handle_exit(&mut self.tx);
event_loop.exit();
}
WindowEvent::RedrawRequested if self.initial => {
let size = window.inner_size();
surface
.resize(
NonZero::new(size.width).unwrap(),
NonZero::new(size.height).unwrap(),
)
.unwrap();
self.server.handle_initial(DisplaySurfaceImpl {
inner: surface.buffer_mut().unwrap(),
width: size.width as _,
height: size.height as _,
});
self.initial = false;
}
WindowEvent::Resized(size) => {
let width = NonZero::new(size.width).unwrap();
let height = NonZero::new(size.height).unwrap();
surface.resize(width, height).unwrap();
let display_surface = DisplaySurfaceImpl {
inner: surface.buffer_mut().unwrap(),
width: size.width as usize,
height: size.height as usize,
};
self.server
.handle_display_resize(&mut self.tx, display_surface);
}
WindowEvent::MouseInput { state, button, .. } => {
let _ = button;
if state == ElementState::Pressed {
self.grab_input(true);
}
}
WindowEvent::KeyboardInput { event, .. } => {
let pressed = event.state == ElementState::Pressed;
match event.physical_key {
PhysicalKey::Code(KeyCode::AltLeft) => self.lalt = pressed,
PhysicalKey::Code(KeyCode::ControlLeft) => self.lctrl = pressed,
PhysicalKey::Code(KeyCode::KeyG) => self.key_g = pressed,
_ => (),
}
if self.lalt && self.lctrl && self.key_g {
log::info!("Ctrl+Alt+G pressed, releasing grab");
self.grab_input(false);
return;
}
if let Some(event) = convert_key_event(event) {
let size = window.inner_size();
let display_surface = DisplaySurfaceImpl {
inner: surface.buffer_mut().unwrap(),
width: size.width as usize,
height: size.height as usize,
};
self.server
.handle_keyboard_event(display_surface, &mut self.tx, event);
}
}
_ => (),
}
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
_device_id: DeviceId,
event: DeviceEvent,
) {
let (Some(window), Some(surface)) = (self.window.as_ref(), self.surface.as_mut()) else {
return;
};
let size = window.inner_size();
match event {
DeviceEvent::MouseMotion { delta } if self.input_grabbed => {
let (dx, dy) = delta;
let display_surface = DisplaySurfaceImpl {
inner: surface.buffer_mut().unwrap(),
width: size.width as usize,
height: size.height as usize,
};
let event = RawMouseEvent {
button1: false,
button2: false,
button3: false,
dx: dx as i32,
dy: dy as i32,
};
self.server
.handle_mouse_event(display_surface, &mut self.tx, event);
}
_ => (),
}
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: FromClient) {
let (Some(window), Some(surface)) = (self.window.as_ref(), self.surface.as_mut()) else {
return;
};
let size = window.inner_size();
let display_surface = DisplaySurfaceImpl {
inner: surface.buffer_mut().unwrap(),
width: size.width as usize,
height: size.height as usize,
};
self.server
.handle_client_message(display_surface, &mut self.tx, event);
}
}
impl<S: WindowServer> Backend<S> {
pub fn new(server: S) -> Result<Self, Error> {
let args = Args::parse();
let event_loop = EventLoop::with_user_event().build()?;
let window = event_loop.create_window(
WindowAttributes::new()
.with_title("colors")
.with_resizable(false)
.with_inner_size(LogicalSize::new(args.width, args.height)),
)?;
fs::remove_file(libcolors::CHANNEL_NAME).ok();
let event_proxy = event_loop.create_proxy();
let listener = uipc::Channel::bind(libcolors::CHANNEL_NAME).unwrap();
let (tx, rx) = listener.split();
let tx = ServerSender { sender: tx };
std::thread::spawn(move || {
Self::io_worker(rx, event_proxy);
});
Ok(Self {
event_loop,
window: Rc::new(window),
tx,
args,
server,
tx,
rx: Some(rx),
context: None,
window: None,
surface: None,
initial: true,
input_grabbed: false,
lalt: false,
lctrl: false,
key_g: false,
})
}
fn io_worker(mut rx: Receiver<ClientMessage>, event_proxy: EventLoopProxy<FromClient>) {
fn io_worker(mut rx: uipc::Receiver<ClientMessage>, event_proxy: EventLoopProxy<FromClient>) {
loop {
let (message, file, peer) = rx.receive_with_file_from().unwrap();
let message = FromClient {
@@ -159,62 +302,35 @@ impl<S: WindowServer> Backend<S> {
}
}
pub fn run(mut self) -> Result<(), Error> {
let mut tx = ServerSender { sender: self.tx };
let context = softbuffer::Context::new(self.window.clone())?;
let mut surface = softbuffer::Surface::new(&context, self.window.clone())?;
fn grab_input(&mut self, grab: bool) {
if self.input_grabbed == grab {
return;
}
if let Some(window) = self.window.as_ref() {
if grab {
window.set_cursor_grab(CursorGrabMode::Locked).ok();
window.set_cursor_visible(false);
} else {
window.set_cursor_grab(CursorGrabMode::None).ok();
window.set_cursor_visible(true);
}
self.input_grabbed = grab;
}
}
let size = self.window.inner_size();
surface
.resize(
NonZero::new(size.width).unwrap(),
NonZero::new(size.height).unwrap(),
)
.unwrap();
self.server.handle_initial(DisplaySurface {
inner: surface.buffer_mut().unwrap(),
width: size.width as _,
height: size.height as _,
pub fn run(mut self) -> Result<(), Error> {
let event_loop = EventLoop::<FromClient>::with_user_event()
.build()
.expect("create event loop");
let event_proxy = event_loop.create_proxy();
event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait);
let rx = self.rx.take().expect("ipc rx channel");
std::thread::spawn(move || {
Self::io_worker(rx, event_proxy);
});
self.event_loop.run(|event, el| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => {
el.exit();
}
WindowEvent::Resized(size) => {
let width = NonZero::new(size.width).unwrap();
let height = NonZero::new(size.height).unwrap();
surface.resize(width, height).unwrap();
surface.buffer_mut().unwrap().present().unwrap();
}
WindowEvent::KeyboardInput { event, .. } => {
if let Some(event) = convert_key_event(event) {
let size = self.window.inner_size();
let display_surface = DisplaySurface {
inner: surface.buffer_mut().unwrap(),
width: size.width as usize,
height: size.height as usize,
};
self.server
.handle_keyboard_event(display_surface, &mut tx, event);
}
}
_ => (),
},
Event::UserEvent(event) => {
let size = self.window.inner_size();
let display_surface = DisplaySurface {
inner: surface.buffer_mut().unwrap(),
width: size.width as usize,
height: size.height as usize,
};
self.server
.handle_client_message(display_surface, &mut tx, event);
}
_ => (),
})?;
Ok(())
event_loop.run_app(&mut self).map_err(Error::from)
}
}
@@ -228,6 +344,8 @@ fn convert_key_event(raw: winit::event::KeyEvent) -> Option<KeyboardKeyEvent> {
KeyCode::ShiftRight => Key::RShift,
KeyCode::ControlLeft => Key::LControl,
KeyCode::ControlRight => Key::RControl,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
KeyCode::Backspace => Key::Backspace,
KeyCode::Space => Key::Char(b' '),
KeyCode::Enter => Key::Enter,
+106 -81
View File
@@ -1,5 +1,4 @@
use std::{
cmp,
fs::{File, OpenOptions},
io::{self, Read},
ops::{Deref, DerefMut},
@@ -9,14 +8,21 @@ use std::{
use clap::Parser;
use cross::{io::Poll, mem::FileMapping};
use libcolors::{event::KeyboardKeyEvent, input::Key, message::ClientMessage};
use runtime::rt::io::device;
use libcolors::{
application::surface::SurfaceLike, event::KeyboardKeyEvent, geometry::Rectangle, input::Key,
message::ClientMessage,
};
use runtime::{abi as yggdrasil_abi, rt as yggdrasil_rt};
use uipc::{Channel, Receiver};
use yggdrasil_abi::io::KeyboardKey;
use yggdrasil_abi::io::{
KeyboardKey as SysKeyboardKey, KeyboardKeyEvent as SysKeyboardKeyEvent,
MouseEvent as SysMouseEvent,
};
use yggdrasil_rt::io::device;
use crate::sys::FromClient;
use crate::sys::{DisplaySurface, FromClient, RawMouseEvent};
use super::{Point, ServerSender, WindowServer};
use super::{ServerSender, WindowServer};
#[derive(Debug, thiserror::Error)]
pub enum Error {
@@ -32,7 +38,8 @@ pub struct Backend<'a, S: WindowServer> {
tx: ServerSender,
display: Display<'a>,
input: KeyboardInput,
keyboard_input: KeyboardInput,
mouse_input: MouseInput,
server: S,
}
@@ -51,55 +58,33 @@ struct Display<'a> {
}
struct KeyboardInput(File);
struct MouseInput(File);
pub struct DisplaySurface<'a, 'd> {
pub struct DisplaySurfaceImpl<'a, 'd> {
display: &'a mut Display<'d>,
}
impl DisplaySurface<'_, '_> {
pub fn width(&self) -> usize {
self.display.width
}
pub fn height(&self) -> usize {
self.display.height
}
pub fn blit_buffer(
mut self,
source: &[u32],
dst: Point<usize>,
src: Point<usize>,
w: usize,
h: usize,
src_stride: usize,
) {
let src_w = (self.display.width - src.0).min(w);
let dst_w = (self.display.width - dst.0).min(w);
let src_h = (self.display.height - src.1).min(h);
let dst_h = (self.display.height - dst.1).min(h);
let w = cmp::min(src_w, dst_w);
let h = cmp::min(src_h, dst_h);
for y in 0..h {
let dst_offset = (y + src.1 + dst.1) * self.display.width + dst.0 + src.0;
let src_offset = (y + src.1) * src_stride + src.0;
let src_chunk = &source[src_offset..src_offset + w];
let dst_chunk = &mut self[dst_offset..dst_offset + w];
dst_chunk.copy_from_slice(src_chunk);
}
impl DisplaySurface for DisplaySurfaceImpl<'_, '_> {
fn present_with_damage(self, _damage: &[Rectangle<u32>]) {
self.present();
}
pub fn present(self) {
fn present(self) {
self.display.flush();
}
}
impl Deref for DisplaySurface<'_, '_> {
impl SurfaceLike for DisplaySurfaceImpl<'_, '_> {
fn width(&self) -> u32 {
self.display.width as u32
}
fn height(&self) -> u32 {
self.display.height as u32
}
}
impl Deref for DisplaySurfaceImpl<'_, '_> {
type Target = [u32];
fn deref(&self) -> &Self::Target {
@@ -107,7 +92,7 @@ impl Deref for DisplaySurface<'_, '_> {
}
}
impl DerefMut for DisplaySurface<'_, '_> {
impl DerefMut for DisplaySurfaceImpl<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.display.data
}
@@ -124,11 +109,13 @@ impl<S: WindowServer> Backend<'_, S> {
let channel = Channel::bind(libcolors::CHANNEL_NAME)?;
let (tx, rx) = channel.split();
let tx = ServerSender { sender: tx };
let input = KeyboardInput::open()?;
let keyboard_input = KeyboardInput::open()?;
let mouse_input = MouseInput::open()?;
let mut poll = Poll::new()?;
poll.add(&rx)?;
poll.add(&input)?;
poll.add(&keyboard_input)?;
poll.add(&mouse_input)?;
let display = Display::open(args.framebuffer)?;
Ok(Self {
@@ -137,14 +124,15 @@ impl<S: WindowServer> Backend<'_, S> {
rx,
display,
input,
keyboard_input,
mouse_input,
server,
})
}
pub fn run(mut self) -> Result<(), Error> {
self.server.handle_initial(DisplaySurface {
self.server.handle_initial(DisplaySurfaceImpl {
display: &mut self.display,
});
@@ -158,20 +146,36 @@ impl<S: WindowServer> Backend<'_, S> {
file,
peer,
};
let surface = DisplaySurface {
let surface = DisplaySurfaceImpl {
display: &mut self.display,
};
self.server
.handle_client_message(surface, &mut self.tx, event);
} else if fd == self.input.as_raw_fd() {
let event = self.input.read_event()?;
} else if fd == self.keyboard_input.as_raw_fd() {
let event = self.keyboard_input.read_event()?;
if let Some(event) = convert_key_event(event) {
let surface = DisplaySurface {
let surface = DisplaySurfaceImpl {
display: &mut self.display,
};
self.server
.handle_keyboard_event(surface, &mut self.tx, event);
}
} else if fd == self.mouse_input.as_raw_fd() {
let event = self.mouse_input.read_event()?;
let button1 = event.button(0);
let button2 = event.button(1);
let button3 = event.button(2);
let event = RawMouseEvent {
button1,
button2,
button3,
dx: event.dx,
dy: event.dy,
};
let surface = DisplaySurfaceImpl {
display: &mut self.display,
};
self.server.handle_mouse_event(surface, &mut self.tx, event);
}
}
}
@@ -224,12 +228,12 @@ impl KeyboardInput {
Ok(Self(file))
}
pub fn read_event(&mut self) -> Result<yggdrasil_abi::io::KeyboardKeyEvent, Error> {
pub fn read_event(&mut self) -> Result<SysKeyboardKeyEvent, Error> {
let mut buf = [0; 4];
let len = self.0.read(&mut buf)?;
if len == 4 {
Ok(yggdrasil_abi::io::KeyboardKeyEvent::from_bytes(buf))
Ok(SysKeyboardKeyEvent::from_bytes(buf))
} else {
todo!()
}
@@ -242,34 +246,55 @@ impl AsRawFd for KeyboardInput {
}
}
fn convert_key_event(raw: yggdrasil_abi::io::KeyboardKeyEvent) -> Option<KeyboardKeyEvent> {
impl MouseInput {
pub fn open() -> Result<Self, Error> {
let file = File::open("/dev/mouse")?;
Ok(Self(file))
}
pub fn read_event(&mut self) -> Result<SysMouseEvent, Error> {
let mut buf = [0; 9];
let len = self.0.read(&mut buf)?;
let event =
yggdrasil_rt::io::input::parse_mouse_event(&buf[..len]).map_err(io::Error::from)?;
Ok(event)
}
}
impl AsRawFd for MouseInput {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
fn convert_key_event(raw: SysKeyboardKeyEvent) -> Option<KeyboardKeyEvent> {
let (key, state) = raw.split();
let key = match key {
KeyboardKey::Char(ch) => Key::Char(ch),
KeyboardKey::Backspace => Key::Backspace,
KeyboardKey::Enter => Key::Enter,
KeyboardKey::Home => Key::Home,
KeyboardKey::End => Key::End,
KeyboardKey::PageUp => Key::PageUp,
KeyboardKey::PageDown => Key::PageDown,
KeyboardKey::Escape => Key::Escape,
KeyboardKey::Up => Key::Up,
KeyboardKey::Down => Key::Down,
KeyboardKey::Left => Key::Left,
KeyboardKey::Right => Key::Right,
KeyboardKey::LAlt => Key::LAlt,
KeyboardKey::RAlt => Key::RAlt,
KeyboardKey::LShift => Key::LShift,
KeyboardKey::RShift => Key::RShift,
KeyboardKey::LControl => Key::LControl,
KeyboardKey::RControl => Key::RControl,
KeyboardKey::Insert => return None,
KeyboardKey::Delete => return None,
KeyboardKey::Unknown => return None,
KeyboardKey::CapsLock => return None,
KeyboardKey::Tab => return None,
KeyboardKey::F(_) => return None,
SysKeyboardKey::Char(ch) => Key::Char(ch),
SysKeyboardKey::Backspace => Key::Backspace,
SysKeyboardKey::Enter => Key::Enter,
SysKeyboardKey::Home => Key::Home,
SysKeyboardKey::End => Key::End,
SysKeyboardKey::PageUp => Key::PageUp,
SysKeyboardKey::PageDown => Key::PageDown,
SysKeyboardKey::Escape => Key::Escape,
SysKeyboardKey::Up => Key::Up,
SysKeyboardKey::Down => Key::Down,
SysKeyboardKey::Left => Key::Left,
SysKeyboardKey::Right => Key::Right,
SysKeyboardKey::LAlt => Key::LAlt,
SysKeyboardKey::RAlt => Key::RAlt,
SysKeyboardKey::LShift => Key::LShift,
SysKeyboardKey::RShift => Key::RShift,
SysKeyboardKey::LControl => Key::LControl,
SysKeyboardKey::RControl => Key::RControl,
SysKeyboardKey::Insert => return None,
SysKeyboardKey::Delete => return None,
SysKeyboardKey::Unknown => return None,
SysKeyboardKey::CapsLock => return None,
SysKeyboardKey::Tab => return None,
SysKeyboardKey::F(_) => return None,
};
Some(KeyboardKeyEvent { key, state })
+13 -14
View File
@@ -1,23 +1,22 @@
use cross::mem::FileMapping;
use cross::mem::SharedMemory;
use libcolors::application::surface::Surface;
use uipc::PeerAddress;
pub struct Window<'d> {
pub struct Window {
pub wid: u32,
pub peer: PeerAddress,
pub surface_mapping: FileMapping,
pub surface_data: &'d [u32],
pub surface: Surface,
}
impl Window<'_> {
pub fn resize(&mut self, w: u32, h: u32) {
let new_surface_data = unsafe {
std::slice::from_raw_parts_mut(
self.surface_mapping.as_mut_ptr() as *mut u32,
(w * h) as usize,
)
};
impl Window {
pub fn new(wid: u32, peer: PeerAddress, w: u32, h: u32, mapping_size: usize) -> Self {
let shm = SharedMemory::new(mapping_size).expect("cannot allocate shm");
let surface = Surface::from_shared_memory(shm.into(), mapping_size, w, h)
.expect("surface creation failed");
Self { wid, peer, surface }
}
self.surface_data = new_surface_data;
pub fn resize(&mut self, w: u32, h: u32) {
self.surface.resize(w, h).expect("surface resize error");
}
}
+6 -6
View File
@@ -1,9 +1,9 @@
use std::{fmt, hash::Hash, iter};
use libcolors::message::WindowType;
use libcolors::{geometry::Rectangle, message::WindowType};
use super::{
layout::{Direction, NodeLayout, Rect},
layout::{Direction, NodeLayout},
workspace::Workspace,
};
@@ -84,13 +84,13 @@ impl<T: Eq + Hash + Copy> Display<T> {
iter::chain(reservation_windows, workspace_windows)
}
pub fn dirty_windows(&self) -> impl Iterator<Item = (T, Rect)> + '_ {
pub fn dirty_windows(&self) -> impl Iterator<Item = (T, Rectangle<u32>)> + '_ {
let reservation_windows = self.dirty_reservation_windows();
let workspace_windows = self.current_workspace().dirty_windows();
iter::chain(reservation_windows, workspace_windows)
}
pub fn window_frame(&self, wid: T) -> Option<Rect> {
pub fn window_frame(&self, wid: T) -> Option<Rectangle<u32>> {
if let Some(reservation) = self.lookup_reservation(wid) {
return reservation.layout.get();
}
@@ -173,7 +173,7 @@ impl<T: Eq + Hash + Copy> Display<T> {
self.update_layout(true);
}
fn dirty_reservation_windows(&self) -> impl Iterator<Item = (T, Rect)> + '_ {
fn dirty_reservation_windows(&self) -> impl Iterator<Item = (T, Rectangle<u32>)> + '_ {
self.reservation_windows().filter_map(|(wid, layout)| {
if !layout.clear_dirty() {
return None;
@@ -197,7 +197,7 @@ impl<T: Eq + Hash + Copy> Display<T> {
fn update_reservation_layout(&mut self) -> bool {
let mut res_y = 0;
for reservation in self.reservations_top.iter() {
reservation.layout.set(Rect {
reservation.layout.set(Rectangle {
x: 0,
y: res_y,
w: self.width,
+5 -11
View File
@@ -1,5 +1,7 @@
use std::cell::Cell;
use libcolors::geometry::Rectangle;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Up,
@@ -8,14 +10,6 @@ pub enum Direction {
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Orientation {
Horizontal,
@@ -23,7 +17,7 @@ pub enum Orientation {
}
pub struct NodeLayout {
pub rect: Cell<(Option<Rect>, bool)>,
pub rect: Cell<(Option<Rectangle<u32>>, bool)>,
}
impl NodeLayout {
@@ -33,13 +27,13 @@ impl NodeLayout {
}
}
pub fn set(&self, new: Rect) {
pub fn set(&self, new: Rectangle<u32>) {
let (old, _) = self.rect.get();
let dirty = old.map_or(true, |old| old != new);
self.rect.set((Some(new), dirty));
}
pub fn get(&self) -> Option<Rect> {
pub fn get(&self) -> Option<Rectangle<u32>> {
let (value, _) = self.rect.get();
value
}
@@ -1,6 +1,8 @@
use std::{collections::HashMap, hash::Hash};
use super::layout::{Direction, NodeLayout, Orientation, Rect};
use libcolors::geometry::Rectangle;
use super::layout::{Direction, NodeLayout, Orientation};
pub type NodeId = u32;
@@ -108,7 +110,7 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
})
}
pub fn dirty_windows(&self) -> impl Iterator<Item = (T, Rect)> + '_ {
pub fn dirty_windows(&self) -> impl Iterator<Item = (T, Rectangle<u32>)> + '_ {
self.all_windows().filter_map(|(wid, layout)| {
if !layout.clear_dirty() {
return None;
@@ -421,7 +423,7 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
// self.reservations_top.iter().find(|r| r.wid == wid)
// }
pub(super) fn window_frame(&self, wid: T) -> Option<Rect> {
pub(super) fn window_frame(&self, wid: T) -> Option<Rectangle<u32>> {
self.window(wid)?.layout.get()
}
@@ -439,7 +441,7 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
pub(super) fn update_layout(&mut self) {
self.update_layout_for(
self.root,
Rect {
Rectangle {
x: self.margin,
y: self.margin + self.reservation_top,
w: self.width - self.margin * 2,
@@ -502,7 +504,7 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
})
}
fn update_layout_for(&self, node_id: NodeId, rect: Rect) {
fn update_layout_for(&self, node_id: NodeId, rect: Rectangle<u32>) {
let Some(node) = self.nodes.get(&node_id) else {
log::warn!("update_layout_for: no node {node_id}");
return;
@@ -524,7 +526,7 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
for (i, &child_nid) in container.children.iter().enumerate() {
let i = i as u32;
let child_rect = Rect {
let child_rect = Rectangle {
x: rect.x,
y: rect.y + i * ystep,
w: rect.w,
@@ -541,7 +543,7 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
for (i, &child_nid) in container.children.iter().enumerate() {
let i = i as u32;
let child_rect = Rect {
let child_rect = Rectangle {
x: rect.x + i * xstep,
y: rect.y,
w: wstep,
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
RUST_LOG=info cargo run --release --bin colors-bar
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
cd ../term
RUST_LOG=info cargo run --release
+1
View File
@@ -16,6 +16,7 @@ thiserror.workspace = true
clap.workspace = true
serde.workspace = true
toml.workspace = true
bitflags.workspace = true
rusttype = "0.9.3"
@@ -15,16 +15,6 @@ pub struct Color {
pub b: u8,
}
#[derive(Clone, Copy, Debug)]
pub struct CellAttributes {
pub fg: Color,
pub bg: Color,
pub bold: bool,
pub italic: bool,
pub underlined: bool,
pub strikethrough: bool,
}
impl Color {
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
+1 -1
View File
@@ -6,7 +6,7 @@ use std::{
use serde::Deserialize;
use crate::attr::Color;
use crate::attribute::Color;
#[derive(Deserialize)]
pub struct FontConfig {
+7 -5
View File
@@ -1,9 +1,11 @@
#[derive(thiserror::Error, Debug)]
use std::io;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Application error: {0:?}")]
ApplicationError(#[from] libcolors::error::Error),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
Io(#[from] io::Error),
#[error("Font load error")]
FontLoadError,
#[error("Window error: {0}")]
Window(#[from] libcolors::error::Error),
}
-28
View File
@@ -1,28 +0,0 @@
use libcolors::{event::KeyModifiers, input::Key};
pub const KEY_UP: &[u8] = b"\x1B[A";
pub const KEY_DOWN: &[u8] = b"\x1B[B";
pub const KEY_RIGHT: &[u8] = b"\x1B[C";
pub const KEY_LEFT: &[u8] = b"\x1B[D";
pub const KEY_ENTER: &[u8] = b"\x0D";
pub const KEY_ESCAPE: &[u8] = b"\x1B";
pub const KEY_HOME: &[u8] = b"\x1B[H";
pub const KEY_END: &[u8] = b"\x1B[F";
pub const KEY_PAGEUP: &[u8] = b"\x1B[5~";
pub const KEY_PAGEDOWN: &[u8] = b"\x1B[6~";
pub fn map(modifier: KeyModifiers, key: Key) -> Option<&'static [u8]> {
match (modifier, key) {
(KeyModifiers::NONE, Key::Enter) => Some(KEY_ENTER),
(KeyModifiers::NONE, Key::Escape) => Some(KEY_ESCAPE),
(KeyModifiers::NONE, Key::Up) => Some(KEY_UP),
(KeyModifiers::NONE, Key::Down) => Some(KEY_DOWN),
(KeyModifiers::NONE, Key::Right) => Some(KEY_RIGHT),
(KeyModifiers::NONE, Key::Left) => Some(KEY_LEFT),
(KeyModifiers::NONE, Key::Home) => Some(KEY_HOME),
(KeyModifiers::NONE, Key::End) => Some(KEY_END),
(KeyModifiers::NONE, Key::PageUp) => Some(KEY_PAGEUP),
(KeyModifiers::NONE, Key::PageDown) => Some(KEY_PAGEDOWN),
_ => None,
}
}
+12 -486
View File
@@ -1,506 +1,32 @@
#![feature(if_let_guard)]
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
use std::{
io::{Read, Write},
mem,
os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
process::{Child, Command, ExitCode, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
Arc, LazyLock, Mutex,
},
};
use std::{process::ExitCode, sync::LazyLock};
use clap::Parser;
use config::Config;
use cross::{
io::{Poll, PtyMaster, TerminalOptions, TerminalOptionsImpl, TerminalSize},
process::CommandSpawnExt,
};
use error::Error;
use font::{Font, Fonts};
use libcolors::{
application::{
window::{EventOutcome, Window},
Application,
},
event::KeyModifiers,
input::Key,
};
use libterm::escape::CursorStyle;
use state::{Cursor, GridCell, State};
pub mod attr;
use crate::{config::Config, terminal::Terminal};
pub mod attribute;
pub mod config;
pub mod error;
pub mod escape;
pub mod font;
pub mod state;
pub mod terminal;
struct DrawState {
width: usize,
force_redraw: bool,
focus_changed: bool,
focused: bool,
old_cursor: Cursor,
old_cursor_style: CursorStyle,
fonts: Fonts,
}
pub struct Terminal<'a> {
poll: Poll,
conn_fd: RawFd,
application: Application<'a>,
state: Arc<Mutex<State>>,
pty_master: Arc<Mutex<PtyMaster>>,
pty_master_fd: RawFd,
#[allow(unused)]
shell: Child,
}
#[derive(Parser, Debug)]
#[derive(Debug, Parser)]
pub struct Args {
command: Vec<String>,
}
impl DrawState {
pub fn new(fonts: Fonts, width: usize) -> Self {
Self {
width,
fonts,
force_redraw: true,
focus_changed: false,
focused: true,
old_cursor_style: CursorStyle::Block(true),
old_cursor: Cursor { row: 0, col: 0 },
}
}
#[allow(clippy::too_many_arguments)]
fn draw_character(
dt: &mut [u32],
cx: usize,
cy: usize,
fw: usize,
fh: usize,
stride: usize,
cell: &GridCell,
fonts: &Fonts,
invert: bool,
) {
fn blend(x: u8, y: u8, f: f32) -> u8 {
let v = f * (x as f32) + (1.0 - f) * (y as f32);
v as u8
}
let mut bg = cell.attrs.bg;
let mut fg = cell.attrs.fg;
if invert {
mem::swap(&mut bg, &mut fg);
}
// Fill cell
for y in 0..fh {
let off = (cy + y) * stride + cx;
dt[off..off + fw].fill(bg.to_u32());
}
if cell.char == '\0' {
return;
}
let font = match (cell.attrs.bold, cell.attrs.italic) {
(true, true) => &fonts.bold_italic,
(false, true) => &fonts.italic,
(true, false) => &fonts.bold,
(false, false) => &fonts.regular,
};
font.map_glyph(cell.char, |x, y, v| {
let v = v.min(1.0);
let r = blend(fg.r, bg.r, v);
let g = blend(fg.g, bg.g, v);
let b = blend(fg.b, bg.b, v);
let color = (b as u32) | ((g as u32) << 8) | ((r as u32) << 16) | 0xFF000000;
dt[(cy + y) * stride + cx + x] = color;
});
if cell.attrs.underlined {
let s = (cy + fh - 1) * stride + cx;
let d = &mut dt[s..s + fw];
d.fill(fg.to_u32());
}
if cell.attrs.strikethrough {
let s = (cy + fh / 2) * stride + cx;
let d = &mut dt[s..s + fw];
d.fill(fg.to_u32());
}
}
pub fn draw(&mut self, dt: &mut [u32], state: &mut State) {
let default_fg = state.default_attributes.fg.to_u32();
let default_bg = state.default_attributes.bg.to_u32();
let font_layout = self.fonts.regular.layout();
let fw = font_layout.width;
let fh = font_layout.height;
let cursor_dirty = (self.old_cursor != state.cursor)
|| (self.old_cursor_style != state.cursor_style)
|| state.cursor_dirty;
state.cursor_dirty = false;
if self.force_redraw {
dt.fill(default_bg);
}
if cursor_dirty {
state.buffer.set_row_dirty(self.old_cursor.row);
}
let scroll = state.adjust_scroll();
let cursor_visible = scroll == 0 && state.cursor_visible;
state.buffer.visible_rows_mut(scroll, |i, row| {
let cy = i * fh;
for (j, cell) in row.cells().enumerate() {
let cx = j * fw;
Self::draw_character(dt, cx, cy, fw, fh, self.width, cell, &self.fonts, false);
}
});
if cursor_visible && cursor_dirty {
let cx = state.cursor.col * fw;
let cy = state.cursor.row * fh;
// Character under cursor
let cell = state.buffer.cell(state.cursor.row, state.cursor.col);
let fg = cell.attrs.fg.to_u32();
match state.cursor_style {
CursorStyle::Bar(_) if self.focused => {
for y in 0..fh {
let off = (cy + y) * self.width + cx;
dt[off] = fg;
}
}
CursorStyle::Underline(_) if self.focused => {
let off = (cy + fh - 1) * self.width + cx;
dt[off..off + fw].fill(fg);
}
// Block, focused
CursorStyle::Block(_) if self.focused => {
Self::draw_character(dt, cx, cy, fw, fh, self.width, cell, &self.fonts, true);
}
// Block, not focused (default not focused)
_ => {
Self::draw_character(dt, cx, cy, fw, fh, self.width, cell, &self.fonts, false);
// Draw outline
for y in 0..fh {
let off = (cy + y) * self.width + cx;
if y == 0 || y == fh - 1 {
dt[off..off + fw].fill(fg);
} else {
dt[off] = default_fg;
dt[off + fw - 1] = fg;
}
}
}
}
self.old_cursor = state.cursor;
}
self.force_redraw = false;
self.focus_changed = false;
}
}
impl Terminal<'_> {
pub fn new() -> Result<Self, Error> {
let fonts = Fonts::from_config()?;
let mut app = Application::new()?;
let mut window = Window::new(&app)?;
let mut poll = Poll::new()?;
let width = window.width() as usize;
let height = window.height() as usize;
let font_layout = fonts.regular.layout();
let rows = height / font_layout.height;
let columns = width / font_layout.width;
let termios = TerminalOptionsImpl::normal();
// let termios = TerminalOptions::default();
let (pty_master, pty_slave) = cross::io::open_pty(
&termios,
TerminalSize {
rows: rows as _,
columns: columns as _,
x_pixels: 0,
y_pixels: 0,
},
)?; // create_pty(Some(termios), TerminalSize { rows, columns })?;
let pty_master_fd = pty_master.as_raw_fd();
// TODO I hate this
let pty_master = Arc::new(Mutex::new(pty_master));
let state = Arc::new(Mutex::new(State::new(columns, rows)));
let draw_state = Arc::new(Mutex::new(DrawState::new(fonts, width)));
let state_c = state.clone();
let draw_state_c = draw_state.clone();
let pty_master_c = pty_master.clone();
window.set_on_resized(move |width, height| {
let mut s = state_c.lock().unwrap();
let mut ds = draw_state_c.lock().unwrap();
let mut pty_master = pty_master_c.lock().unwrap();
let width = width as usize;
let height = height as usize;
let font_layout = ds.fonts.regular.layout();
let rows = height / font_layout.height;
let columns = width / font_layout.width;
pty_master
.resize_terminal(TerminalSize {
rows: rows as _,
columns: columns as _,
x_pixels: width as _,
y_pixels: height as _,
})
.expect("terminal resize");
s.resize(width / font_layout.width, height / font_layout.height);
ds.width = width;
ds.force_redraw = true;
EventOutcome::Redraw
});
let state_c = state.clone();
let pty_master_c = pty_master.clone();
window.set_on_key_input(move |ev| {
let mut s = state_c.lock().unwrap();
let mut pty_master = pty_master_c.lock().unwrap();
let mut need_redraw = false;
// TODO error reporting from handlers
if let Some(mut input) = ev.input {
if input == '\n' {
input = '\x0D';
}
pty_master.write_all(&[input as u8]).unwrap();
need_redraw = s.scroll_end();
} else {
match (ev.modifiers, ev.key) {
// terminal control
(KeyModifiers::CTRL, Key::Char(b'l')) => {
if !s.alternate {
s.clear();
}
pty_master.write_all(&[0x0C]).unwrap();
need_redraw = !s.alternate;
}
(KeyModifiers::SHIFT, Key::PageUp) => {
need_redraw = s.scroll_up();
}
(KeyModifiers::SHIFT, Key::PageDown) => {
need_redraw = s.scroll_down();
}
(KeyModifiers::SHIFT, Key::Home) => {
need_redraw = s.scroll_home();
}
(KeyModifiers::SHIFT, Key::End) => {
need_redraw = s.scroll_end();
}
// termios chars
(KeyModifiers::NONE, Key::Backspace) => {
pty_master.write_all(&[termios.erase_char()]).ok();
need_redraw = s.scroll_end();
}
(KeyModifiers::CTRL, Key::Char(b'c')) => {
pty_master.write_all(&[termios.interrupt_char()]).unwrap();
need_redraw = s.scroll_end();
}
(KeyModifiers::CTRL, Key::Char(b'd')) => {
pty_master.write_all(&[termios.eof_char()]).unwrap();
need_redraw = s.scroll_end();
}
// other sequences
(m, k) if let Some(data) = escape::map(m, k) => {
pty_master.write_all(data).unwrap();
need_redraw = s.scroll_end();
}
_ => (),
}
}
if need_redraw {
EventOutcome::Redraw
} else {
EventOutcome::None
}
});
let draw_state_c = draw_state.clone();
window.set_on_focus_changed(move |focused| {
let mut ds = draw_state_c.lock().unwrap();
ds.focused = focused;
ds.focus_changed = true;
EventOutcome::Redraw
});
let state_c = state.clone();
window.set_on_redraw_requested(move |dt: &mut [u32], _, _| {
let mut s = state_c.lock().unwrap();
let mut ds = draw_state.lock().unwrap();
ds.draw(dt, &mut s);
});
app.add_window(window);
let conn_fd = app.connection().lock().unwrap().as_raw_fd();
poll.add(&conn_fd)?;
poll.add(&pty_master_fd)?;
let pty_slave_stdin = pty_slave.into_raw_fd();
let pty_slave_stdout = cross::io::clone_fd(pty_slave_stdin)?;
let pty_slave_stderr = cross::io::clone_fd(pty_slave_stdin)?;
log::debug!("stdin = {pty_slave_stdin:?}, stdout = {pty_slave_stdout:?}, stderr = {pty_slave_stderr:?}");
let term_var = CONFIG
.shell
.env
.get("TERM")
.map(String::as_str)
.unwrap_or("xterm-256color");
let mut command = if let Some((shell, args)) = CONFIG.shell.command.split_first() {
let mut command = Command::new(shell);
command.args(args);
command
} else {
let mut command = Command::new("/bin/sh");
command.arg("-l");
command
};
command.env("TERM", term_var);
for (key, value) in CONFIG.shell.env.iter() {
if key == "TERM" {
continue;
}
command.env(key, value);
}
let shell = unsafe {
command
.stdin(Stdio::from_raw_fd(pty_slave_stdin))
.stdout(Stdio::from_raw_fd(pty_slave_stdout))
.stderr(Stdio::from_raw_fd(pty_slave_stderr))
.create_session()?
.spawn()?
};
Ok(Self {
poll,
conn_fd,
state,
application: app,
pty_master,
pty_master_fd,
shell,
})
}
fn handle_shell(&mut self) -> Result<bool, Error> {
let mut buf = [0; 8192];
let mut pty = self.pty_master.lock().unwrap();
let len = pty.read(&mut buf)?;
if len == 0 {
ABORT.store(true, Ordering::Release);
return Ok(false);
}
let mut needs_redraw = false;
let mut s = self.state.lock().unwrap();
for &ch in &buf[..len] {
needs_redraw |= s.handle_shell_output(&mut pty, ch);
}
Ok(needs_redraw)
}
fn run_inner(mut self) -> Result<ExitCode, Error> {
while !ABORT.load(Ordering::Acquire) {
match self.poll.wait(None)? {
Some(fd) if fd == self.conn_fd => {
self.application.poll_events()?;
}
Some(fd) if fd == self.pty_master_fd => {
let needs_redraw = self.handle_shell()?;
if needs_redraw {
self.application.redraw()?;
}
}
Some(_) => (),
None => (),
}
}
Ok(ExitCode::SUCCESS)
}
pub fn run(self) -> ExitCode {
match self.run_inner() {
Ok(code) => code,
Err(error) => {
eprintln!("Error: {error}");
ExitCode::FAILURE
}
}
}
}
static ABORT: AtomicBool = AtomicBool::new(false);
static CONFIG: LazyLock<Config> = LazyLock::new(|| {
pub static CONFIG: LazyLock<Config> = LazyLock::new(|| {
let args = Args::parse();
Config::load_or_default("/etc/term.conf", args)
});
fn run() -> Result<ExitCode, Error> {
LazyLock::force(&CONFIG);
let term = Terminal::new()?;
Ok(term.run())
}
fn main() -> ExitCode {
logsink::setup_logging(false);
match run() {
Ok(code) => code,
Err(error) => {
log::error!("{error}");
ExitCode::FAILURE
}
}
logsink::setup_logging(true);
LazyLock::force(&CONFIG);
let term = Terminal::new().expect("create terminal");
log::info!("Run terminal");
term.run()
}
-869
View File
@@ -1,869 +0,0 @@
use std::{collections::VecDeque, io::Write};
use cross::io::PtyMaster;
use libterm::escape::{
CharacterAttribute, ControlSequence, CursorStyle, EraseInDisplay, EraseInLine, EscapeParser,
TerminalInput,
};
use crate::{
attr::{CellAttributes, Color},
CONFIG,
};
#[derive(Clone, Copy, Debug)]
pub struct GridCell {
pub char: char,
pub attrs: CellAttributes,
}
#[derive(Clone)]
pub struct GridRow {
cols: Vec<GridCell>,
dirty: bool,
}
pub struct Buffer {
scrollback: VecDeque<GridRow>,
rows: Vec<GridRow>,
width: usize,
height: usize,
scrollback_limit: usize,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Cursor {
pub row: usize,
pub col: usize,
}
#[derive(Default)]
struct Utf8Decoder {
buffer: [u8; 4],
len: usize,
}
pub struct State {
pub buffer: Buffer,
utf8_decode: Utf8Decoder,
esc_parser: EscapeParser,
// esc_state: EscapeState,
// esc_args: Vec<u32>,
scroll: usize,
pub cursor: Cursor,
pub cursor_visible: bool,
pub alternate: bool,
#[allow(unused)]
saved_cursor: Option<Cursor>,
pub cursor_style: CursorStyle,
pub default_attributes: CellAttributes,
pub attributes: CellAttributes,
pub fg_index: Option<u32>,
pub cursor_dirty: bool,
}
impl Utf8Decoder {
pub fn push(&mut self, byte: u8) -> Option<char> {
self.buffer[self.len] = byte;
self.len += 1;
if let Ok(str) = std::str::from_utf8(&self.buffer[..self.len]) {
let ch = str.chars().next().unwrap();
self.len = 0;
Some(ch)
} else {
if self.len == 4 {
// Got 4 bytes and could not decode a single character
self.len = 0;
Some('\u{25A1}')
} else {
None
}
}
}
}
impl GridCell {
pub fn new(char: char, attrs: CellAttributes) -> Self {
Self { char, attrs }
}
pub fn empty(fg: Color, bg: Color) -> Self {
Self {
char: '\0',
attrs: CellAttributes {
fg,
bg,
bold: false,
italic: false,
underlined: false,
strikethrough: false,
},
}
}
}
impl GridRow {
pub fn new(width: usize, fg: Color, bg: Color) -> Self {
Self {
cols: vec![GridCell::empty(fg, bg); width],
dirty: true,
}
}
pub fn set_cell(&mut self, col: usize, char: char, attrs: CellAttributes) {
self.cols[col] = GridCell::new(char, attrs);
self.dirty = true;
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn clear_dirty(&mut self) {
self.dirty = false;
}
pub fn cells(&self) -> impl Iterator<Item = &GridCell> {
self.cols.iter()
}
fn clear(&mut self, fg: Color, bg: Color) {
self.cols.fill(GridCell::empty(fg, bg));
self.dirty = true;
}
fn erase_to_right(&mut self, start: usize, fg: Color, bg: Color) {
self.cols[start..].fill(GridCell::empty(fg, bg));
self.dirty = true;
}
fn resize(&mut self, width: usize, fg: Color, bg: Color) {
self.cols.resize(width, GridCell::empty(fg, bg));
self.dirty = true;
}
}
impl Buffer {
pub fn new(width: usize, height: usize, fg: Color, bg: Color) -> Self {
Self {
rows: vec![GridRow::new(width, fg, bg); height],
scrollback: VecDeque::new(),
scrollback_limit: 1024,
width,
height,
}
}
pub fn clear(&mut self, fg: Color, bg: Color) {
self.rows.fill(GridRow::new(self.width, fg, bg));
}
pub fn iter_rows_mut<F: FnMut(usize, &mut GridRow)>(&mut self, scroll: usize, mut handler: F) {
let scroll = scroll.min(self.scrollback.len());
let non_scroll = self.height.saturating_sub(scroll);
let scroll_end = self.height - non_scroll;
for y in 0..scroll_end {
let i = scroll - 1 - y;
let row = &mut self.scrollback[i];
handler(y, row);
}
for i in 0..non_scroll {
let y = scroll + i;
let row = &mut self.rows[i];
handler(y, row);
}
}
pub fn visible_rows_mut<F: FnMut(usize, &mut GridRow)>(
&mut self,
scroll: usize,
mut handler: F,
) {
self.iter_rows_mut(scroll, |y, row| {
if row.dirty {
row.dirty = false;
handler(y, row);
}
});
}
pub fn resize(&mut self, width: usize, height: usize, fg: Color, bg: Color) {
self.rows.resize(height, GridRow::new(width, fg, bg));
for row in self.rows.iter_mut() {
row.resize(width, fg, bg);
}
self.width = width;
self.height = height;
}
pub fn set_cell(&mut self, cur: Cursor, cell: GridCell) {
self.rows[cur.row].cols[cur.col] = cell;
self.rows[cur.row].dirty = true;
}
pub fn cell(&self, row: usize, col: usize) -> &GridCell {
&self.rows[row].cols[col]
}
pub fn scroll_once(&mut self, fg: Color, bg: Color) {
self.scrollback.push_front(self.rows[0].clone());
if self.scrollback.len() >= self.scrollback_limit {
self.scrollback.pop_back();
}
for i in 1..self.height {
self.rows[i - 1] = self.rows[i].clone();
self.rows[i - 1].dirty = true;
}
self.rows[self.height - 1] = GridRow::new(self.width, fg, bg);
}
pub fn erase_row(&mut self, row: usize, fg: Color, bg: Color) {
self.rows[row].clear(fg, bg);
}
pub fn insert_row(&mut self, after: usize, fg: Color, bg: Color) {
if after >= self.height {
return;
}
for i in (after + 2..self.height).rev() {
self.rows.swap(i - 1, i);
}
self.rows[after] = GridRow::new(self.width, fg, bg);
}
pub fn remove_row(&mut self, at: usize, fg: Color, bg: Color) {
if at >= self.height {
return;
}
for i in at..self.height - 1 {
self.rows.swap(i, i + 1);
}
self.rows[self.height - 1].clear(fg, bg);
}
pub fn set_row_dirty(&mut self, row: usize) {
if row >= self.rows.len() {
return;
}
self.rows[row].dirty = true;
}
pub fn invalidate_rows(&mut self, scroll: usize) {
self.iter_rows_mut(scroll, |_, row| row.dirty = true);
}
}
impl State {
pub fn new(width: usize, height: usize) -> Self {
let config = &*CONFIG;
let default_attributes = CellAttributes {
fg: config.colors.dim.white,
bg: config.colors.dim.black,
bold: false,
italic: false,
underlined: false,
strikethrough: false,
};
Self {
buffer: Buffer::new(width, height, default_attributes.fg, default_attributes.bg),
utf8_decode: Utf8Decoder::default(),
esc_parser: EscapeParser::new(),
cursor: Cursor { row: 0, col: 0 },
cursor_visible: true,
alternate: false,
scroll: 0,
saved_cursor: None,
default_attributes,
attributes: default_attributes,
fg_index: Some(7),
cursor_style: CursorStyle::Block(true),
cursor_dirty: false,
}
}
fn update_fg_color(&mut self) {
if let Some(fg_index) = self.fg_index {
self.attributes.fg = Color::from_escape(&*CONFIG, self.attributes.bold, fg_index)
.unwrap_or(self.default_attributes.fg);
}
}
pub fn clear(&mut self) {
self.buffer
.clear(self.default_attributes.fg, self.attributes.bg);
self.cursor_dirty = true;
}
pub fn resize(&mut self, width: usize, height: usize) {
self.buffer.resize(
width,
height,
self.default_attributes.fg,
self.default_attributes.bg,
);
self.scroll = 0;
self.cursor_dirty = true;
if self.cursor.row >= height {
self.cursor.row = height - 1;
}
if self.cursor.col >= width {
self.cursor.col = width - 1;
}
}
// fn putc_normal(&mut self, ch: char) -> bool {
// let mut redraw = match ch {
// // c if c >= 127 => {
// // let attr = CellAttributes {
// // fg: Color::Black,
// // bg: Color::Red,
// // bright: false,
// // };
// // self.buffer.set_cell(self.cursor, GridCell::new('?', attr));
// // self.cursor.col += 1;
// // true
// // }
// '\x1B' => {
// self.esc_state = EscapeState::Escape;
// self.esc_args.clear();
// self.esc_args.push(0);
// return false;
// }
// '\x7F' | '\x08' => {
// }
// '\r' => {
// }
// '\t' => {
// }
// '\n' => {
// }
// _ => {
// }
// };
// redraw
// }
// fn handle_ctlseq(&mut self, byte: u8, c: char) -> bool {
// let redraw = match c {
// 'h' if byte == b'?' => match self.esc_args.get(0).copied().unwrap_or(0) {
// 25 => {
// // Cursor visible
// self.cursor_visible = true;
// true
// }
// 1049 => {
// // Enter alternate mode
// self.alternate = true;
// true
// }
// _ => false,
// },
// 'l' if byte == b'?' => match self.esc_args.get(0).copied().unwrap_or(0) {
// 25 => {
// // Cursor not visible
// self.cursor_visible = false;
// true
// }
// 1049 => {
// // Leave alternate mode
// self.alternate = false;
// self.clear();
// self.cursor = Cursor { col: 0, row: 0 };
// self.cursor_dirty = true;
// true
// }
// _ => false,
// },
// 'q' => {
// match self.esc_args.get(0).copied().unwrap_or(0) {
// 3 | 4 => self.cursor_style = CursorStyle::Underline,
// 5 | 6 => self.cursor_style = CursorStyle::Bar,
// _ => self.cursor_style = CursorStyle::Block,
// }
// self.cursor_dirty = true;
// true
// }
// 'c' => {
// // Send device attributes
// false
// }
// // // Move cursor down
// // 'B' => {
// // let amount = self.esc_args[0].max(1);
// // if self.cursor.row < self.buffer.height - 1 {
// // let amount = amount.min((self.buffer.height - self.cursor.row) as u32 - 1);
// // self.buffer.set_row_dirty(self.cursor.row);
// // self.cursor.row += amount as usize;
// // true
// // } else {
// // false
// // }
// // }
// // Move back one character
// 'D' => {
// if self.cursor.col > 0 {
// self.buffer.set_row_dirty(self.cursor.row);
// self.cursor.col -= 1;
// true
// } else {
// false
// }
// }
// // Character attributes
// 'm' => {
// }
// // Move cursor to position
// 'f' => {
// }
// // Move cursor to home position (0; 0)
// 'H' => {
// }
// '$' => {
// return false;
// }
// // Clear rows/columns/screen
// 'J' => match self.esc_args[0] {
// },
// 'X' => {
// let amount = self.esc_args[0].max(1);
// for i in 0..amount {
// let x = i as usize + self.cursor.col;
// if x >= self.buffer.width {
// break;
// }
// self.buffer.set_cell(
// Cursor {
// col: x,
// ..self.cursor
// },
// GridCell {
// char: ' ',
// attrs: self.attributes,
// },
// );
// }
// true
// }
// 'K' => match self.esc_args[0] {
// },
// _ => {
// let ch0 = if byte == b'(' { '(' } else { '[' };
// let ch1 = if byte == b'?' { "?" } else { "" };
// log::warn!("Unhandled ctlseq: ESC {ch0}{ch1} {:?} {c}", self.esc_args);
// false
// }
// };
// self.esc_state = EscapeState::Normal;
// redraw
// }
// fn handle_ctlseq_byte(&mut self, byte: u8, c: char) -> bool {
// match c {
// '?' if byte == 0 => {
// self.esc_state = EscapeState::Csi(CsiState { byte: b'?' });
// false
// }
// c if let Some(digit) = c.to_digit(10) => {
// let arg = self.esc_args.last_mut().unwrap();
// *arg *= 10;
// *arg += digit;
// false
// }
// ';' => {
// self.esc_args.push(0);
// false
// }
// _ => self.handle_ctlseq(byte, c),
// }
// }
fn fix_cursor(&mut self, mut redraw: bool) -> bool {
if self.alternate {
self.cursor.col = self.cursor.col.min(self.buffer.width - 1);
self.cursor.row = self.cursor.row.min(self.buffer.height - 1);
}
if self.cursor.col >= self.buffer.width {
if self.cursor.row < self.buffer.height {
self.buffer.rows[self.cursor.row].dirty = true;
}
self.cursor.row += 1;
self.cursor.col = 0;
redraw = true;
}
while self.cursor.row >= self.buffer.height {
self.buffer
.scroll_once(self.default_attributes.fg, self.default_attributes.bg);
self.cursor.row -= 1;
redraw = true;
}
redraw
}
fn handle_control_sequence(&mut self, pty: &mut PtyMaster, seq: ControlSequence) -> bool {
match seq {
ControlSequence::SetCharacterAttribute(attr) => match attr {
CharacterAttribute::Reset => {
self.attributes = self.default_attributes;
self.fg_index = Some(7);
self.update_fg_color();
true
}
CharacterAttribute::ForegroundRgb(r, g, b) => {
self.attributes.fg = Color::new(r, g, b);
self.fg_index = None;
self.update_fg_color();
true
}
CharacterAttribute::FgIndex(Some(index)) => {
self.fg_index = Some(index as u32);
self.update_fg_color();
true
}
CharacterAttribute::FgIndex(None) => {
self.fg_index = Some(7);
self.update_fg_color();
true
}
CharacterAttribute::BackgroundRgb(r, g, b) => {
self.attributes.bg = Color::new(r, g, b);
self.fg_index = None;
self.update_fg_color();
true
}
CharacterAttribute::BgIndex(Some(index)) => {
self.attributes.bg = Color::from_escape(&CONFIG, false, index as u32)
.unwrap_or(self.default_attributes.bg);
self.update_fg_color();
true
}
CharacterAttribute::BgIndex(None) => {
self.attributes.bg = self.default_attributes.bg;
self.update_fg_color();
true
}
CharacterAttribute::DefaultFgBg => {
self.attributes.fg = self.default_attributes.fg;
self.attributes.bg = self.default_attributes.bg;
self.fg_index = Some(7);
self.update_fg_color();
true
}
CharacterAttribute::Bold => {
self.attributes.bold = true;
self.update_fg_color();
true
}
CharacterAttribute::Normal | CharacterAttribute::Faint => {
self.attributes.bold = false;
self.update_fg_color();
true
}
CharacterAttribute::Italicized(set) => {
self.attributes.italic = set;
false
}
CharacterAttribute::Underlined(set) => {
self.attributes.underlined = set;
false
}
CharacterAttribute::CrossedOut(set) => {
self.attributes.strikethrough = set;
false
}
_ => {
log::warn!("TODO: SetCharacterAttribute({attr:?})");
false
}
},
// Erase
ControlSequence::EraseInDisplay(erase) => match erase {
EraseInDisplay::All => {
self.buffer
.clear(self.default_attributes.fg, self.attributes.bg);
self.cursor_dirty = true;
true
}
EraseInDisplay::Above => {
for row in 0..self.cursor.row {
self.buffer
.erase_row(row, self.attributes.fg, self.attributes.bg);
}
true
}
EraseInDisplay::Below => {
for row in self.cursor.row + 1..self.buffer.height {
self.buffer
.erase_row(row, self.attributes.fg, self.attributes.bg);
}
true
}
EraseInDisplay::SavedLines => false,
},
ControlSequence::EraseInLine(erase) => match erase {
EraseInLine::ToRight => {
self.buffer.rows[self.cursor.row].erase_to_right(
self.cursor.col,
self.default_attributes.fg,
self.attributes.bg,
);
true
}
EraseInLine::ToLeft => {
log::warn!("TODO: Erase in line to left");
false
}
EraseInLine::All => {
self.buffer.erase_row(
self.cursor.row,
self.default_attributes.fg,
self.attributes.bg,
);
true
}
},
ControlSequence::EraseCharacters(times) => {
log::warn!("TODO: EraseCharacters {times}");
false
}
// Scroll
ControlSequence::InsertLines(count) => {
log::warn!("XXX: InsertLines {count}");
for _ in 0..count {
self.buffer
.insert_row(self.cursor.row, self.attributes.fg, self.attributes.bg);
}
true
}
ControlSequence::DeleteLines(count) => {
log::warn!("XXX: DeleteLines {count}");
for _ in 0..count {
self.buffer
.remove_row(self.cursor.row, self.attributes.fg, self.attributes.bg);
}
true
}
// Cursor controls
ControlSequence::SetCursorStyle(style) => {
self.cursor_style = style;
self.cursor_dirty = true;
true
}
ControlSequence::MoveCursorUp(times) => {
let new_row = self.cursor.row.saturating_sub(times as usize);
self.cursor.row = new_row;
self.cursor_dirty = true;
true
}
ControlSequence::MoveCursorDown(times) => {
let new_row = (self.cursor.row + times as usize).min(self.buffer.height - 1);
self.cursor.row = new_row;
self.cursor_dirty = true;
true
}
ControlSequence::MoveCursorForward(times) => {
let new_col = (self.cursor.col + times as usize).min(self.buffer.width - 1);
self.cursor.col = new_col;
self.cursor_dirty = true;
true
}
ControlSequence::SetCursorPosition(row, col) => {
let row = row.saturating_sub(1);
let col = col.saturating_sub(1);
self.buffer.set_row_dirty(self.cursor.row);
self.cursor = Cursor {
row: row as _,
col: col as _,
};
self.cursor_dirty = true;
true
}
// "Device" controls
ControlSequence::ReportCursorPosition => {
write!(pty, "\x1B[{};{}R", self.cursor.row + 1, self.cursor.col + 1).ok();
pty.flush().ok();
false
}
ControlSequence::QueryKeyModifiers(_query) => {
// TODO
pty.write_all(b"\x1B[>0m").ok();
pty.flush().ok();
false
}
ControlSequence::Decrqm => {
pty.write_all(b"\x1B[?0;0$y").ok();
pty.flush().ok();
false
}
ControlSequence::SendDeviceAttributes(ps) => {
_ = ps;
pty.write_all(b"\x1B[?1;2c").ok();
pty.flush().ok();
false
}
// Not handled yet
ControlSequence::SetWindowParameter(_, _) => false,
_ => {
log::warn!("TODO: {seq:?}");
false
}
}
}
pub fn handle_shell_output(&mut self, pty: &mut PtyMaster, ch: u8) -> bool {
let Some(ch) = self.utf8_decode.push(ch) else {
return false;
};
match self.esc_parser.push(ch) {
Some(TerminalInput::Character(ch)) => {
self.buffer
.set_cell(self.cursor, GridCell::new(ch, self.attributes));
self.cursor.col += 1;
self.fix_cursor(true)
}
Some(TerminalInput::CarriageReturn) => {
self.buffer.rows[self.cursor.row].dirty = true;
self.cursor.col = 0;
self.fix_cursor(true)
}
Some(TerminalInput::NewLine) => {
self.buffer.rows[self.cursor.row].dirty = true;
self.cursor.row += 1;
self.cursor.col = 0;
self.fix_cursor(true)
}
Some(TerminalInput::Tab) => {
self.buffer.rows[self.cursor.row].dirty = true;
self.cursor.col = (self.cursor.col + 3) & !3;
self.fix_cursor(true)
}
Some(TerminalInput::NewPage) => {
todo!();
}
Some(TerminalInput::Backspace) => {
if self.cursor.col > 0 {
self.cursor.col -= 1;
} else if self.cursor.row > 0 {
self.cursor.row -= 1;
self.cursor.col = 0;
}
self.fix_cursor(true)
}
Some(TerminalInput::Delete) => {
todo!();
}
Some(TerminalInput::Bell) | Some(TerminalInput::VerticalTab) => false,
Some(TerminalInput::Control(seq)) => self.handle_control_sequence(pty, seq),
None => false,
}
// if ch == 0x07 {
// return false;
// }
// if let Some(ch) = self.utf8_decode.push(ch) {
// match self.esc_state {
// EscapeState::Normal => self.putc_normal(ch),
// EscapeState::Escape => match ch {
// '[' => {
// self.esc_state = EscapeState::Csi(CsiState { byte: 0 });
// false
// }
// '(' => {
// self.esc_state = EscapeState::Csi(CsiState { byte: b'(' });
// false
// }
// _ => {
// self.esc_state = EscapeState::Normal;
// false
// }
// },
// EscapeState::Csi(CsiState { byte }) => self.handle_ctlseq_byte(byte, ch),
// }
// } else {
// false
// }
}
pub fn invalidate_current_viewport(&mut self) {
self.buffer.invalidate_rows(self.scroll);
}
pub fn adjust_scroll(&mut self) -> usize {
if self.scroll > self.buffer.scrollback.len() {
self.scroll = self.buffer.scrollback.len();
}
self.scroll
}
pub fn scroll_up(&mut self) -> bool {
let max = self.buffer.scrollback.len();
if max == 0 {
self.scroll = 0;
return true;
}
let amount = max.min(8);
if amount != 0 {
self.scroll += amount;
self.invalidate_current_viewport();
true
} else {
false
}
}
pub fn scroll_down(&mut self) -> bool {
let amount = self.scroll.min(8);
if amount != 0 {
self.scroll -= amount;
self.invalidate_current_viewport();
true
} else {
false
}
}
pub fn scroll_home(&mut self) -> bool {
if self.scroll != self.buffer.scrollback.len() {
self.scroll = self.buffer.scrollback.len();
self.invalidate_current_viewport();
true
} else {
false
}
}
pub fn scroll_end(&mut self) -> bool {
if self.scroll != 0 {
self.scroll = 0;
self.invalidate_current_viewport();
true
} else {
false
}
}
}
@@ -0,0 +1,107 @@
use std::{
os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
process::{Command, ExitCode, Stdio},
};
use cross::{
io::{PidFd, Poll, TerminalOptions, TerminalOptionsImpl, TerminalSize},
process::CommandSpawnExt,
};
use libcolors::application::Application;
use crate::{error::Error, font::Fonts, terminal::State};
pub struct Terminal {
application: Application<RawFd>,
window_id: u32,
poll: Poll,
connection_fd: RawFd,
}
impl Terminal {
pub fn new() -> Result<Self, Error> {
let fonts = Fonts::from_config()?;
let termios = TerminalOptionsImpl::normal();
let (pty_master, pty_slave) = cross::io::open_pty(
&termios,
TerminalSize {
rows: 25,
columns: 80,
x_pixels: 80 * 8,
y_pixels: 25 * 12,
},
)?;
let pty_master_fd = pty_master.as_raw_fd();
let pty_slave_stdin = pty_slave.into_raw_fd();
let pty_slave_stdout = cross::io::clone_fd(pty_slave_stdin)?;
let pty_slave_stderr = cross::io::clone_fd(pty_slave_stdin)?;
let child = unsafe {
Command::new("/bin/sh")
.arg("-l")
.stdin(Stdio::from_raw_fd(pty_slave_stdin))
.stdout(Stdio::from_raw_fd(pty_slave_stdout))
.stderr(Stdio::from_raw_fd(pty_slave_stderr))
.create_session()?
.spawn()?
};
let child_pidfd = PidFd::new(child.id())?;
let child_pidfd_fd = child_pidfd.as_raw_fd();
let state = State::new(pty_master, child, child_pidfd, fonts);
let application = Application::single_windowed(state)?;
let window_id = application.main_window_id().expect("main window id");
let connection_fd = application.connection().lock().unwrap().as_raw_fd();
let mut poll = Poll::new()?;
poll.add(&connection_fd)?;
poll.add(&pty_master_fd)?;
poll.add(&child_pidfd_fd)?;
Ok(Self {
application,
window_id,
poll,
connection_fd,
})
}
fn run_inner(&mut self) -> Result<ExitCode, Error> {
loop {
if self.application.handle_user_events()? {
break;
}
// while !ABORT.load(Ordering::Acquire) {
match self.poll.wait(None)? {
Some(fd) if fd == self.connection_fd => {
if self.application.poll_events(&mut ())? {
break;
}
}
Some(fd) => {
self.application.push_window_event(self.window_id, fd);
}
None => (),
}
}
Ok(ExitCode::SUCCESS)
}
pub fn run(mut self) -> ExitCode {
match self.run_inner() {
Ok(code) => code,
Err(error) => {
log::error!("Error: {error}");
ExitCode::FAILURE
}
}
}
}
@@ -0,0 +1,386 @@
use std::collections::VecDeque;
use bitflags::bitflags;
use libterm::escape::{CursorStyle, EraseInDisplay, EraseInLine};
use crate::{attribute::Color, CONFIG};
bitflags! {
#[derive(Clone, Copy)]
pub struct CellAttributes: u8 {
const BOLD = 1 << 0;
const ITALIC = 1 << 1;
const UNDERLINE = 1 << 2;
const STRIKETHROUGH = 1 << 3;
const INVERSE = 1 << 4;
}
}
#[derive(Clone, Copy)]
pub struct Cell {
pub character: char,
pub fg: Color,
pub bg: Color,
pub attr: CellAttributes,
}
pub struct Row {
cells: Box<[Cell]>,
damage: bool,
}
#[derive(Clone, Copy)]
pub struct Cursor {
pub row: usize,
pub column: usize,
}
bitflags! {
pub struct BufferOptions: u32 {
const WRAP_LINES = 1 << 0;
}
}
pub struct Buffer {
// `scrollback` rows - history
// `height` rows - current
rows: VecDeque<Row>,
scrollback_limit: usize,
scroll_position: usize,
pub width: usize,
pub height: usize,
default_fg: Color,
default_bg: Color,
fg: Color,
fg_index: Option<u32>,
bg: Color,
attr: CellAttributes,
options: BufferOptions,
cursor: Cursor,
cursor_style: CursorStyle,
}
impl Row {
pub fn empty(width: usize, fg: Color, bg: Color, attr: CellAttributes) -> Self {
Self {
cells: vec![
Cell {
character: '\0',
fg,
bg,
attr
};
width
]
.into_boxed_slice(),
damage: true,
}
}
pub fn set(&mut self, column: usize, ch: char, fg: Color, bg: Color, attr: CellAttributes) {
self.cells[column] = Cell {
character: ch,
fg,
bg,
attr,
};
self.damage = true;
}
pub fn cells(&self) -> &[Cell] {
&self.cells[..]
}
pub fn cells_mut(&mut self) -> &mut [Cell] {
&mut self.cells[..]
}
}
impl Buffer {
pub fn new(rows: usize, columns: usize, scrollback: usize) -> Self {
let default_fg = Color::from_escape(&CONFIG, false, 7).unwrap_or(Color::from_u32(0xFFFFFF));
let default_bg = Color::from_escape(&CONFIG, false, 0).unwrap_or(Color::from_u32(0x000000));
Self {
rows: VecDeque::new(),
scrollback_limit: scrollback,
scroll_position: 0,
width: columns,
height: rows,
default_fg,
default_bg,
fg_index: Some(7),
fg: default_fg,
bg: default_bg,
attr: CellAttributes::empty(),
options: BufferOptions::WRAP_LINES,
cursor: Cursor { row: 0, column: 0 },
cursor_style: CursorStyle::Block(true),
}
}
fn update_foreground(&mut self) {
let Some(index) = self.fg_index else {
return;
};
let bold = self.attr.contains(CellAttributes::BOLD);
if let Some(color) = Color::from_escape(&CONFIG, bold, index) {
self.fg = color;
} else {
self.fg_index = Some(7);
self.fg = self.default_fg;
}
}
pub fn set_attribute(&mut self, attr: CellAttributes, set: bool) {
if set {
self.attr |= attr;
} else {
self.attr &= !attr;
}
if attr.contains(CellAttributes::BOLD) {
self.update_foreground();
}
}
pub fn set_cursor_style(&mut self, style: CursorStyle) {
self.cursor_style = style;
}
pub fn cursor_style(&self) -> CursorStyle {
self.cursor_style
}
pub fn set_foreground_index(&mut self, index: u32) {
self.fg_index = Some(index);
self.update_foreground();
}
pub fn current_fg(&self) -> Color {
self.fg
}
pub fn set_background_index(&mut self, index: u32) {
if let Some(color) = Color::from_escape(&CONFIG, false, index) {
self.bg = color;
} else {
self.bg = self.default_bg;
}
}
pub fn map_damage<F: FnMut(usize, &Row) -> bool>(&mut self, mut map: F) {
let range = self.scroll_position..self.rows.len();
for i in range {
let y = i - self.scroll_position;
if !map(y, &self.rows[i]) {
break;
}
self.rows[i].damage = false;
}
}
pub fn damage_screen(&mut self) {
for i in self.scroll_position..self.rows.len() {
self.rows[i].damage = true;
}
}
pub fn damage_cursor(&mut self) {
let i = self.cursor.row + self.scroll_position;
if i < self.rows.len() {
self.rows[i].damage = true;
}
}
fn scroll_bottom(&self) -> usize {
self.rows.len().saturating_sub(self.height)
}
pub fn scroll_to_end(&mut self) {
self.scroll_position = self.scroll_bottom();
self.damage_screen();
}
pub fn cursor(&self) -> &Cursor {
&self.cursor
}
pub fn cursor_mut(&mut self) -> &mut Cursor {
&mut self.cursor
}
pub fn cursor_row_mut(&mut self) -> &mut Row {
assert!(self.cursor_in_bounds());
let i = self.cursor.row + self.scroll_position;
self.rows.resize_with(i + 1, || {
Row::empty(self.width, self.fg, self.bg, self.attr)
});
&mut self.rows[i]
}
pub fn cell_at(&self, row: usize, col: usize) -> Option<&Cell> {
let i = row + self.scroll_position;
let row = self.rows.get(i)?;
row.cells().get(col)
}
pub fn putc(&mut self, ch: char) {
let col = self.cursor.column;
let fg = self.fg;
let bg = self.bg;
let attr = self.attr;
self.cursor_row_mut().set(col, ch, fg, bg, attr);
self.cursor.column += 1;
}
pub fn scroll_down(&mut self, amount: usize) -> usize {
let new = (self.scroll_position + amount).min(self.scroll_bottom());
let actual = new - self.scroll_position;
self.scroll_position = new;
if actual != 0 {
self.damage_screen();
}
actual
}
pub fn scroll_up(&mut self, amount: usize) -> usize {
let new = self.scroll_position.saturating_sub(amount);
let actual = self.scroll_position - new;
self.scroll_position = new;
if actual != 0 {
self.damage_screen();
}
actual
}
pub fn newline(&mut self, _carriage_return: bool) {
todo!()
}
pub fn insert_lines(&mut self, _count: usize) -> bool {
todo!()
}
pub fn delete_lines(&mut self, _count: usize) -> bool {
todo!()
}
pub fn erase_in_display(&mut self, erase: EraseInDisplay) -> bool {
let fg = self.fg;
let bg = self.bg;
let attr = self.attr;
let w = self.width;
match erase {
EraseInDisplay::All => {
for y in 0..self.height {
let i = self.scroll_position + y;
if i >= self.rows.len() {
break;
}
self.rows[i] = Row::empty(w, fg, bg, attr);
}
}
EraseInDisplay::Below => {
todo!();
}
EraseInDisplay::Above => {
todo!();
}
EraseInDisplay::SavedLines => {
todo!();
}
}
true
}
pub fn erase_in_line(&mut self, erase: EraseInLine) -> bool {
let fg = self.fg;
let bg = self.bg;
let attr = self.attr;
let w = self.width;
let col = self.cursor().column;
let row = self.cursor_row_mut();
match erase {
EraseInLine::All => {
*row = Row::empty(w, fg, bg, attr);
}
EraseInLine::ToLeft => {
for i in 0..col {
row.cells[i] = Cell {
character: '\0',
fg,
bg,
attr,
};
}
}
EraseInLine::ToRight => {
for i in col + 1..w {
row.cells[i] = Cell {
character: '\0',
fg,
bg,
attr,
};
}
}
}
row.damage = true;
true
}
pub fn erase_characters(&mut self, _count: usize) -> bool {
false
}
pub fn clip_cursor(&mut self) -> bool {
let mut redraw = false;
if self.cursor.column >= self.width {
if self.options.contains(BufferOptions::WRAP_LINES) {
self.cursor.row += 1;
self.cursor.column = 0;
} else {
self.cursor.column = self.width - 1;
}
}
if self.scrollback_limit != 0 {
while self.cursor.row >= self.height {
self.scroll_position += 1;
self.cursor.row -= 1;
self.damage_screen();
redraw = true;
}
while self.scroll_position >= self.scrollback_limit {
log::info!("Drop first");
self.rows.pop_back();
self.scroll_position -= 1;
}
} else {
self.cursor.row = self.cursor.row.min(self.height - 1);
}
redraw
}
fn cursor_in_bounds(&self) -> bool {
self.cursor.row + self.scroll_position < self.height + self.scrollback_limit
&& self.cursor.column < self.width
}
}
@@ -0,0 +1,184 @@
use std::{
mem,
ops::{Deref, DerefMut},
};
use libcolors::{
application::surface::SurfaceLike,
geometry::{Point, Rectangle},
};
use libterm::escape::CursorStyle;
use crate::{
attribute::Color,
font::{Font, Fonts},
terminal::buffer::{Cell, CellAttributes},
};
pub struct DrawContext<'s, S: SurfaceLike> {
surface: &'s mut S,
fonts: &'s Fonts,
font_width: usize,
font_height: usize,
}
fn blend(x: u8, y: u8, f: f32) -> u8 {
let v = f * (x as f32) + (1.0 - f) * (y as f32);
v as u8
}
impl<'s, S: SurfaceLike> DrawContext<'s, S> {
pub fn new(
surface: &'s mut S,
fonts: &'s Fonts,
font_width: usize,
font_height: usize,
) -> Self {
Self {
surface,
fonts,
font_width,
font_height,
}
}
pub fn draw_cursor(
&mut self,
position: Point<u32>,
cell_under_cursor: Option<Cell>,
default_fg: Color,
style: CursorStyle,
) {
let fw = self.font_width as u32;
let fh = self.font_height as u32;
let fg = cell_under_cursor
.as_ref()
.map(|c| c.fg)
.unwrap_or(default_fg);
match style {
CursorStyle::Bar(_) => {
self.fill_rect(
Rectangle {
x: position.x,
y: position.y,
w: 1,
h: fh,
},
fg.to_u32(),
);
}
CursorStyle::Block(_) => {
if let Some(cell_under_cursor) = cell_under_cursor {
self.draw_cell(position, &cell_under_cursor, true);
} else {
self.fill_rect(
Rectangle {
x: position.x,
y: position.y,
w: position.x + fw,
h: position.y + fh,
},
fg.to_u32(),
);
}
}
CursorStyle::Underline(_) => {
self.fill_rect(
Rectangle {
x: position.x,
y: position.y + fh - 1,
w: fw,
h: 1,
},
fg.to_u32(),
);
}
}
}
pub fn draw_cell(&mut self, position: Point<u32>, cell: &Cell, invert: bool) {
let fw = self.font_width as u32;
let fh = self.font_height as u32;
let cell_rect = Rectangle {
x: position.x,
y: position.y,
w: fw,
h: fh,
};
let mut bg = cell.bg;
let mut fg = cell.fg;
let invert = cell.attr.contains(CellAttributes::INVERSE) ^ invert;
if invert {
mem::swap(&mut bg, &mut fg);
}
self.fill_rect(cell_rect, bg.to_u32());
if cell.character == '\0' {
return;
}
let font = match (
cell.attr.contains(CellAttributes::BOLD),
cell.attr.contains(CellAttributes::ITALIC),
) {
(true, true) => &self.fonts.bold_italic,
(false, true) => &self.fonts.italic,
(true, false) => &self.fonts.bold,
(false, false) => &self.fonts.regular,
};
font.map_glyph(cell.character, |x, y, v| {
let v = v.min(1.0);
let r = blend(fg.r, bg.r, v);
let g = blend(fg.g, bg.g, v);
let b = blend(fg.b, bg.b, v);
let color = (b as u32) | ((g as u32) << 8) | ((r as u32) << 16) | 0xFF000000;
self.set_pixel(position.x + x as u32, position.y + y as u32, color);
});
if cell.attr.contains(CellAttributes::UNDERLINE) {
self.fill_rect(
Rectangle {
x: position.x,
y: position.y + fh - 1,
w: fw,
h: 1,
},
fg.to_u32(),
);
}
if cell.attr.contains(CellAttributes::STRIKETHROUGH) {
let y = position.y + fh / 2;
self.fill_rect(
Rectangle {
x: position.x,
y,
w: fw,
h: 1,
},
fg.to_u32(),
);
}
}
}
impl<'s, S: SurfaceLike> Deref for DrawContext<'s, S> {
type Target = S;
fn deref(&self) -> &Self::Target {
self.surface
}
}
impl<'s, S: SurfaceLike> DerefMut for DrawContext<'s, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.surface
}
}
+484
View File
@@ -0,0 +1,484 @@
use std::{
io::{Read, Write},
os::fd::{AsRawFd, RawFd},
process::Child,
};
use cross::io::{PidFd, PtyMaster};
use libcolors::{
application::{
surface::{Surface, SurfaceLike},
window::{EventOutcome, WindowHandler},
},
event::{KeyInput, KeyModifiers},
geometry::{Point, Rectangle},
input::Key,
};
pub use application::Terminal;
use libterm::escape::{CharacterAttribute, ControlSequence, EscapeParser, TerminalInput};
use crate::{
attribute::Color,
font::{Font, Fonts},
terminal::{
buffer::{Buffer, CellAttributes, Cursor},
draw::DrawContext,
},
};
pub mod application;
pub mod buffer;
pub mod draw;
struct State {
// I/O
pty_master: PtyMaster,
child: Child,
#[allow(unused)]
child_pidfd: PidFd,
utf8: Utf8Decoder,
escape: EscapeParser,
// Screen
fonts: Fonts,
focused: bool,
current_buffer: usize,
buffers: [Buffer; 2],
}
#[derive(Debug, Default)]
struct Utf8Decoder {
buffer: [u8; 8],
buffer_len: usize,
}
impl Utf8Decoder {
pub fn push(&mut self, byte: u8) -> Option<char> {
self.buffer[self.buffer_len] = byte;
self.buffer_len += 1;
if let Some(ch) = std::str::from_utf8(&self.buffer[..self.buffer_len])
.ok()
.and_then(|s| s.chars().next())
{
self.buffer_len = 0;
return Some(ch);
}
// Buffer full and no character decoded
if self.buffer_len >= self.buffer.len() {
self.buffer_len = 0;
Some(std::char::REPLACEMENT_CHARACTER)
} else {
None
}
}
}
impl State {
pub fn new(pty_master: PtyMaster, child: Child, child_pidfd: PidFd, fonts: Fonts) -> Self {
let primary_buffer = Buffer::new(25, 80, 1000);
let alternate_buffer = Buffer::new(25, 80, 0);
Self {
pty_master,
child,
child_pidfd,
utf8: Utf8Decoder::default(),
escape: EscapeParser::Normal,
fonts,
focused: false,
current_buffer: 0,
buffers: [primary_buffer, alternate_buffer],
}
}
fn current_buffer_mut(&mut self) -> &mut Buffer {
&mut self.buffers[self.current_buffer]
}
fn handle_character_attribute(&mut self, attr: CharacterAttribute) {
let buffer = self.current_buffer_mut();
match attr {
CharacterAttribute::Reset => {
buffer.set_attribute(CellAttributes::all(), false);
buffer.set_foreground_index(7);
buffer.set_background_index(0);
}
CharacterAttribute::Bold => {
buffer.set_attribute(CellAttributes::BOLD, true);
}
CharacterAttribute::Faint | CharacterAttribute::Normal => {
buffer.set_attribute(CellAttributes::BOLD, false);
}
CharacterAttribute::Hidden(_) => (),
CharacterAttribute::Blinking(_) => (),
CharacterAttribute::Inverse(set) => {
buffer.set_attribute(CellAttributes::INVERSE, set);
}
CharacterAttribute::Italicized(set) => {
buffer.set_attribute(CellAttributes::ITALIC, set);
}
CharacterAttribute::Underlined(set) => {
buffer.set_attribute(CellAttributes::UNDERLINE, set);
}
CharacterAttribute::CrossedOut(set) => {
buffer.set_attribute(CellAttributes::STRIKETHROUGH, set);
}
CharacterAttribute::DefaultFgBg => {
buffer.set_foreground_index(7);
buffer.set_background_index(0);
}
CharacterAttribute::FgIndex(index) => {
buffer.set_foreground_index(index.unwrap_or(7) as u32);
}
CharacterAttribute::BgIndex(index) => {
buffer.set_background_index(index.unwrap_or(0) as u32);
}
CharacterAttribute::ForegroundRgb(r, g, b) => {
let color = Color::new(r, g, b);
_ = color;
}
CharacterAttribute::BackgroundRgb(r, g, b) => {
let color = Color::new(r, g, b);
_ = color;
}
}
}
fn handle_control(&mut self, seq: ControlSequence) -> bool {
let buffer = self.current_buffer_mut();
match seq {
// Buffer modification
ControlSequence::EraseInLine(erase) => buffer.erase_in_line(erase),
ControlSequence::EraseInDisplay(erase) => buffer.erase_in_display(erase),
ControlSequence::EraseCharacters(count) => buffer.erase_characters(count as usize),
ControlSequence::DeleteLines(count) => buffer.delete_lines(count as usize),
ControlSequence::InsertLines(count) => buffer.insert_lines(count as usize),
ControlSequence::SetScrollingRegion(top, bottom) => {
log::warn!("TODO: SetScrollingRegion({top}, {bottom})");
false
}
// Attributes
ControlSequence::SetCursorStyle(style) => {
buffer.set_cursor_style(style);
true
}
ControlSequence::SetCharacterAttribute(attr) => {
self.handle_character_attribute(attr);
false
}
ControlSequence::SetWindowParameter(param, value) => {
log::info!("SetWindowParameter {param:?} = {value:?}");
false
}
// Modes
ControlSequence::SetAlternateMode(enable) => {
if enable {
log::info!("Enter alternate mode");
self.current_buffer = 1;
} else {
log::info!("Leave alternate mode");
self.current_buffer = 0;
}
self.current_buffer_mut().damage_screen();
true
}
ControlSequence::SetBracketedPasteMode(enable) => {
log::warn!("SetBracketedPasteMode {enable}");
false
}
ControlSequence::SetApplicationCursor(_enable) => false,
ControlSequence::SetAlternateKeypad(_enable) => false,
// Cursor
ControlSequence::MoveCursorUp(count) => {
let old = buffer.cursor_mut().row;
buffer.damage_cursor();
buffer.cursor_mut().row = old.saturating_sub(count as usize);
buffer.clip_cursor()
}
ControlSequence::MoveCursorDown(count) => {
buffer.damage_cursor();
buffer.cursor_mut().row += count as usize;
buffer.clip_cursor()
}
ControlSequence::MoveCursorForward(count) => {
buffer.damage_cursor();
buffer.cursor_mut().column += count as usize;
buffer.clip_cursor()
}
ControlSequence::SetCursorPosition(row, column) => {
buffer.damage_cursor();
*buffer.cursor_mut() = Cursor {
row: row as usize,
column: column as usize,
};
buffer.clip_cursor()
}
// Queries
ControlSequence::QueryKeyModifiers(_query) => todo!(),
ControlSequence::SendDeviceAttributes(_query) => todo!(),
ControlSequence::ReportCursorPosition => {
log::warn!("ReportCursorPosition");
false
}
ControlSequence::DeviceStatusReport => todo!(),
ControlSequence::Decrqm => todo!(),
}
}
pub fn handle_raw_pty_input(&mut self, byte: u8) -> bool {
// log::info!("{:?} {:#x}", byte as char, byte);
let Some(ch) = self.utf8.push(byte) else {
return false;
};
let input = self.escape.push(ch);
let buffer = self.current_buffer_mut();
let mut redraw = match input {
Some(TerminalInput::Character(ch)) => {
buffer.putc(ch);
true
}
Some(TerminalInput::NewLine) => {
buffer.cursor_mut().row += 1;
false
}
Some(TerminalInput::CarriageReturn) => {
buffer.cursor_mut().column = 0;
false
}
Some(TerminalInput::Tab) => {
log::warn!("Tab");
//
false
}
Some(TerminalInput::Delete) => {
log::warn!("Delete");
//
false
}
Some(TerminalInput::Backspace) => {
log::warn!("Backspace");
//
false
}
Some(TerminalInput::NewPage) => {
//
false
}
Some(TerminalInput::Control(seq)) => self.handle_control(seq),
Some(TerminalInput::Bell | TerminalInput::VerticalTab) => false,
None => false,
};
redraw |= self.current_buffer_mut().clip_cursor();
redraw
}
}
impl WindowHandler for State {
type UserEvent = RawFd;
fn on_redraw_requested(&mut self, surface: &mut Surface) {
let columns = self.current_buffer_mut().width;
let cursor = *self.current_buffer_mut().cursor();
let cell_under_cursor = self
.current_buffer_mut()
.cell_at(cursor.row, cursor.column)
.copied();
let cursor_style = self.current_buffer_mut().cursor_style();
let default_fg = self.current_buffer_mut().current_fg();
let font_layout = self.fonts.regular.layout();
let fw = font_layout.width;
let fh = font_layout.height;
let mut dc = DrawContext::new(surface, &self.fonts, fw, fh);
// log::info!("Redraw");
self.buffers[self.current_buffer].map_damage(|y, row| {
let cells = row.cells();
let len = columns.min(cells.len());
let last = cells.last().unwrap();
let last_bg = last.bg.to_u32();
if dc.width() as usize > len * fw {
let extra = dc.width() as usize - len * fw;
dc.fill_rect(
Rectangle {
x: (len * fw) as u32,
y: (y * fh) as u32,
w: extra as u32,
h: fh as u32,
},
last_bg,
);
}
for (i, cell) in cells[..len].iter().enumerate() {
dc.draw_cell(
Point {
x: (i * fw) as u32,
y: (y * fh) as u32,
},
cell,
false,
);
}
true
});
// Draw cursor
let cursor_point = Point {
x: (cursor.column * fw) as u32,
y: (cursor.row * fh) as u32,
};
dc.draw_cursor(cursor_point, cell_under_cursor, default_fg, cursor_style);
}
fn on_close_requested(&mut self) -> EventOutcome {
log::info!("Close requested");
EventOutcome::Destroy
}
fn on_key_input(&mut self, input: KeyInput) -> EventOutcome {
log::info!("Key input: {input:?}");
let mut encode_buffer = [0; 8];
let need_redraw = if let Some(mut ch) = input.input {
if ch == '\n' {
ch = '\x0D';
}
let s = ch.encode_utf8(&mut encode_buffer);
self.pty_master.write_all(s.as_bytes()).ok();
// TODO scroll to end if needed
false
} else {
let buffer = self.current_buffer_mut();
let scroll_amount = buffer.height / 2;
match (input.modifiers, input.key) {
(KeyModifiers::SHIFT, Key::PageUp) => {
log::info!("Scroll up {scroll_amount}");
buffer.scroll_up(scroll_amount) != 0
}
(KeyModifiers::SHIFT, Key::PageDown) => {
log::info!("Scroll down {scroll_amount}");
buffer.scroll_down(scroll_amount) != 0
//need_redraw = s.scroll_down();
}
(KeyModifiers::NONE, Key::Escape) => {
self.pty_master.write_all(&[0x1B]).ok();
false
}
// (KeyModifiers::SHIFT, Key::Home) => {
// EventOutcome::None
// //need_redraw = s.scroll_home();
// }
// (KeyModifiers::SHIFT, Key::End) => {
// EventOutcome::None
// //need_redraw = s.scroll_end();
// }
// // termios chars
// (KeyModifiers::NONE, Key::Backspace) => {
// // self.pty_master.write_all(&[termios.erase_char()]).ok();
// // need_redraw = s.scroll_end();
// EventOutcome::None
// }
// (KeyModifiers::CTRL, Key::Char(b'c')) => {
// // self.pty_master
// // .write_all(&[termios.interrupt_char()])
// // .unwrap();
// // need_redraw = s.scroll_end();
// EventOutcome::None
// }
// (KeyModifiers::CTRL, Key::Char(b'd')) => {
// // self.pty_master.write_all(&[termios.eof_char()]).unwrap();
// // need_redraw = s.scroll_end();
// EventOutcome::None
// }
// other sequences
// (m, k) if let Some(data) = escape::map(m, k) => {
// self.pty_master.write_all(data).unwrap();
// // need_redraw = s.scroll_end();
// EventOutcome::None
// }
_ => false,
}
};
if need_redraw {
EventOutcome::Redraw
} else {
EventOutcome::None
}
}
fn on_focus_changed(&mut self, focused: bool) -> EventOutcome {
log::info!("focused = {focused}");
self.focused = focused;
EventOutcome::Redraw
}
fn on_resized(&mut self, width: u32, height: u32) -> EventOutcome {
log::warn!("TODO: Resize {width}x{height}");
EventOutcome::Redraw
}
fn on_user_event(&mut self, event: Self::UserEvent) -> EventOutcome {
if event == self.pty_master.as_raw_fd() {
let mut buf = [0u8; 256];
let len = match self.pty_master.read(&mut buf) {
Ok(0) => {
return EventOutcome::Destroy;
}
Ok(len) => len,
Err(error) => {
log::error!("Terminal read error: {error}");
return EventOutcome::Destroy;
}
};
let mut redraw = false;
for &byte in &buf[..len] {
redraw |= self.handle_raw_pty_input(byte);
}
if redraw {
EventOutcome::Redraw
} else {
EventOutcome::None
}
} else {
log::info!("Child event");
match self.child.try_wait() {
Ok(Some(status)) => {
log::info!("Child exited with status {status:?}");
return EventOutcome::Destroy;
}
Ok(None) => (),
Err(error) => {
log::error!("Child wait error: {error}");
return EventOutcome::Destroy;
}
}
EventOutcome::None
}
}
}
+4
View File
@@ -140,6 +140,10 @@ impl RawStdin {
pub fn open() -> io::Result<Self> {
sys::RawStdinImpl::open().map(Self)
}
pub fn set_options(&mut self, options: TerminalOptionsImpl) -> io::Result<TerminalOptionsImpl> {
self.0.set_options(options)
}
}
impl Read for RawStdin {
+6
View File
@@ -22,6 +22,12 @@ impl SharedMemory {
}
}
impl Into<OwnedFd> for SharedMemory {
fn into(self) -> OwnedFd {
self.0.into()
}
}
impl AsRawFd for SharedMemory {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
+2 -1
View File
@@ -47,6 +47,7 @@ pub(crate) trait Pipe: Read + Write + AsRawFd + Sized {
pub(crate) trait RawStdin: Sized + Read + AsRawFd {
fn open() -> io::Result<Self>;
fn set_options(&mut self, options: TerminalOptionsImpl) -> io::Result<TerminalOptionsImpl>;
// fn new(stdin: &'a mut Stdin) -> io::Result<Self>;
}
@@ -117,7 +118,7 @@ pub(crate) trait FileMapping: AsRawFd + DerefMut<Target = [u8]> + Sized {
fn map<F: Into<OwnedFd>>(file: F, size: usize, write: bool) -> io::Result<Self>;
}
pub(crate) trait SharedMemory: AsRawFd + Sized {
pub(crate) trait SharedMemory: AsRawFd + Into<OwnedFd> + Sized {
type Mapping: FileMapping;
fn new(size: usize) -> io::Result<Self>;
+6
View File
@@ -39,6 +39,12 @@ impl sys::SharedMemory for SharedMemoryImpl {
}
}
impl Into<OwnedFd> for SharedMemoryImpl {
fn into(self) -> OwnedFd {
self.fd
}
}
impl AsRawFd for SharedMemoryImpl {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
+1 -1
View File
@@ -13,7 +13,7 @@ pub struct PidFdImpl {
impl PidFd for PidFdImpl {
fn new(pid: u32) -> io::Result<Self> {
let pid = pid as i32;
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) } as i32;
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, libc::PIDFD_NONBLOCK) } as i32;
if fd < 0 {
return Err(io::Error::last_os_error());
}
+14 -7
View File
@@ -1,6 +1,7 @@
use std::{
io::{self, Read, Write},
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
process::Stdio,
};
use crate::sys::Pipe;
@@ -25,26 +26,32 @@ impl Pipe for PipeImpl {
Ok((Self { fd: read }, Self { fd: write }))
}
fn to_child_stdio(&self) -> std::process::Stdio {
todo!()
fn to_child_stdio(&self) -> Stdio {
unsafe { Stdio::from_raw_fd(self.as_raw_fd()) }
}
}
impl Read for PipeImpl {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let _ = buf;
todo!()
let len = unsafe { libc::read(self.fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) };
if len < 0 {
return Err(io::Error::last_os_error());
}
Ok(len as usize)
}
}
impl Write for PipeImpl {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let _ = buf;
todo!()
let len = unsafe { libc::write(self.fd.as_raw_fd(), buf.as_ptr().cast(), buf.len()) };
if len < 0 {
return Err(io::Error::last_os_error());
}
Ok(len as usize)
}
fn flush(&mut self) -> io::Result<()> {
todo!()
Ok(())
}
}
+7 -1
View File
@@ -5,7 +5,7 @@ use std::{
os::fd::{AsRawFd, RawFd},
};
use crate::sys::RawStdin;
use crate::{io::TerminalOptionsImpl, sys::RawStdin};
enum Inner {
Stdin(Stdin),
@@ -72,6 +72,12 @@ impl RawStdin for RawStdinImpl {
})?;
Ok(Self { inner, saved })
}
fn set_options(&mut self, options: TerminalOptionsImpl) -> io::Result<TerminalOptionsImpl> {
self.inner.update_options(|t| {
*t = options;
})
}
}
impl io::Read for RawStdinImpl {
@@ -31,6 +31,12 @@ impl SharedMemory for SharedMemoryImpl {
}
}
impl Into<OwnedFd> for SharedMemoryImpl {
fn into(self) -> OwnedFd {
self.inner.into()
}
}
impl AsRawFd for SharedMemoryImpl {
fn as_raw_fd(&self) -> RawFd {
self.inner.as_raw_fd()
+17 -8
View File
@@ -1,28 +1,37 @@
use std::{
io,
os::{
fd::{AsRawFd, RawFd},
yggdrasil::io::pid::{PidFd as YggPidFd, ProcessId},
},
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
};
use runtime::rt::{
process::{ProcessId, ProcessWait, WaitFlags},
sys as syscall,
};
use crate::sys::PidFd;
pub struct PidFdImpl(YggPidFd);
pub struct PidFdImpl {
fd: OwnedFd,
}
impl PidFd for PidFdImpl {
fn new(pid: u32) -> io::Result<Self> {
let pid = unsafe { ProcessId::from_raw(pid) };
YggPidFd::child(pid, false).map(Self)
let target = ProcessWait::Process(pid);
let fd = unsafe { syscall::create_pid(&target, WaitFlags::NON_BLOCKING) }
.map_err(io::Error::from)?;
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
Ok(Self { fd })
}
fn exit_status(&self) -> io::Result<i32> {
self.0.status()
let (_, code) = runtime::rt::io::read_pid_fd(self.fd.as_raw_fd())?;
Ok(code)
}
}
impl AsRawFd for PidFdImpl {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
self.fd.as_raw_fd()
}
}
@@ -63,6 +63,10 @@ impl RawStdin for RawStdinImpl {
let saved = inner.update_options(|_| TerminalOptions::raw_input())?;
Ok(Self { inner, saved })
}
fn set_options(&mut self, options: TerminalOptions) -> io::Result<TerminalOptions> {
self.inner.update_options(|_| options)
}
}
impl io::Read for RawStdinImpl {
+5 -1
View File
@@ -4,7 +4,7 @@ use std::{
time::Duration,
};
use crate::io::{Poll, RawStdin};
use crate::io::{Poll, RawStdin, TerminalOptionsImpl};
pub struct TerminalInput {
stdin: RawStdin,
@@ -75,6 +75,10 @@ impl TerminalInput {
})
}
pub fn set_options(&mut self, options: TerminalOptionsImpl) -> io::Result<TerminalOptionsImpl> {
self.stdin.set_options(options)
}
fn take(&mut self, count: usize) {
self.buffer.copy_within(count..self.buffer_len, 0);
self.buffer_len -= count;
+1 -1
View File
@@ -9,7 +9,7 @@ chrono.workspace = true
rustls.workspace = true
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil-rng_core-0.6.4" }
ring.workspace = true
# ring.workspace = true
sha2.workspace = true
hmac.workspace = true
chacha20poly1305.workspace = true
@@ -0,0 +1,96 @@
use std::{io, net::TcpStream};
use crate::{
connection::{tls::TlsConnection, TlsConnector},
HttpConnection, HttpConnector, TcpConnector,
};
pub struct AutoConnector {
tcp: TcpConnector,
tls: TlsConnector,
}
pub enum AutoConnection {
Tls(TlsConnection),
Tcp(TcpStream),
}
impl AutoConnector {
pub fn new(tcp: TcpConnector, tls: TlsConnector) -> Self {
Self { tcp, tls }
}
pub fn insecure() -> Self {
let tls = TlsConnector::insecure();
let tcp = TcpConnector;
Self { tcp, tls }
}
}
impl HttpConnector for AutoConnector {
type Error = io::Error;
type Connection = AutoConnection;
fn connect(
&mut self,
remote: &std::net::SocketAddr,
scheme: &str,
server_name: &str,
timeout: Option<std::time::Duration>,
options: super::HttpConnectionOptions,
) -> Result<Self::Connection, Self::Error> {
if scheme == "https" {
self.tls
.connect(remote, scheme, server_name, timeout, options)
.map(AutoConnection::Tls)
} else {
self.tcp
.connect(remote, scheme, server_name, timeout, options)
.map(AutoConnection::Tcp)
}
}
fn supports_scheme(&self, scheme: &str) -> bool {
scheme == "https" || scheme == "http"
}
fn default_port(&self, scheme: &str) -> u16 {
if scheme == "https" {
443
} else {
80
}
}
}
impl HttpConnection for AutoConnection {
type Error = io::Error;
fn send(&mut self, buffer: &[u8]) -> Result<usize, Self::Error> {
match self {
Self::Tls(c) => c.send(buffer),
Self::Tcp(c) => c.send(buffer),
}
}
fn send_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {
match self {
Self::Tls(c) => c.send_all(buffer),
Self::Tcp(c) => c.send_all(buffer),
}
}
fn recv(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
match self {
Self::Tls(c) => c.recv(buffer),
Self::Tcp(c) => c.recv(buffer),
}
}
fn recv_exact(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> {
match self {
Self::Tls(c) => c.recv_exact(buffer),
Self::Tcp(c) => c.recv_exact(buffer),
}
}
}
+7 -2
View File
@@ -1,10 +1,16 @@
use std::{net::SocketAddr, time::Duration};
pub use tcp::TcpConnector;
#[cfg(feature = "https")]
pub use auto::AutoConnector;
#[cfg(feature = "https")]
pub use tls::TlsConnector;
pub mod tcp;
#[cfg(feature = "https")]
pub mod auto;
#[cfg(feature = "https")]
pub mod tls;
@@ -21,8 +27,6 @@ pub trait HttpConnector {
type Connection: HttpConnection<Error = Self::Error>;
type Error: std::error::Error + Send + 'static;
const DEFAULT_PORT: u16;
fn connect(
&mut self,
remote: &SocketAddr,
@@ -33,6 +37,7 @@ pub trait HttpConnector {
) -> Result<Self::Connection, Self::Error>;
fn supports_scheme(&self, scheme: &str) -> bool;
fn default_port(&self, scheme: &str) -> u16;
}
#[derive(Clone)]
+4 -2
View File
@@ -12,8 +12,6 @@ impl HttpConnector for TcpConnector {
type Connection = TcpStream;
type Error = io::Error;
const DEFAULT_PORT: u16 = 80;
fn connect(
&mut self,
remote: &SocketAddr,
@@ -34,6 +32,10 @@ impl HttpConnector for TcpConnector {
fn supports_scheme(&self, scheme: &str) -> bool {
scheme.eq_ignore_ascii_case("http")
}
fn default_port(&self, _scheme: &str) -> u16 {
80
}
}
impl HttpConnection for TcpStream {
+4 -2
View File
@@ -29,8 +29,6 @@ impl HttpConnector for TlsConnector {
type Connection = TlsConnection;
type Error = io::Error;
const DEFAULT_PORT: u16 = 443;
fn connect(
&mut self,
remote: &SocketAddr,
@@ -59,6 +57,10 @@ impl HttpConnector for TlsConnector {
fn supports_scheme(&self, scheme: &str) -> bool {
scheme == "https"
}
fn default_port(&self, _scheme: &str) -> u16 {
443
}
}
impl HttpConnection for TlsConnection {
+15 -1
View File
@@ -66,7 +66,9 @@ impl<C: HttpConnector> HttpClient<C> {
}
let host = url.host().ok_or(HttpError::MalformedUrl)?;
let port = url.port_u16().unwrap_or(C::DEFAULT_PORT);
let port = url
.port_u16()
.unwrap_or(self.connector.default_port(scheme));
let request = if let Some(path) = url.path_and_query() {
request.uri(path.as_str())
@@ -127,6 +129,18 @@ impl Default for HttpClient<TcpConnector> {
}
impl<C: HttpConnector, U: TryInto<Uri>> HttpRequestBuilder<'_, C, U> {
pub fn header<V: TryInto<HeaderValue>>(
mut self,
name: HeaderName,
value: V,
) -> Result<Self, HttpError<C::Error>>
where
http::Error: From<V::Error>,
{
self.builder = self.builder.header(name, value);
Ok(self)
}
pub fn call<R: HttpBody>(self) -> Result<HttpResponse<C::Connection, R>, HttpError<C::Error>> {
self.client.send(self.url, self.builder, ())
}
+136 -30
View File
@@ -1,5 +1,6 @@
use std::{
collections::BTreeMap,
collections::{BTreeMap, VecDeque},
os::fd::{AsRawFd, RawFd},
process::ExitCode,
sync::{
atomic::{AtomicBool, Ordering},
@@ -10,23 +11,36 @@ use std::{
use cross::signal::set_sigint_handler;
use crate::{
application::window::{EventOutcome, WindowEventHandler, WindowHandler},
error::Error,
event::{Event, EventData, WindowEvent, WindowManagementEvent},
message::ClientMessage,
message::{ClientMessage, CreateWindowInfo},
};
use self::{connection::Connection, window::Window};
pub mod connection;
pub mod surface;
pub mod window;
pub trait WindowManagementEventHandler = Fn(WindowManagementEvent) -> bool;
pub trait ApplicationHandler<E = ()> {
fn on_window_management_event(
&mut self,
application: &mut Application<E>,
event: WindowManagementEvent,
) -> EventOutcome {
let _ = application;
let _ = event;
EventOutcome::None
}
}
pub struct Application<'a> {
pub struct Application<E = ()> {
pub(crate) connection: Arc<Mutex<Connection>>,
windows: BTreeMap<u32, Window<'a>>,
user_events: VecDeque<(u32, E)>,
window_management_event_handler: Box<dyn WindowManagementEventHandler>,
windows: BTreeMap<u32, Box<dyn WindowEventHandler<E>>>,
exit_after_close: Option<u32>,
}
static EXIT_SIGNAL: AtomicBool = AtomicBool::new(false);
@@ -35,7 +49,27 @@ fn sigint_handler() {
EXIT_SIGNAL.store(true, Ordering::Release);
}
impl<'a> Application<'a> {
impl<E> ApplicationHandler<E> for () {}
impl<E> Application<E> {
pub fn single_windowed<H: WindowHandler<UserEvent = E> + 'static>(
handler: H,
) -> Result<Self, Error> {
Self::single_windowed_with_info(Default::default(), handler)
}
pub fn single_windowed_with_info<H: WindowHandler<UserEvent = E> + 'static>(
info: CreateWindowInfo,
handler: H,
) -> Result<Self, Error> {
let mut application = Self::new()?;
let window = Window::new_with_info(&application, info, handler)?;
let id = window.window_id();
application.add_window(Box::new(window));
application.exit_after_close = Some(id);
Ok(application)
}
pub fn new() -> Result<Self, Error> {
set_sigint_handler(sigint_handler);
@@ -45,27 +79,29 @@ impl<'a> Application<'a> {
Ok(Self {
connection: Arc::new(Mutex::new(connection)),
windows: BTreeMap::new(),
window_management_event_handler: Box::new(|_| false),
exit_after_close: None,
user_events: VecDeque::new(),
})
}
pub fn push_window_event(&mut self, window_id: u32, event: E) {
self.user_events.push_back((window_id, event));
}
pub fn subscribe_to_window_management_events(&mut self) -> Result<(), Error> {
let mut connection = self.connection.lock().unwrap();
connection.send(&ClientMessage::SubscribeToWindowManagement)?;
Ok(())
}
pub fn set_window_management_event_handler<H: WindowManagementEventHandler + 'static>(
&mut self,
handler: H,
) {
self.window_management_event_handler = Box::new(handler);
pub fn add_window(&mut self, window: Box<dyn WindowEventHandler<E>>) {
let id = window.window_id();
assert!(!self.windows.contains_key(&id));
self.windows.insert(id, window);
}
pub fn add_window(&mut self, window: Window<'a>) {
assert!(!self.windows.contains_key(&window.id()));
self.windows.insert(window.id(), window);
pub fn main_window_id(&self) -> Option<u32> {
self.exit_after_close
}
pub fn is_running(&self) -> bool {
@@ -74,21 +110,54 @@ impl<'a> Application<'a> {
fn run_inner(mut self) -> Result<ExitCode, Error> {
while self.is_running() {
self.wait_events()?;
if self.wait_events(&mut ())? {
break;
}
}
Ok(ExitCode::SUCCESS)
}
pub fn handle_event(&mut self, event: Event) -> Result<(), Error> {
pub fn handle_event<H: ApplicationHandler<E>>(
&mut self,
handler: &mut H,
event: Event,
) -> Result<bool, Error> {
match event.data {
EventData::ServerExit => {
log::warn!("Window manager has exited");
return Ok(true);
}
EventData::WindowManagementEvent(event) => {
if (self.window_management_event_handler)(event) {
self.redraw()?;
match handler.on_window_management_event(self, event) {
EventOutcome::None => (),
EventOutcome::Redraw => {
self.redraw()?;
}
EventOutcome::Destroy => {
return Ok(true);
}
}
}
EventData::WindowEvent(window_id, ev) => {
if let Some(window) = self.windows.get_mut(&window_id) {
window.handle_event(ev)?;
if window.on_window_event(ev)? == EventOutcome::Destroy {
self.connection
.lock()
.unwrap()
.send(&ClientMessage::DestroyWindow(window_id))
.ok();
self.windows.remove(&window_id);
if self
.exit_after_close
.map(|id| id == window_id)
.unwrap_or(false)
{
log::info!("Last window closed, exiting");
return Ok(true);
}
}
} else {
log::warn!("Unknown window ID received: {window_id}");
}
@@ -96,28 +165,63 @@ impl<'a> Application<'a> {
_ => (),
}
Ok(())
Ok(false)
}
fn handle_user_event(&mut self, window_id: u32, event: E) -> Result<bool, Error> {
if let Some(window) = self.windows.get_mut(&window_id) {
Ok(window.on_user_event(event)? == EventOutcome::Destroy)
} else {
log::warn!("Invalid window ID for user event: {window_id}");
Ok(false)
}
}
pub fn redraw(&mut self) -> Result<(), Error> {
for (_, window) in self.windows.iter_mut() {
// Inject RedrawRequested
window.handle_event(WindowEvent::RedrawRequested)?;
window.on_window_event(WindowEvent::RedrawRequested)?;
}
Ok(())
}
pub fn wait_events(&mut self) -> Result<(), Error> {
pub fn handle_user_events(&mut self) -> Result<bool, Error> {
let mut exit = false;
while let Some((window_id, event)) = self.user_events.pop_front() {
exit |= self.handle_user_event(window_id, event)?;
}
Ok(exit)
}
pub fn wait_events<H: ApplicationHandler<E>>(
&mut self,
handler: &mut H,
) -> Result<bool, Error> {
if self.handle_user_events()? {
return Ok(true);
}
let event = {
let mut connection = self.connection.lock().unwrap();
connection.receive_event()?
};
self.handle_event(event)
self.handle_event(handler, event)
}
pub fn poll_events(&mut self) -> Result<(), Error> {
pub fn poll_fd(&self) -> RawFd {
self.connection.lock().unwrap().as_raw_fd()
}
pub fn poll_events<H: ApplicationHandler<E>>(
&mut self,
handler: &mut H,
) -> Result<bool, Error> {
loop {
if self.handle_user_events()? {
return Ok(true);
}
let event = {
let mut connection = self.connection.lock().unwrap();
match connection.poll_event() {
@@ -127,9 +231,11 @@ impl<'a> Application<'a> {
}
};
self.handle_event(event)?;
if self.handle_event(handler, event)? {
return Ok(true);
}
}
Ok(())
Ok(false)
}
pub fn run(self) -> ExitCode {
@@ -147,7 +253,7 @@ impl<'a> Application<'a> {
}
}
impl Drop for Application<'_> {
impl<E> Drop for Application<E> {
fn drop(&mut self) {
let mut conn = self.connection.lock().unwrap();
for (window_id, _) in self.windows.iter() {
@@ -0,0 +1,213 @@
use std::{
ops::{Deref, DerefMut},
os::fd::{AsRawFd, OwnedFd, RawFd},
};
use cross::mem::FileMapping;
use crate::{
error::Error,
geometry::{Point, Rectangle},
};
pub trait SurfaceStorage: DerefMut<Target = [u32]> {
fn resize(&mut self, width: u32, height: u32) -> Result<(), Error>;
}
pub trait SurfaceLike: DerefMut<Target = [u32]> {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn copy_rect_from<S: SurfaceLike>(
&mut self,
source: &S,
source_rect: Rectangle<u32>,
destination: Point<u32>,
) -> Option<Rectangle<u32>> {
let display_w = self.width();
let h = self.height();
let source_rect = source_rect.min(display_w, h)?;
let destination_rect = Rectangle {
x: destination.x,
y: destination.y,
w: source_rect.w,
h: source_rect.h,
};
let destination_rect = destination_rect.min(display_w, h)?;
let w = source_rect.w.min(destination_rect.w);
let h = source_rect.h.min(destination_rect.h);
let damage = Rectangle {
x: destination_rect.x,
y: destination_rect.y,
w,
h,
};
for iy in 0..h {
// TODO use stride here
let src_si = (iy + source_rect.y) * source.width() + source_rect.x;
let src_di = src_si + w;
let dst_si = (iy + destination_rect.y) * display_w + destination_rect.x;
let dst_di = dst_si + w;
self[dst_si as usize..dst_di as usize]
.copy_from_slice(&source[src_si as usize..src_di as usize]);
}
Some(damage)
}
fn fill_rect(&mut self, rect: Rectangle<u32>, color: u32) {
let w = self.width();
let sx = rect.x.min(w);
let dx = (rect.x + rect.w).min(w);
if dx <= sx {
return;
}
for iy in 0..rect.h {
let y = rect.y + iy;
if y >= self.height() {
break;
}
let si = (y * w + sx) as usize;
let di = (y * w + dx) as usize;
self[si..di].fill(color);
}
}
fn get_pixel(&self, x: u32, y: u32) -> u32 {
let i = (y * self.width() + x) as usize;
self[i]
}
fn set_pixel(&mut self, x: u32, y: u32, color: u32) {
let i = (y * self.width() + x) as usize;
self[i] = color;
}
}
pub struct MappedSurfaceStorage {
mapping: FileMapping,
data: *mut u32,
len: usize,
}
pub struct Surface<S: SurfaceStorage = MappedSurfaceStorage> {
storage: S,
width: u32,
height: u32,
}
impl<S: SurfaceStorage> Surface<S> {
pub fn resize(&mut self, width: u32, height: u32) -> Result<(), Error> {
self.storage.resize(width, height)?;
self.width = width;
self.height = height;
Ok(())
}
}
impl<S: SurfaceStorage> SurfaceLike for Surface<S> {
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
}
impl Surface<MappedSurfaceStorage> {
pub fn from_shared_memory(
shm_fd: OwnedFd,
len: usize,
width: u32,
height: u32,
) -> Result<Self, Error> {
let mut mapping = FileMapping::map(shm_fd, len, true)?;
let data = mapping.as_mut_ptr().cast();
let storage = MappedSurfaceStorage { data, mapping, len };
Ok(Self {
storage,
width,
height,
})
}
}
impl Surface<Vec<u32>> {
pub fn new_vec(width: u32, height: u32) -> Self {
let len = (width * height) as usize;
let storage = vec![0; len];
Self {
storage,
width,
height,
}
}
}
impl<S: AsRawFd + SurfaceStorage> AsRawFd for Surface<S> {
fn as_raw_fd(&self) -> RawFd {
self.storage.as_raw_fd()
}
}
impl<S: SurfaceStorage> Deref for Surface<S> {
type Target = [u32];
fn deref(&self) -> &Self::Target {
&self.storage[..(self.width * self.height) as usize]
}
}
impl<S: SurfaceStorage> DerefMut for Surface<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.storage[..(self.width * self.height) as usize]
}
}
impl Deref for MappedSurfaceStorage {
type Target = [u32];
fn deref(&self) -> &Self::Target {
unsafe { core::slice::from_raw_parts(self.data, self.len) }
}
}
impl DerefMut for MappedSurfaceStorage {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { core::slice::from_raw_parts_mut(self.data, self.len) }
}
}
impl AsRawFd for MappedSurfaceStorage {
fn as_raw_fd(&self) -> RawFd {
self.mapping.as_raw_fd()
}
}
impl SurfaceStorage for MappedSurfaceStorage {
fn resize(&mut self, width: u32, height: u32) -> Result<(), Error> {
let len = (width * height * 4) as usize;
if len > self.len {
panic!("TODO: Handle SHM resize");
}
Ok(())
}
}
impl SurfaceStorage for Vec<u32> {
fn resize(&mut self, width: u32, height: u32) -> Result<(), Error> {
let new_len = (width * height) as usize;
Vec::resize(self, new_len, 0);
Ok(())
}
}
+118 -127
View File
@@ -1,51 +1,84 @@
use std::sync::{Arc, Mutex};
use cross::mem::FileMapping;
use crate::{
application::{connection::Connection, surface::Surface, Application},
error::Error,
event::{EventData, KeyEvent, KeyInput, WindowEvent},
message::{ClientMessage, CreateWindowInfo},
};
use super::{connection::Connection, Application};
pub trait WindowEventHandler<E> {
fn on_window_event(&mut self, ev: WindowEvent) -> Result<EventOutcome, Error>;
fn on_user_event(&mut self, ev: E) -> Result<EventOutcome, Error>;
fn window_id(&self) -> u32;
}
pub trait OnCloseRequested = Fn() -> EventOutcome;
pub trait OnKeyInput = Fn(KeyInput) -> EventOutcome;
pub trait OnResized = Fn(u32, u32) -> EventOutcome;
pub trait OnFocusChanged = Fn(bool) -> EventOutcome;
pub trait OnKeyPressed = Fn(KeyEvent) -> EventOutcome;
pub trait OnKeyReleased = Fn(KeyEvent) -> EventOutcome;
pub trait OnRedrawRequested = Fn(&mut [u32], u32, u32);
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EventOutcome {
None,
Redraw,
Destroy,
}
pub struct Window<'a> {
pub trait WindowHandler {
type UserEvent = ();
fn on_close_requested(&mut self) -> EventOutcome {
EventOutcome::Destroy
}
fn on_key_input(&mut self, input: KeyInput) -> EventOutcome {
let _ = input;
EventOutcome::None
}
fn on_key_pressed(&mut self, key: KeyEvent) -> EventOutcome {
let _ = key;
EventOutcome::None
}
fn on_key_released(&mut self, key: KeyEvent) -> EventOutcome {
let _ = key;
EventOutcome::None
}
fn on_focus_changed(&mut self, focused: bool) -> EventOutcome {
let _ = focused;
EventOutcome::None
}
fn on_resized(&mut self, width: u32, height: u32) -> EventOutcome {
let _ = (width, height);
EventOutcome::Redraw
}
fn on_redraw_requested(&mut self, surface: &mut Surface) {
surface.fill(0xFF888888);
}
fn on_user_event(&mut self, event: Self::UserEvent) -> EventOutcome {
let _ = event;
EventOutcome::None
}
}
pub struct Window<H: WindowHandler> {
connection: Arc<Mutex<Connection>>,
window_id: u32,
surface_mapping: FileMapping,
surface_data: &'a mut [u32],
width: u32,
height: u32,
focused: bool,
on_close_requested: Box<dyn OnCloseRequested>,
on_redraw_requested: Box<dyn OnRedrawRequested>,
on_key_input: Box<dyn OnKeyInput>,
on_resized: Box<dyn OnResized>,
on_focus_changed: Box<dyn OnFocusChanged>,
on_key_pressed: Box<dyn OnKeyPressed>,
on_key_released: Box<dyn OnKeyReleased>,
surface: Surface,
handler: H,
}
impl<'a> Window<'a> {
pub fn new_with_info(application: &Application, info: CreateWindowInfo) -> Result<Self, Error> {
impl<H: WindowHandler> Window<H> {
pub fn new_with_info(
application: &Application<H::UserEvent>,
info: CreateWindowInfo,
handler: H,
) -> Result<Self, Error> {
let mut connection = application.connection.lock().unwrap();
connection.send(&ClientMessage::CreateWindow(info))?;
@@ -59,128 +92,40 @@ impl<'a> Window<'a> {
})?;
assert_eq!(create_info.surface_stride % 4, 0);
let mut surface_mapping =
FileMapping::map(surface_shm_fd, create_info.surface_mapping_size, true)?;
let surface_data = unsafe {
std::slice::from_raw_parts_mut(
surface_mapping.as_mut_ptr() as *mut u32,
(create_info.width * create_info.height) as usize,
)
};
let surface = Surface::from_shared_memory(
surface_shm_fd,
create_info.surface_mapping_size,
create_info.width,
create_info.height,
)?;
log::info!("Created window: #{}", create_info.window_id);
Ok(Self {
connection: application.connection.clone(),
window_id: create_info.window_id,
width: create_info.width,
height: create_info.height,
surface_mapping,
surface_data,
focused: false,
on_close_requested: Box::new(|| {
// Do nothing
EventOutcome::Destroy
}),
on_redraw_requested: Box::new(|dt, _, _| {
dt.fill(0xFF888888);
}),
on_key_input: Box::new(|_ev| EventOutcome::None),
on_key_pressed: Box::new(|_| EventOutcome::None),
on_key_released: Box::new(|_| EventOutcome::None),
on_resized: Box::new(|_w, _h| EventOutcome::Redraw),
on_focus_changed: Box::new(|_| EventOutcome::None),
surface,
handler,
})
}
pub fn new(application: &Application) -> Result<Self, Error> {
Self::new_with_info(application, Default::default())
pub fn new(application: &Application<H::UserEvent>, handler: H) -> Result<Self, Error> {
Self::new_with_info(application, Default::default(), handler)
}
pub fn id(&self) -> u32 {
self.window_id
}
pub fn set_on_key_input<H: OnKeyInput + 'static>(&mut self, handler: H) {
self.on_key_input = Box::new(handler);
}
pub fn set_on_key_pressed<H: OnKeyPressed + 'static>(&mut self, handler: H) {
self.on_key_pressed = Box::new(handler);
}
pub fn set_on_key_released<H: OnKeyReleased + 'static>(&mut self, handler: H) {
self.on_key_released = Box::new(handler);
}
pub fn set_on_resized<H: OnResized + 'static>(&mut self, handler: H) {
self.on_resized = Box::new(handler);
}
pub fn set_on_redraw_requested<H: OnRedrawRequested + 'static>(&mut self, handler: H) {
self.on_redraw_requested = Box::new(handler);
}
pub fn set_on_focus_changed<H: OnFocusChanged + 'static>(&mut self, handler: H) {
self.on_focus_changed = Box::new(handler);
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn redraw(&mut self) -> Result<(), Error> {
(self.on_redraw_requested)(self.surface_data, self.width, self.height);
fn redraw(&mut self) -> Result<(), Error> {
self.handler.on_redraw_requested(&mut self.surface);
// Blit
self.blit_rect(0, 0, self.width, self.height)
}
pub fn handle_event(&mut self, ev: WindowEvent) -> Result<EventOutcome, Error> {
let outcome = match ev {
WindowEvent::RedrawRequested => {
self.redraw()?;
EventOutcome::None
}
WindowEvent::Resized { width, height } => {
self.width = width;
self.height = height;
let new_surface_data = unsafe {
std::slice::from_raw_parts_mut(
self.surface_mapping.as_mut_ptr() as *mut u32,
(width * height) as usize,
)
};
self.surface_data = new_surface_data;
(self.on_resized)(width, height)
}
WindowEvent::KeyPressed(key) => (self.on_key_pressed)(key),
WindowEvent::KeyReleased(key) => (self.on_key_released)(key),
WindowEvent::KeyInput(input) => (self.on_key_input)(input),
WindowEvent::FocusChanged(focused) => {
self.focused = focused;
(self.on_focus_changed)(focused)
}
WindowEvent::CloseRequested => (self.on_close_requested)(),
_ => EventOutcome::None,
};
if outcome == EventOutcome::Redraw {
self.redraw()?;
}
Ok(outcome)
}
fn blit_rect(&mut self, x: u32, y: u32, w: u32, h: u32) -> Result<(), Error> {
// Clip to self bounds
let x = x.min(self.width);
@@ -204,3 +149,49 @@ impl<'a> Window<'a> {
Ok(())
}
}
impl<H: WindowHandler> WindowEventHandler<H::UserEvent> for Window<H> {
fn window_id(&self) -> u32 {
self.window_id
}
fn on_window_event(&mut self, ev: WindowEvent) -> Result<EventOutcome, Error> {
let outcome = match ev {
WindowEvent::RedrawRequested => {
self.redraw()?;
EventOutcome::None
}
WindowEvent::Resized { width, height } => {
self.width = width;
self.height = height;
self.surface.resize(width, height)?;
self.handler.on_resized(width, height)
}
WindowEvent::KeyPressed(key) => self.handler.on_key_pressed(key),
WindowEvent::KeyReleased(key) => self.handler.on_key_released(key),
WindowEvent::KeyInput(input) => self.handler.on_key_input(input),
WindowEvent::FocusChanged(focused) => {
self.focused = focused;
self.handler.on_focus_changed(focused)
}
WindowEvent::CloseRequested => self.handler.on_close_requested(),
_ => EventOutcome::None,
};
if outcome == EventOutcome::Redraw {
self.redraw()?;
}
Ok(outcome)
}
fn on_user_event(&mut self, ev: H::UserEvent) -> Result<EventOutcome, Error> {
let outcome = self.handler.on_user_event(ev);
if outcome == EventOutcome::Redraw {
self.redraw()?;
}
Ok(outcome)
}
}
+2
View File
@@ -8,4 +8,6 @@ pub enum Error {
IoError(#[from] io::Error),
#[error("Communication error: {0:?}")]
CommunicationError(#[from] uipc::Error),
#[error("Window server has terminated")]
ServerTerminated,
}
+1 -2
View File
@@ -1,7 +1,5 @@
use std::os::fd::OwnedFd;
// pub use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
use serde::{Deserialize, Serialize};
use crate::input::Key;
@@ -66,6 +64,7 @@ pub enum EventData {
// Server events
KeyboardKey(KeyboardKeyEvent),
RedrawRequested,
ServerExit,
// Window events
WindowEvent(u32, WindowEvent),
+81
View File
@@ -0,0 +1,81 @@
use std::{
cmp,
ops::{Add, Sub},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Point<T> {
pub x: T,
pub y: T,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rectangle<T: Copy> {
pub x: T,
pub y: T,
pub w: T,
pub h: T,
}
impl<T: Copy> Rectangle<T> {
pub fn origin(&self) -> Point<T> {
Point {
x: self.x,
y: self.y,
}
}
pub fn min(self, w: T, h: T) -> Option<Self>
where
T: Ord + Add<Output = T> + Sub<Output = T>,
{
let sx = self.x.min(w);
let dx = (self.x + self.w).min(w);
let sy = self.y.min(h);
let dy = (self.y + self.h).min(h);
if sx < dx && sy < dy {
Some(Self {
x: sx,
y: sy,
w: dx - sx,
h: dy - sy,
})
} else {
None
}
}
pub fn intersect(&self, other: &Self) -> Option<Self>
where
T: Ord + Add<Output = T> + Sub<Output = T>,
{
let ax1 = self.x;
let ay1 = self.y;
let ax2 = self.x + self.w;
let ay2 = self.y + self.h;
let bx1 = other.x;
let by1 = other.y;
let bx2 = other.x + other.w;
let by2 = other.y + other.h;
let ix1 = cmp::max(ax1, bx1);
let iy1 = cmp::max(ay1, by1);
let ix2 = cmp::min(ax2, bx2);
let iy2 = cmp::min(ay2, by2);
if ix2 <= ix1 || iy2 <= iy1 {
None
} else {
let iw = ix2 - ix1;
let ih = iy2 - iy1;
Some(Rectangle {
x: ix1,
y: iy1,
w: iw,
h: ih,
})
}
}
}
-1
View File
@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Key {
Char(u8),
+3 -2
View File
@@ -1,5 +1,5 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
#![cfg_attr(feature = "client", feature(trait_alias))]
#![cfg_attr(feature = "client", feature(trait_alias, associated_type_defaults))]
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
pub const CHANNEL_NAME: &str = "/colors.sock";
@@ -12,5 +12,6 @@ pub mod application;
pub mod error;
pub mod event;
pub mod message;
pub mod geometry;
pub mod input;
pub mod message;
+1 -1
View File
@@ -49,7 +49,7 @@ pub enum EraseInDisplay {
SavedLines,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum CursorStyle {
Block(bool),
Bar(bool),

Some files were not shown because too many files have changed in this diff Show More