Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 895fa74c4d | |||
| 0c9eb93d6b | |||
| d52eaabe25 | |||
| dbbfb39164 | |||
| 4c13a90799 | |||
| c425130aca | |||
| 622d11ee05 | |||
| c63fa5517a | |||
| cac1350b13 | |||
| 11ebda95b1 | |||
| 05c73735ba | |||
| c59be9de5c | |||
| bc795904bd | |||
| 5a0a22bd69 | |||
| b487e51fc4 | |||
| 9b172a6b08 | |||
| 15f33c33dc | |||
| dbcefe14fc | |||
| cf93403375 | |||
| 970e2d796c | |||
| 8616a62fec | |||
| d7ec7f29c2 |
Generated
+534
-367
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
||||
use core::sync::atomic::{AtomicU16, Ordering};
|
||||
|
||||
use alloc::{collections::BTreeMap, format, sync::Arc};
|
||||
use alloc::{collections::BTreeMap, sync::Arc};
|
||||
use libk_util::{queue::UnboundedMpmcQueue, sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
|
||||
|
||||
use crate::{
|
||||
address::UsbBusAddress,
|
||||
class_driver,
|
||||
device::{UsbDevice, UsbDeviceAccess},
|
||||
device::UsbDeviceAccess,
|
||||
sysfs::{self, UsbBusKObject},
|
||||
UsbHostController,
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ impl UsbInterfaceDriver for UsbHidKeyboardDriver {
|
||||
async fn run(
|
||||
self: Arc<Self>,
|
||||
device: Arc<UsbDeviceAccess>,
|
||||
interface: UsbInterfaceInfo,
|
||||
_interface: UsbInterfaceInfo,
|
||||
) -> Result<(), UsbError> {
|
||||
// TODO not sure whether to use boot protocol (easy) or GetReport
|
||||
let config = device.current_configuration().unwrap();
|
||||
|
||||
@@ -16,7 +16,7 @@ impl UsbInterfaceDriver for UsbHidMouseDriver {
|
||||
async fn run(
|
||||
self: Arc<Self>,
|
||||
device: Arc<UsbDeviceAccess>,
|
||||
interface: UsbInterfaceInfo,
|
||||
_interface: UsbInterfaceInfo,
|
||||
) -> Result<(), UsbError> {
|
||||
let config = device.current_configuration().unwrap();
|
||||
|
||||
@@ -26,8 +26,6 @@ impl UsbInterfaceDriver for UsbHidMouseDriver {
|
||||
.await?;
|
||||
|
||||
let mut buffer = [0; 16];
|
||||
let mut button_state = 0;
|
||||
|
||||
loop {
|
||||
let len = pipe.read(&mut buffer).await?;
|
||||
if len < 4 {
|
||||
@@ -50,8 +48,8 @@ impl UsbInterfaceDriver for UsbHidMouseDriver {
|
||||
|
||||
fn probe(
|
||||
&self,
|
||||
device: &UsbDeviceAccess,
|
||||
interface: &UsbInterfaceInfo,
|
||||
_device: &UsbDeviceAccess,
|
||||
_interface: &UsbInterfaceInfo,
|
||||
class: UsbInterfaceClass,
|
||||
) -> bool {
|
||||
class
|
||||
|
||||
@@ -1,42 +1,16 @@
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use async_trait::async_trait;
|
||||
use libk::task::runtime;
|
||||
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
|
||||
|
||||
use crate::{
|
||||
address::UsbInterfaceAddress,
|
||||
device::UsbDeviceAccess,
|
||||
error::UsbError,
|
||||
info::{UsbInterfaceInfo, CLASS_FROM_INTERFACE},
|
||||
pipe::control::{ControlTransferSetup, UsbClassSpecificRequest},
|
||||
address::UsbInterfaceAddress, device::UsbDeviceAccess, error::UsbError, info::UsbInterfaceInfo,
|
||||
};
|
||||
|
||||
// use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
// use async_trait::async_trait;
|
||||
// use libk::task::runtime;
|
||||
// use libk_util::sync::spin_rwlock::IrqSafeRwLock;
|
||||
//
|
||||
// use crate::{
|
||||
// device::UsbDeviceAccess,
|
||||
// error::UsbError,
|
||||
// info::{UsbDeviceClass, UsbDeviceProtocol},
|
||||
// };
|
||||
//
|
||||
pub mod hid_keyboard;
|
||||
pub mod hid_mouse;
|
||||
// pub mod mass_storage;
|
||||
//
|
||||
// #[derive(Debug)]
|
||||
// pub struct UsbClassInfo {
|
||||
// pub class: UsbDeviceClass,
|
||||
// pub subclass: u8,
|
||||
// pub protocol: UsbDeviceProtocol,
|
||||
// pub device_protocol_number: u8,
|
||||
// pub interface_protocol_number: u8,
|
||||
// }
|
||||
//
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct UsbInterfaceClass {
|
||||
pub class: u8,
|
||||
@@ -61,80 +35,6 @@ pub trait UsbInterfaceDriver: Send + Sync {
|
||||
) -> 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);
|
||||
// };
|
||||
//
|
||||
// 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
|
||||
// // }
|
||||
// // e => e,
|
||||
// // }
|
||||
// // })
|
||||
// // .map_err(UsbError::SystemError)?;
|
||||
// // }
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
async fn setup_interface(
|
||||
device: &Arc<UsbDeviceAccess>,
|
||||
address: UsbInterfaceAddress,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::{
|
||||
communication::UsbDirection,
|
||||
device::{self, UsbSpeed},
|
||||
error::UsbError,
|
||||
info::UsbEndpointType,
|
||||
communication::UsbDirection, device::UsbSpeed, error::UsbError, info::UsbEndpointType,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
use core::{
|
||||
fmt,
|
||||
ops::{Deref, Sub},
|
||||
};
|
||||
use core::ops::Deref;
|
||||
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
format,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
vec::Vec,
|
||||
};
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use async_trait::async_trait;
|
||||
use libk::error::Error;
|
||||
use libk_util::{
|
||||
sync::spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard},
|
||||
OneTimeInit,
|
||||
};
|
||||
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
|
||||
|
||||
use crate::{
|
||||
address::UsbBusAddress,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use core::fmt;
|
||||
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use yggdrasil_abi::primitive_enum;
|
||||
|
||||
use crate::communication::UsbDirection;
|
||||
|
||||
@@ -29,36 +26,10 @@ pub enum UsbUsageType {
|
||||
Reserved,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
// pub enum UsbVersion {
|
||||
// Usb11,
|
||||
// Usb20,
|
||||
// Usb21,
|
||||
// Usb30,
|
||||
// Usb31,
|
||||
// Usb32,
|
||||
// }
|
||||
|
||||
pub const CLASS_FROM_INTERFACE: u8 = 0x00;
|
||||
pub const CLASS_HID: u8 = 0x03;
|
||||
pub const CLASS_MASS_STORAGE: u8 = 0x08;
|
||||
|
||||
// primitive_enum! {
|
||||
// pub enum UsbDeviceClass: u8 {
|
||||
// FromInterface = 0x00,
|
||||
// Hid = 0x03,
|
||||
// MassStorage = 0x08,
|
||||
// Unknown = 0xFF,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// primitive_enum! {
|
||||
// pub enum UsbDeviceProtocol: u8 {
|
||||
// FromInterface = 0x00,
|
||||
// Unknown = 0xFF,
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UsbInterfaceInfo {
|
||||
pub name: String,
|
||||
@@ -67,13 +38,6 @@ pub struct UsbInterfaceInfo {
|
||||
pub interface_class: u8,
|
||||
pub interface_subclass: u8,
|
||||
pub interface_protocol: u8,
|
||||
// pub name: String,
|
||||
// pub number: u8,
|
||||
|
||||
// pub interface_class: UsbDeviceClass,
|
||||
// pub interface_subclass: u8,
|
||||
// pub interface_protocol: UsbDeviceProtocol,
|
||||
// pub interface_protocol_number: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -112,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,14 +1,6 @@
|
||||
use core::{
|
||||
any::Any,
|
||||
marker::PhantomData,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use core::{any::Any, marker::PhantomData, sync::atomic::AtomicBool};
|
||||
|
||||
use alloc::{
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
vec::Vec,
|
||||
};
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
@@ -121,11 +113,12 @@ impl<T: IntegerAttributeValue, V: IntegerAttributeOps<T>> RegularImpl
|
||||
|
||||
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!()
|
||||
// let instance = instance.ok_or(Error::InvalidFile)?;
|
||||
// let instance = instance
|
||||
// .downcast_ref::<StringAttributeState>()
|
||||
// .ok_or(Error::InvalidFile)?;
|
||||
|
||||
// if instance.modified.load(Ordering::Acquire) {
|
||||
// let value = instance.value.read();
|
||||
@@ -167,9 +160,9 @@ impl<T: IntegerAttributeValue, V: IntegerAttributeOps<T>> RegularImpl
|
||||
fn write(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
instance: Option<&InstanceData>,
|
||||
pos: u64,
|
||||
buf: &[u8],
|
||||
_instance: Option<&InstanceData>,
|
||||
_pos: u64,
|
||||
_buf: &[u8],
|
||||
) -> Result<usize, Error> {
|
||||
todo!()
|
||||
// if !V::WRITEABLE {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+479
-292
File diff suppressed because it is too large
Load Diff
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 |
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
/bin/colors-bar
|
||||
@@ -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.
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
RUST_LOG=info cargo run --release --bin colors-bar
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd ../term
|
||||
RUST_LOG=info cargo run --release
|
||||
@@ -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 }
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::attr::Color;
|
||||
use crate::attribute::Color;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FontConfig {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -118,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>;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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),
|
||||
|
||||
@@ -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,6 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Key {
|
||||
Char(u8),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -49,7 +49,7 @@ pub enum EraseInDisplay {
|
||||
SavedLines,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum CursorStyle {
|
||||
Block(bool),
|
||||
Bar(bool),
|
||||
|
||||
@@ -159,7 +159,13 @@ impl CsiState {
|
||||
// CSI Ps e Line Position Relative [rows] (default = [row+1,column])
|
||||
('e', '\0') => todo!(),
|
||||
// CSI Ps ; Ps f Horizontal and Vertical Position [row;column] (default=[1,1])
|
||||
('f', '\0') => todo!(),
|
||||
('f', '\0') => (
|
||||
Some(ControlSequence::SetCursorPosition(
|
||||
args.get_or(0, 1),
|
||||
args.get_or(1, 1),
|
||||
)),
|
||||
true,
|
||||
),
|
||||
// CSI Ps g Tab Clear
|
||||
// Ps = 0 ⇒ Clear Current Column (default)
|
||||
// Ps = 3 ⇒ Clear All
|
||||
|
||||
@@ -84,7 +84,7 @@ impl RawTerminal for Stdout {
|
||||
}
|
||||
|
||||
fn raw_clear_line(&mut self, what: u32) -> io::Result<()> {
|
||||
write!(self, "\x1B[{}K", what)
|
||||
write!(self, "\x1B[{what}K")
|
||||
}
|
||||
|
||||
fn raw_report_cursor_position(&mut self) -> io::Result<()> {
|
||||
@@ -96,20 +96,9 @@ impl RawTerminal for Stdout {
|
||||
}
|
||||
|
||||
fn raw_set_cursor_style(&mut self, style: CursorStyle) -> io::Result<()> {
|
||||
// TODO term does not support spaces in ctl-seqs
|
||||
#[cfg(not(target_os = "yggdrasil"))]
|
||||
{
|
||||
match style {
|
||||
CursorStyle::Default => self.write_all(b"\x1B[0 q")?,
|
||||
CursorStyle::Line => self.write_all(b"\x1B[6 q")?,
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "yggdrasil")]
|
||||
{
|
||||
match style {
|
||||
CursorStyle::Default => self.write_all(b"\x1B[0q")?,
|
||||
CursorStyle::Line => self.write_all(b"\x1B[6q")?,
|
||||
}
|
||||
match style {
|
||||
CursorStyle::Default => self.write_all(b"\x1B[0 q")?,
|
||||
CursorStyle::Line => self.write_all(b"\x1B[6 q")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::{
|
||||
fs::{self, ReadDir},
|
||||
fs::{self},
|
||||
io,
|
||||
path::PathBuf,
|
||||
process::ExitCode,
|
||||
};
|
||||
|
||||
struct BusAddress {
|
||||
@@ -11,6 +10,7 @@ struct BusAddress {
|
||||
}
|
||||
|
||||
struct Device {
|
||||
#[allow(unused)]
|
||||
path: PathBuf,
|
||||
|
||||
address: BusAddress,
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
collections::HashSet,
|
||||
io::{self, Read, Write},
|
||||
net::SocketAddr,
|
||||
os::fd::{self, AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
||||
os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
||||
path::PathBuf,
|
||||
process::{self, Command, ExitCode, Stdio},
|
||||
str::FromStr,
|
||||
|
||||
Reference in New Issue
Block a user