colors: rewrite colors, hosted testing support

This commit is contained in:
Mark Poliakov 2025-02-18 11:27:54 +02:00
parent 82175f342e
commit f605b0a80c
31 changed files with 3804 additions and 777 deletions

1940
userspace/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,10 @@ members = [
"rsh",
"lib/cross",
"crypt",
"lib/runtime"
, "lib/uipc"]
"lib/runtime",
"lib/uipc",
"lib/logsink"
]
exclude = ["dynload-program", "test-kernel-module", "lib/ygglibc"]
[workspace.dependencies]
@ -29,6 +31,7 @@ serde_json = "1.0.132"
serde = { version = "1.0.214", features = ["derive"] }
bytemuck = "1.19.0"
thiserror = "1.0.64"
env_logger = "0.11.5"
sha2 = { version = "0.10.8" }
chrono = { version = "0.4.31", default-features = false }
postcard = { version = "1.1.1", features = ["alloc"] }
@ -50,6 +53,7 @@ uipc.path = "lib/uipc"
yggdrasil-rt.path = "../lib/runtime"
yggdrasil-abi = { path = "../lib/abi", features = ["serde", "alloc", "bytemuck"] }
abi-serde = { path = "../lib/abi-serde" }
logsink.path = "lib/logsink"
[workspace.lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }

View File

@ -7,13 +7,25 @@ authors = ["Mark Poliakov <mark@alnyan.me>"]
[dependencies]
uipc.workspace = true
cross.workspace = true
logsink.workspace = true
libcolors = { workspace = true, default-features = false }
serde.workspace = true
thiserror.workspace = true
libcolors = { workspace = true, default-features = false }
log.workspace = true
clap.workspace = true
[target.'cfg(target_os = "yggdrasil")'.dependencies]
yggdrasil-abi.workspace = true
runtime.workspace = true
[target.'cfg(unix)'.dependencies]
winit = "0.30.9"
softbuffer = "0.4.6"
[dev-dependencies]
winit = "0.30.9"
softbuffer = "0.4.6"
[lints]
workspace = true

View File

@ -10,65 +10,11 @@ use std::{
use crate::error::Error;
pub struct Display<'a> {
#[allow(unused)]
mapping: FileMapping<'a>,
data: &'a mut [u32],
width: usize,
height: usize,
// TODO use those
_stride: usize,
_size: usize,
}
pub struct Point<T>(pub T, pub T);
impl Display<'_> {
pub fn open(framebuffer: impl AsRef<Path>) -> Result<Self, Error> {
let file = OpenOptions::new().open(framebuffer)?;
let mut buffer = [0; 128];
device::device_request::<device::AcquireDevice>(file.as_raw_fd(), &mut buffer, &())
.map_err(std::io::Error::from)?;
let framebuffer = device::device_request::<device::GetActiveFramebuffer>(
file.as_raw_fd(),
&mut buffer,
&(),
)
.map_err(std::io::Error::from)?;
let width = framebuffer.width as usize;
let height = framebuffer.height as usize;
let mut mapping = FileMapping::new(file, 0, framebuffer.size)?;
let data = unsafe {
std::slice::from_raw_parts_mut(mapping.as_mut_ptr() as *mut u32, width * height)
};
Ok(Self {
mapping,
data,
width,
height,
_stride: framebuffer.stride / size_of::<u32>(),
_size: framebuffer.size / size_of::<u32>(),
})
}
pub fn flush(&mut self) {
let mut buffer = [0; 0];
device::device_request::<device::FlushDisplay>(self.mapping.as_raw_fd(), &mut buffer, &()).ok();
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn fill(&mut self, color: u32) {
self.data.fill(color);
}

View File

@ -1,15 +1,9 @@
use std::{
fs::File,
io::Read,
os::fd::{AsRawFd, RawFd},
use libcolors::{
event::{KeyInput, KeyModifiers},
input::Key,
};
// use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
use libcolors::event::{KeyInput, KeyModifiers};
use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
use crate::error::Error;
pub struct KeyboardInput(File);
#[derive(Default)]
pub struct InputState {
@ -21,37 +15,15 @@ pub struct InputState {
ralt: bool,
}
impl KeyboardInput {
pub fn open() -> Result<Self, Error> {
let file = File::open("/dev/kbd")?;
Ok(Self(file))
}
pub fn as_poll_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
pub fn read_event(&mut self) -> Result<KeyboardKeyEvent, Error> {
let mut buf = [0; 4];
let len = self.0.read(&mut buf)?;
if len == 4 {
Ok(KeyboardKeyEvent::from_bytes(buf))
} else {
todo!()
}
}
}
impl InputState {
pub fn update(&mut self, key: KeyboardKey, state: bool) {
pub fn update(&mut self, key: Key, state: bool) {
match key {
KeyboardKey::LAlt => self.lalt = state,
KeyboardKey::RAlt => self.ralt = state,
KeyboardKey::LShift => self.lshift = state,
KeyboardKey::RShift => self.rshift = state,
KeyboardKey::LControl => self.lctrl = state,
KeyboardKey::RControl => self.rctrl = state,
Key::LAlt => self.lalt = state,
Key::RAlt => self.ralt = state,
Key::LShift => self.lshift = state,
Key::RShift => self.rshift = state,
Key::LControl => self.lctrl = state,
Key::RControl => self.rctrl = state,
_ => (),
}
}
@ -75,15 +47,15 @@ impl InputState {
}
}
pub fn make_input(&self, key: KeyboardKey) -> KeyInput {
pub fn make_input(&self, key: Key) -> KeyInput {
let modifiers = self.modifiers();
let input = match (key, modifiers) {
(KeyboardKey::Char(ch), KeyModifiers::NONE) => Some(ch as _),
(Key::Char(ch), KeyModifiers::NONE) => Some(ch as _),
// TODO proper shift key translation
(KeyboardKey::Char(ch), KeyModifiers::SHIFT) => Some(Self::translate_shift(ch) as _),
(KeyboardKey::Tab, KeyModifiers::NONE) => Some('\t'),
(KeyboardKey::Enter, KeyModifiers::NONE) => Some('\n'),
(Key::Char(ch), KeyModifiers::SHIFT) => Some(Self::translate_shift(ch) as _),
// (KeyboardKey::Tab, KeyModifiers::NONE) => Some('\t'),
(Key::Enter, KeyModifiers::NONE) => Some('\n'),
(_, _) => None,
};

View File

@ -1,205 +1,93 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
#![feature(map_many_mut, iter_chain)]
use std::{
collections::{BTreeMap, HashMap},
env,
os::{
fd::{AsRawFd, RawFd},
yggdrasil::io::poll::PollChannel,
},
path::Path,
marker::PhantomData,
os::fd::{AsRawFd, RawFd},
process::{Command, ExitCode},
};
use cross::mem::{FileMapping, SharedMemory};
use display::Display;
use error::Error;
use input::{InputState, KeyboardInput};
use cross::mem::SharedMemory;
use input::InputState;
use libcolors::{
event::{EventData, KeyModifiers, KeyboardKey, KeyboardKeyEvent, WindowEvent, WindowInfo},
message::{ClientMessage, ServerMessage},
event::{EventData, KeyModifiers, KeyboardKeyEvent, WindowEvent, WindowInfo},
input::Key,
message::{ClientMessage, CreateWindowInfo, WindowType},
};
use uipc::{Channel, PeerAddress, Receiver, Sender};
use sys::{Backend, DisplaySurface, FromClient, Point, ServerSender, WindowServer};
use uipc::PeerAddress;
use window::Window;
use wm::{Direction, Workspace};
// TODO rewrite and split this into meaningful components
pub mod display;
pub mod error;
pub mod input;
pub mod sys;
pub mod window;
pub mod wm;
pub struct Window<'a> {
window_id: u32,
client_id: PeerAddress,
surface_mapping: FileMapping,
surface_data: &'a [u32],
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
Backend(#[from] sys::Error),
}
pub struct Frame {
x: u32,
y: u32,
w: u32,
h: u32,
dirty: bool,
window: Option<u32>,
}
pub struct Row {
frames: Vec<Frame>,
x: u32,
y: u32,
width: u32,
height: u32,
}
pub struct ServerSender(Sender<ServerMessage>);
pub struct Server<'a, 'd> {
display: Display<'d>,
pub struct Server<'a> {
input_state: InputState,
last_client_id: u32,
client_map: HashMap<u32, PeerAddress>,
// Window management
workspace: Workspace<u32>,
windows: BTreeMap<u32, Window<'a>>,
rows: Vec<Row>,
last_window_id: u32,
focused_frame: Option<(usize, usize)>,
// Outer frame
padding: usize,
background: u32,
// Event generators
poll: PollChannel,
receiver: Receiver<ClientMessage>,
input: KeyboardInput,
// Comms
sender: ServerSender,
_pd: PhantomData<&'a ()>,
}
impl Row {
pub fn new(x: u32, y: u32, w: u32, h: u32) -> Self {
Self {
frames: vec![],
x,
y,
width: w,
height: h,
}
}
pub fn balance_frames(&mut self) {
if self.frames.is_empty() {
return;
}
let spacing = 4;
let wc = self.frames.len() as u32;
let w = (self.width - spacing * (wc - 1)) / wc;
let h = self.height;
let mut x = self.x;
let y = self.y;
for frame in self.frames.iter_mut() {
frame.dirty = true;
frame.x = x;
frame.y = y;
frame.w = w;
frame.h = h;
x += w + spacing;
}
}
pub fn place_frame(&mut self) -> &mut Frame {
self.frames.push(Frame {
x: 0,
y: 0,
w: 0,
h: 0,
dirty: true,
window: None,
});
self.balance_frames();
self.frames.last_mut().unwrap()
}
pub fn remove_frame(&mut self, col: usize) {
self.frames.remove(col);
self.balance_frames();
}
}
impl<'a> Server<'a, '_> {
pub fn new(framebuffer: &str) -> Result<Self, Error> {
let mut poll = PollChannel::new()?;
let mut display = Display::open(framebuffer)?;
let input = KeyboardInput::open()?;
let channel = Channel::bind(libcolors::CHANNEL_NAME)?;
let (sender, receiver) = channel.split();
let sender = ServerSender(sender);
poll.add(input.as_poll_fd())?;
poll.add(receiver.as_raw_fd())?;
let background = 0xFFCCCCCC;
display.fill(background);
display.flush();
impl<'a> Server<'a> {
fn new() -> Result<Self, Error> {
Ok(Self {
display,
input_state: InputState::default(),
poll,
receiver,
input,
sender,
padding: 4,
background,
background: 0xCCCCCC,
last_client_id: 0,
client_map: HashMap::new(),
windows: BTreeMap::new(),
rows: vec![],
workspace: Workspace::new(800, 600),
last_window_id: 1,
focused_frame: None,
_pd: PhantomData, // windows: BTreeMap::new(),
// rows: vec![],
// focused_frame: None,
})
}
fn create_window(&mut self, client_id: &PeerAddress) -> Result<(WindowInfo, RawFd), Error> {
if self.rows.is_empty() {
self.rows.push(Row::new(
self.padding as _,
self.padding as _,
(self.display.width() - self.padding * 2) as _,
(self.display.height() - self.padding * 2) as _,
));
}
// Create a frame
let row = self.rows.last_mut().unwrap();
let frame = row.place_frame();
// Create the actual window
let window_id = self.last_window_id;
fn create_window(
&mut self,
surface: &mut DisplaySurface,
tx: &mut ServerSender,
peer: &PeerAddress,
info: CreateWindowInfo
) -> Result<(WindowInfo, RawFd), Error> {
let wid = self.last_window_id;
self.last_window_id += 1;
let mapping_size = self.display.width() * self.display.height() * 4;
let need_focus = match info.ty {
WindowType::Default => {
if !self.workspace.create_window(wid) {
todo!()
}
true
}
WindowType::Reservation(height) => {
self.workspace.add_reservation(wid, height);
false
}
};
let frame = self.workspace.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();
@ -211,298 +99,133 @@ impl<'a> Server<'a, '_> {
)
};
frame.window = Some(window_id);
let window = Window {
window_id,
client_id: client_id.clone(),
wid,
peer: peer.clone(),
surface_mapping,
surface_data,
};
self.windows.insert(window_id, window);
self.windows.insert(wid, window);
let info = WindowInfo {
window_id,
surface_stride: self.display.width() * 4,
window_id: wid,
surface_stride: surface.width() * 4, // self.display.width() * 4,
surface_mapping_size: mapping_size,
width: frame.w,
height: frame.h,
};
self.display.fill(self.background);
self.set_focused_window(window_id)?;
self.flush_dirty_frames();
surface.fill(self.background);
if need_focus {
self.focus_window(tx, wid);
}
Ok((info, fd))
}
fn remove_window(&mut self, window_id: u32) {
// Find the window
if !self.windows.contains_key(&window_id) {
return;
fn focus_window(&mut self, tx: &mut ServerSender, wid: u32) {
let (old_wid, new) = self.workspace.focus_window(wid);
let old = old_wid.and_then(|wid| {
let window = self.windows.get(&wid)?;
Some((wid, window))
});
let new = new.and_then(|_| self.windows.get(&wid));
if let Some((wid, window)) = old {
log::info!("wid #{wid} focus=false");
tx.send_event(
EventData::WindowEvent(wid, WindowEvent::FocusChanged(false)),
&window.peer,
);
}
// TODO this is ugly
let mut res = None;
for (i, row) in self.rows.iter().enumerate() {
let j = row
.frames
.iter()
.position(|f| f.window.map(|w| w == window_id).unwrap_or(false));
if let Some(j) = j {
res = Some((i, j));
}
if let Some(window) = new {
log::info!("wid #{wid} focus=true");
tx.send_event(
EventData::WindowEvent(wid, WindowEvent::FocusChanged(true)),
&window.peer,
);
}
}
// Remove the frame
if let Some((row, col)) = res {
self.rows[row].remove_frame(col);
self.display.fill(self.background);
self.flush_dirty_frames();
fn move_focus(&mut self, tx: &mut ServerSender, direction: Direction) {
if let Some(wid) = self.workspace.window_towards_wrap(direction) {
self.focus_window(tx, wid);
}
}
self.windows.remove(&window_id);
fn move_window(&mut self, surface: &mut DisplaySurface, tx: &mut ServerSender, direction: Direction) {
if self.workspace.move_window(direction) {
surface.fill(self.background);
self.flush_dirty_frames(tx);
self.workspace.all_windows().for_each(|(wid, _)| {
if let Some(window) = self.windows.get(&wid) {
tx.send_event(
EventData::WindowEvent(wid, WindowEvent::RedrawRequested),
&window.peer,
);
}
});
}
}
if self.focused_frame == res {
self.focused_frame = None;
let new_focus = if let Some((row, col)) = res {
// Focus some other frame in the same row
if let Some(f_row) = self.rows.get(row) {
let row_len = f_row.frames.len();
if col == 0 && row_len != 0 {
Some((row, 1))
} else if col > 0 {
Some((row, col - 1))
} else {
// Empty row
None
}
} else {
// No row exists
None
}
} else {
// No frames?
None
fn flush_dirty_frames(&mut self, tx: &mut ServerSender) {
for (wid, rect) in self.workspace.dirty_windows() {
let Some(window) = self.windows.get_mut(&wid) else {
continue;
};
log::info!("Resize #{}: {}x{}", wid, rect.w, rect.h);
self.set_focused_frame(new_focus);
window.resize(rect.w, rect.h);
tx.send_event(
EventData::WindowEvent(
wid,
WindowEvent::Resized {
width: rect.w,
height: rect.h,
},
),
&window.peer,
);
}
}
}
fn handle_keyboard_event(&mut self, event: KeyboardKeyEvent) -> Result<(), Error> {
let (key, state) = event.split();
self.input_state.update(key, state);
if state {
let input = self.input_state.make_input(key);
// Non-window keys
#[allow(clippy::single_match)]
match (input.modifiers, input.key) {
(KeyModifiers::ALT, KeyboardKey::Enter) => {
// TODO do something with spawned child
Command::new("/bin/term").spawn().ok();
return Ok(());
}
_ => (),
}
// Window keys
if let Some((row, col)) = self.focused_frame {
let row_len = self.rows[row].frames.len();
match (input.modifiers, input.key) {
(KeyModifiers::ALT, KeyboardKey::Char(b'l')) => {
if col + 1 < row_len {
self.set_focused_frame(Some((row, col + 1)));
} else {
self.set_focused_frame(Some((row, 0)));
}
return Ok(());
}
(KeyModifiers::ALT, KeyboardKey::Char(b'h')) => {
if col > 0 {
self.set_focused_frame(Some((row, col - 1)));
} else if row_len != 0 {
self.set_focused_frame(Some((row, row_len - 1)));
}
return Ok(());
}
_ => (),
}
}
if let Some((_, window)) = self.get_focused_window() {
// Deliver event to the window
self.sender
.send_event(
EventData::WindowEvent(window.window_id, WindowEvent::KeyInput(input)),
&window.client_id,
)
.ok();
} else {
self.focused_frame = None;
}
}
Ok(())
}
fn get_window(&self, window_id: u32) -> Option<(&Frame, &Window<'a>)> {
let window = self.windows.get(&window_id)?;
for row in self.rows.iter() {
if let Some(f) = row
.frames
.iter()
.find(|f| f.window.map(|w| w == window_id).unwrap_or(false))
{
return Some((f, window));
}
}
// TODO Orphaned frame/window?
None
}
fn get_focused_window(&self) -> Option<(&Frame, &Window<'a>)> {
let (row, col) = self.focused_frame?;
let frame = &self.rows[row].frames[col];
let window = frame.window.and_then(|w| self.windows.get(&w))?;
Some((frame, window))
}
fn set_focused_frame(&mut self, focus: Option<(usize, usize)>) {
if self.focused_frame == focus {
return;
}
if let Some((_, old_window)) = self.get_focused_window() {
self.sender
.send_event(
EventData::WindowEvent(old_window.window_id, WindowEvent::FocusChanged(false)),
&old_window.client_id,
)
.ok();
}
self.focused_frame = focus;
if let Some((row, col)) = focus {
let Some(f_row) = self.rows.get(row) else {
return;
};
let Some(frame) = f_row.frames.get(col) else {
return;
};
let Some(window) = frame.window.and_then(|w| self.windows.get(&w)) else {
return;
};
self.sender
.send_event(
EventData::WindowEvent(window.window_id, WindowEvent::FocusChanged(true)),
&window.client_id,
)
.ok();
}
}
fn set_focused_window(&mut self, window_id: u32) -> Result<(), Error> {
// TODO this is ugly
let mut res = None;
for (i, row) in self.rows.iter().enumerate() {
let j = row
.frames
.iter()
.position(|f| f.window.map(|w| w == window_id).unwrap_or(false));
if let Some(j) = j {
res = Some((i, j));
}
}
self.set_focused_frame(res);
Ok(())
}
fn flush_dirty_frames(&mut self) {
for row in self.rows.iter() {
for frame in row.frames.iter() {
if !frame.dirty {
continue;
}
let Some(window) = frame.window.and_then(|w| self.windows.get_mut(&w)) else {
// TODO handle orphaned frame
continue;
};
let new_surface_data = unsafe {
std::slice::from_raw_parts_mut(
window.surface_mapping.as_mut_ptr() as *mut u32,
(frame.w * frame.h) as usize,
)
};
window.surface_data = new_surface_data;
self.sender
.send_event(
EventData::WindowEvent(
window.window_id,
WindowEvent::Resized {
width: frame.w,
height: frame.h,
},
),
&window.client_id,
)
.ok();
}
}
impl WindowServer for Server<'_> {
fn handle_initial(&mut self, mut surface: DisplaySurface) {
self.workspace.resize(surface.width() as u32, surface.height() as u32);
surface.fill(self.background);
surface.present();
}
fn handle_client_message(
&mut self,
client_id: PeerAddress,
message: ClientMessage,
) -> Result<(), Error> {
match message {
mut surface: DisplaySurface,
tx: &mut ServerSender,
message: FromClient,
) {
let peer = message.peer;
match message.message {
ClientMessage::ClientHello => {
debug_trace!("{:?}: ClientHello", client_id);
log::info!("{:?}: ClientHello", peer);
// Echo the ID back
self.last_client_id += 1;
let id = self.last_client_id;
self.client_map.insert(id, client_id.clone());
self.sender.send_event(EventData::ServerHello(id), &client_id)
self.client_map.insert(id, peer.clone());
tx.send_event(EventData::ServerHello(id), &peer);
}
ClientMessage::CreateWindow => {
debug_trace!("{:?}: CreateWindow", client_id);
let (info, shm_fd) = self.create_window(&client_id)?;
ClientMessage::CreateWindow(info) => {
log::info!("{:?}: CreateWindow", peer);
let (info, shm_fd) = self.create_window(&mut surface, tx, &peer, info).unwrap();
let window_id = info.window_id;
self.sender.send_event_with_file(
EventData::NewWindowInfo(info),
&shm_fd,
&client_id,
)?;
self.sender.send_event(
tx.send_event_with_file(EventData::NewWindowInfo(info), &shm_fd, &peer);
self.flush_dirty_frames(tx);
tx.send_event(
EventData::WindowEvent(window_id, WindowEvent::RedrawRequested),
&client_id,
)?;
Ok(())
&peer,
);
surface.present();
}
ClientMessage::BlitWindow {
window_id,
@ -511,98 +234,148 @@ impl<'a> Server<'a, '_> {
w,
h,
} => {
if let Some((frame, window)) = self.get_window(window_id) {
log::trace!("{:?}: BlitWindow", peer);
if let Some(window) = self.windows.get(&window_id) {
let Some(frame) = self.workspace.window_frame(window_id) else {
return;
};
let x = x.min(frame.w);
let y = y.min(frame.h);
let w = w.min(frame.w - x);
let h = h.min(frame.h - y);
let w = w.min(frame.w.saturating_sub(x));
let h = h.min(frame.h.saturating_sub(y));
if w == 0 || h == 0 {
// Invalid rectangle, skip it
return Ok(());
return;
}
self.display.blit_buffer(
let dst = Point(frame.x as _, frame.y as _);
let src = Point(x as _, y as _);
log::info!("Blit {src:?} {w}x{h} -> {dst:?}");
surface.blit_buffer(
window.surface_data,
display::Point(frame.x as _, frame.y as _),
display::Point(x as _, y as _),
dst,
src,
w as _,
h as _,
frame.w as usize,
);
}
Ok(())
}
ClientMessage::DestroyWindow(window_id) => {
debug_trace!("{:?}: DestroyWindow {}", client_id, window_id);
self.remove_window(window_id);
Ok(())
ClientMessage::DestroyWindow(wid) => {
log::info!("{:?}: DestroyWindow", peer);
let window = self.windows.remove(&wid);
if window.is_some() {
self.workspace.remove_window(wid);
self.flush_dirty_frames(tx);
}
surface.present();
}
}
}
fn run_inner(mut self) -> Result<(), Error> {
loop {
match self.poll.wait(None, true)? {
Some((fd, Ok(_))) if fd == self.input.as_poll_fd() => {
let event = self.input.read_event()?;
self.handle_keyboard_event(event)?;
fn handle_keyboard_event(
&mut self,
mut surface: DisplaySurface,
tx: &mut ServerSender,
event: KeyboardKeyEvent,
) {
self.input_state.update(event.key, event.state);
if event.state {
let input = self.input_state.make_input(event.key);
// Non-window keys
#[allow(clippy::single_match)]
match (input.modifiers, input.key) {
(KeyModifiers::ALT, Key::Enter) => {
// TODO do something with spawned child
Command::new("/bin/term").spawn().ok();
return;
}
Some((fd, Ok(_))) if fd == self.receiver.as_raw_fd() => {
let (data, client_id) = self.receiver.receive_from()?;
self.handle_client_message(client_id, data)?;
}
Some((_, Ok(_))) => {
todo!()
}
Some((_, Err(error))) => {
return Err(Error::from(error));
}
None => (),
_ => (),
}
}
}
pub fn run(self) -> ExitCode {
match self.run_inner() {
Ok(_) => ExitCode::SUCCESS,
Err(error) => {
debug_trace!("colors server finished with an error: {}", error);
ExitCode::FAILURE
let focus = self.workspace.focused_window().and_then(|wid| {
let window = self.windows.get(&wid)?;
Some((wid, window))
});
match (input.modifiers, input.key) {
(KeyModifiers::ALT, Key::Char(b'l')) => {
self.move_focus(tx, Direction::Right);
return;
}
(KeyModifiers::ALT, Key::Char(b'h')) => {
self.move_focus(tx, Direction::Left);
return;
}
(KeyModifiers::ALT, Key::Char(b'j')) => {
self.move_focus(tx, Direction::Down);
return;
}
(KeyModifiers::ALT, Key::Char(b'k')) => {
self.move_focus(tx, Direction::Up);
return;
}
(KeyModifiers::ALT_SHIFT, Key::Char(b'l')) => {
self.move_window(&mut surface, tx, Direction::Right);
surface.present();
return;
}
(KeyModifiers::ALT_SHIFT, Key::Char(b'h')) => {
self.move_window(&mut surface, tx, Direction::Left);
surface.present();
return;
}
(KeyModifiers::ALT_SHIFT, Key::Char(b'j')) => {
self.move_window(&mut surface, tx, Direction::Down);
surface.present();
return;
}
(KeyModifiers::ALT_SHIFT, Key::Char(b'k')) => {
self.move_window(&mut surface, tx, Direction::Up);
surface.present();
return;
}
_ => (),
}
if let Some((wid, window)) = focus {
// Deliver event to the window
tx.send_event(
EventData::WindowEvent(wid, WindowEvent::KeyInput(input)),
&window.peer,
);
}
// // Window keys
// if let Some((row, col)) = self.focused_frame {
// let row_len = self.rows[row].frames.len();
// if let Some((_, window)) = self.get_focused_window() {
// } else {
// self.focused_frame = None;
// }
}
}
}
impl ServerSender {
pub fn send_event(&self, event: EventData, client_id: &PeerAddress) -> Result<(), Error> {
self.0.send_to(&ServerMessage::Event(event), client_id)?;
Ok(())
}
pub fn send_event_with_file<F: AsRawFd>(
&self,
event: EventData,
file: &F,
client_id: &PeerAddress,
) -> Result<(), Error> {
self.0
.send_with_file_to(&ServerMessage::Event(event), file, client_id)?;
Ok(())
}
}
fn main() -> ExitCode {
let args = env::args().skip(1).collect::<Vec<_>>();
let framebuffer = args.first().map_or("/dev/fb0", |s| s.as_str());
let path: &Path = framebuffer.as_ref();
logsink::setup_logging();
log::info!("Colors starting");
if !path.exists() {
debug_trace!("{framebuffer} does not exist, colors won't start");
return ExitCode::FAILURE;
let server = Server::new().unwrap();
let backend = Backend::new(server).unwrap();
log::info!("Run backend");
match backend.run() {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
let server = Server::new(framebuffer).unwrap();
server.run()
}

View File

@ -0,0 +1,66 @@
use std::os::fd::{AsRawFd, OwnedFd};
use libcolors::{
event::{EventData, KeyboardKeyEvent},
message::{ClientMessage, ServerMessage},
};
use uipc::{PeerAddress, Sender};
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
pub mod yggdrasil;
#[cfg(any(rust_analyzer, unix))]
pub mod unix;
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
pub use yggdrasil::{Backend, DisplaySurface, Error};
#[cfg(any(rust_analyzer, unix))]
pub use unix::{Backend, DisplaySurface, Error};
#[derive(Debug)]
pub struct FromClient {
pub peer: PeerAddress,
pub message: ClientMessage,
pub file: Option<OwnedFd>,
}
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_client_message(
&mut self,
surface: DisplaySurface,
tx: &mut ServerSender,
message: FromClient,
);
fn handle_keyboard_event(
&mut self,
surface: DisplaySurface,
tx: &mut ServerSender,
event: KeyboardKeyEvent,
);
}
impl ServerSender {
pub fn send_event(&self, event: EventData, client_id: &PeerAddress) {
self.sender
.send_to(&ServerMessage::Event(event), client_id)
.ok();
}
pub fn send_event_with_file<F: AsRawFd>(
&self,
event: EventData,
file: &F,
client_id: &PeerAddress,
) {
self.sender
.send_with_file_to(&ServerMessage::Event(event), file, client_id)
.ok();
}
}

View File

@ -0,0 +1,262 @@
use std::{
cmp, fs, io,
num::NonZero,
ops::{Deref, DerefMut},
rc::Rc,
};
use clap::Parser;
use libcolors::{
event::KeyboardKeyEvent,
input::Key,
message::{ClientMessage, ServerMessage},
};
use softbuffer::{Buffer, Rect, SoftBufferError};
use uipc::{Receiver, Sender};
use winit::{
dpi::LogicalSize,
error::{EventLoopError, OsError},
event::{ElementState, Event, WindowEvent},
event_loop::{EventLoop, EventLoopProxy},
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowAttributes},
};
use super::{FromClient, Point, ServerSender, WindowServer};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Event loop error: {0}")]
EventLoop(#[from] EventLoopError),
#[error("OS error: {0}")]
Os(#[from] OsError),
#[error("Display error: {0}")]
Softbuffer(#[from] SoftBufferError),
}
pub struct Backend<S: WindowServer> {
event_loop: EventLoop<FromClient>,
window: Rc<Window>,
tx: Sender<ServerMessage>,
server: S,
}
pub struct DisplaySurface<'a> {
inner: Buffer<'a, Rc<Window>, Rc<Window>>,
width: usize,
height: usize,
}
#[derive(Debug, Parser)]
struct Args {
#[clap(short, help = "Framebuffer width", default_value_t = 1024)]
width: u32,
#[clap(short, help = "Framebuffer height", default_value_t = 768)]
height: u32,
}
impl DisplaySurface<'_> {
pub fn width(&self) -> usize {
self.width
}
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();
}
}
impl Deref for DisplaySurface<'_> {
type Target = [u32];
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
impl DerefMut for DisplaySurface<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.deref_mut()
}
}
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();
std::thread::spawn(move || {
Self::io_worker(rx, event_proxy);
});
Ok(Self {
event_loop,
window: Rc::new(window),
tx,
server,
})
}
fn io_worker(mut rx: Receiver<ClientMessage>, event_proxy: EventLoopProxy<FromClient>) {
loop {
let (message, file, peer) = rx.receive_with_file_from().unwrap();
let message = FromClient {
message,
file,
peer,
};
event_proxy.send_event(message).unwrap();
}
}
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())?;
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 _,
});
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(())
}
}
fn convert_key_event(raw: winit::event::KeyEvent) -> Option<KeyboardKeyEvent> {
let PhysicalKey::Code(code) = raw.physical_key else {
return None;
};
let key = match code {
KeyCode::ShiftLeft => Key::LShift,
KeyCode::ShiftRight => Key::RShift,
KeyCode::ControlLeft => Key::LControl,
KeyCode::ControlRight => Key::RControl,
KeyCode::AltLeft => Key::LAlt,
KeyCode::AltRight => Key::RAlt,
KeyCode::KeyQ => Key::Char(b'q'),
KeyCode::KeyW => Key::Char(b'w'),
KeyCode::KeyE => Key::Char(b'e'),
KeyCode::KeyR => Key::Char(b'r'),
KeyCode::KeyT => Key::Char(b't'),
KeyCode::KeyY => Key::Char(b'y'),
KeyCode::KeyU => Key::Char(b'u'),
KeyCode::KeyI => Key::Char(b'i'),
KeyCode::KeyO => Key::Char(b'o'),
KeyCode::KeyP => Key::Char(b'p'),
KeyCode::KeyA => Key::Char(b'a'),
KeyCode::KeyS => Key::Char(b's'),
KeyCode::KeyD => Key::Char(b'd'),
KeyCode::KeyF => Key::Char(b'f'),
KeyCode::KeyG => Key::Char(b'g'),
KeyCode::KeyH => Key::Char(b'h'),
KeyCode::KeyJ => Key::Char(b'j'),
KeyCode::KeyK => Key::Char(b'k'),
KeyCode::KeyL => Key::Char(b'l'),
KeyCode::KeyZ => Key::Char(b'z'),
KeyCode::KeyX => Key::Char(b'x'),
KeyCode::KeyC => Key::Char(b'c'),
KeyCode::KeyV => Key::Char(b'v'),
KeyCode::KeyB => Key::Char(b'b'),
KeyCode::KeyN => Key::Char(b'n'),
KeyCode::KeyM => Key::Char(b'm'),
_ => return None,
};
let state = match raw.state {
ElementState::Pressed => true,
ElementState::Released => false,
};
Some(KeyboardKeyEvent { key, state })
}

View File

@ -0,0 +1,278 @@
use std::{
cmp,
fs::{File, OpenOptions},
io::{self, Read},
ops::{Deref, DerefMut},
os::fd::{AsRawFd, RawFd},
path::{Path, PathBuf},
};
use clap::Parser;
use cross::{io::Poll, mem::FileMapping};
use libcolors::{event::KeyboardKeyEvent, input::Key, message::ClientMessage};
use runtime::rt::io::device;
use uipc::{Channel, Receiver};
use yggdrasil_abi::io::KeyboardKey;
use crate::sys::FromClient;
use super::{Point, ServerSender, WindowServer};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("IPC error: {0}")]
Ipc(#[from] uipc::Error),
}
pub struct Backend<'a, S: WindowServer> {
poll: Poll,
rx: Receiver<ClientMessage>,
tx: ServerSender,
display: Display<'a>,
input: KeyboardInput,
server: S,
}
struct Display<'a> {
#[allow(unused)]
mapping: FileMapping,
data: &'a mut [u32],
width: usize,
height: usize,
// TODO use those
_stride: usize,
_size: usize,
}
struct KeyboardInput(File);
pub struct DisplaySurface<'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);
}
self.present();
}
pub fn present(self) {
self.display.flush();
}
}
impl Deref for DisplaySurface<'_, '_> {
type Target = [u32];
fn deref(&self) -> &Self::Target {
self.display.data
}
}
impl DerefMut for DisplaySurface<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.display.data
}
}
#[derive(Debug, Parser)]
struct Args {
framebuffer: PathBuf,
}
impl<S: WindowServer> Backend<'_, S> {
pub fn new(server: S) -> Result<Self, Error> {
let args = Args::parse();
let channel = Channel::bind(libcolors::CHANNEL_NAME)?;
let (tx, rx) = channel.split();
let tx = ServerSender { sender: tx };
let input = KeyboardInput::open()?;
let mut poll = Poll::new()?;
poll.add(&rx)?;
poll.add(&input)?;
let display = Display::open(args.framebuffer)?;
Ok(Self {
poll,
tx,
rx,
display,
input,
server,
})
}
pub fn run(mut self) -> Result<(), Error> {
self.server.handle_initial(DisplaySurface {
display: &mut self.display
});
loop {
let fd = self.poll.wait(None)?.unwrap();
if fd == self.rx.as_raw_fd() {
let (message, file, peer) = self.rx.receive_with_file_from()?;
let event = FromClient {
message,
file,
peer,
};
let surface = DisplaySurface {
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()?;
if let Some(event) = convert_key_event(event) {
let surface = DisplaySurface {
display: &mut self.display,
};
self.server.handle_keyboard_event(surface, &mut self.tx, event);
}
}
}
}
}
impl Display<'_> {
pub fn open(framebuffer: impl AsRef<Path>) -> Result<Self, Error> {
let framebuffer = framebuffer.as_ref();
let file = OpenOptions::new().open(framebuffer)?;
let mut buffer = [0; 128];
device::device_request::<device::AcquireDevice>(file.as_raw_fd(), &mut buffer, &())
.map_err(std::io::Error::from)?;
let framebuffer = device::device_request::<device::GetActiveFramebuffer>(
file.as_raw_fd(),
&mut buffer,
&(),
)
.map_err(std::io::Error::from)?;
let width = framebuffer.width as usize;
let height = framebuffer.height as usize;
let mut mapping = FileMapping::map(file, framebuffer.size)?;
let data = unsafe {
std::slice::from_raw_parts_mut(mapping.as_mut_ptr() as *mut u32, width * height)
};
Ok(Self {
mapping,
data,
width,
height,
_stride: framebuffer.stride / size_of::<u32>(),
_size: framebuffer.size / size_of::<u32>(),
})
}
pub fn flush(&mut self) {
let mut buffer = [0; 0];
device::device_request::<device::FlushDisplay>(self.mapping.as_raw_fd(), &mut buffer, &())
.ok();
}
}
impl KeyboardInput {
pub fn open() -> Result<Self, Error> {
let file = File::open("/dev/kbd")?;
Ok(Self(file))
}
pub fn read_event(&mut self) -> Result<yggdrasil_abi::io::KeyboardKeyEvent, Error> {
let mut buf = [0; 4];
let len = self.0.read(&mut buf)?;
if len == 4 {
Ok(yggdrasil_abi::io::KeyboardKeyEvent::from_bytes(buf))
} else {
todo!()
}
}
}
impl AsRawFd for KeyboardInput {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
fn convert_key_event(raw: yggdrasil_abi::io::KeyboardKeyEvent) -> 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,
};
Some(KeyboardKeyEvent {
key,
state
})
}

View File

@ -0,0 +1,23 @@
use cross::mem::FileMapping;
use uipc::PeerAddress;
pub struct Window<'d> {
pub wid: u32,
pub peer: PeerAddress,
pub surface_mapping: FileMapping,
pub surface_data: &'d [u32],
}
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,
)
};
self.surface_data = new_surface_data;
}
}

View File

@ -0,0 +1,691 @@
use std::{cell::Cell, collections::HashMap, hash::Hash, iter};
pub type NodeId = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Up,
Down,
Left,
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,
Vertical,
}
pub enum NodeContent<T> {
Container(ContainerNode),
Window(WindowNode<T>),
}
pub struct NodeLayout {
rect: Cell<(Option<Rect>, bool)>,
}
pub struct Node<T> {
parent: Option<NodeId>,
layout: NodeLayout,
content: NodeContent<T>,
}
pub struct ContainerNode {
children: Vec<NodeId>,
orientation: Orientation,
}
pub struct WindowNode<T> {
pub wid: T,
}
pub struct Reservation<T> {
layout: NodeLayout,
size: u32,
wid: T,
}
pub struct Workspace<T> {
nodes: HashMap<NodeId, Node<T>>,
wid_to_nid: HashMap<T, NodeId>,
last_node_id: NodeId,
root: NodeId,
focus: Option<NodeId>,
margin: u32,
spacing: u32,
reservations_top: Vec<Reservation<T>>,
reservation_top: u32,
width: u32,
height: u32,
}
impl<T> Node<T> {
pub fn as_window(&self) -> Option<&WindowNode<T>> {
match &self.content {
NodeContent::Window(window) => Some(window),
_ => None,
}
}
pub fn as_container(&self) -> Option<&ContainerNode> {
match &self.content {
NodeContent::Container(container) => Some(container),
_ => None,
}
}
pub fn as_container_mut(&mut self) -> Option<&mut ContainerNode> {
match &mut self.content {
NodeContent::Container(container) => Some(container),
_ => None,
}
}
}
impl NodeLayout {
pub fn set(&self, new: Rect) {
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> {
let (value, _) = self.rect.get();
value
}
pub fn clear(&self) {
self.rect.set((None, false));
}
pub fn clear_dirty(&self) -> bool {
let (value, dirty) = self.rect.get();
self.rect.set((value, false));
dirty && value.is_some()
}
}
impl<T: Eq + Hash + Copy> Workspace<T> {
pub fn new(width: u32, height: u32) -> Self {
let root = Node {
parent: None,
layout: NodeLayout {
rect: Cell::new((None, false)),
},
content: NodeContent::Container(ContainerNode {
children: vec![],
orientation: Orientation::Horizontal,
}),
};
let nodes = HashMap::from_iter([(0, root)]);
let mut this = Self {
nodes,
wid_to_nid: HashMap::new(),
last_node_id: 0,
margin: 4,
spacing: 4,
root: 0,
focus: None,
reservation_top: 0,
reservations_top: Vec::new(),
width,
height,
};
this.update_layout();
this
}
pub fn add_reservation(&mut self, wid: T, height: u32) {
self.reservations_top.push(Reservation {
layout: NodeLayout {
rect: Cell::new((None, false)),
},
size: height,
wid,
});
self.update_layout();
}
pub fn all_windows(&self) -> impl Iterator<Item = (T, &NodeLayout)> + '_ {
let nodes_windows = self.nodes.iter().filter_map(|(_, node)| {
let window = node.as_window()?;
Some((window.wid, &node.layout))
// Some(node.as_window()?.wid)
});
let reservation_windows = self
.reservations_top
.iter()
.map(|res| (res.wid, &res.layout));
iter::chain(reservation_windows, nodes_windows)
}
pub fn dirty_windows(&self) -> impl Iterator<Item = (T, Rect)> + '_ {
self.all_windows().filter_map(|(wid, layout)| {
if !layout.clear_dirty() {
return None;
}
let rect = layout.get()?;
Some((wid, rect))
})
}
pub fn window_towards_wrap(&self, direction: Direction) -> Option<T> {
let current_nid = self.focus?;
let current_node = self.nodes.get(&current_nid)?;
let parent_nid = current_node.parent?;
let parent_node = self.nodes.get(&parent_nid)?;
let parents_parent_nid = parent_node.parent;
let parent_container = parent_node.as_container()?;
let (orientation, delta) = direction.split();
let nid = if orientation == parent_container.orientation {
log::info!("Within parent {delta}, {:?}", parent_container.orientation);
let position_in_parent = parent_container
.children
.iter()
.position(|&n| n == current_nid)?;
if delta > 0 {
if position_in_parent < parent_container.children.len() - 1 {
parent_container.children[position_in_parent + 1]
} else {
parent_container.children[0]
}
} else {
if position_in_parent > 0 {
parent_container.children[position_in_parent - 1]
} else {
parent_container.children[parent_container.children.len() - 1]
}
}
} else if let Some(parents_parent_nid) = parents_parent_nid {
let parents_parent_node = self.nodes.get(&parents_parent_nid)?;
let parents_parent_container = parents_parent_node.as_container()?;
assert_eq!(parents_parent_container.orientation, orientation);
let position_in_parent = parents_parent_container
.children
.iter()
.position(|&n| n == parent_nid)?;
log::info!(
"Within parent's parent {delta}, {:?}",
parents_parent_container.orientation
);
log::info!("position_in_parent = {position_in_parent}");
if delta > 0 {
if position_in_parent < parent_container.children.len() - 1 {
parents_parent_container.children[position_in_parent + 1]
} else {
parents_parent_container.children[0]
}
} else {
if position_in_parent > 0 {
parents_parent_container.children[position_in_parent - 1]
} else {
parents_parent_container.children[parent_container.children.len() - 1]
}
}
} else {
return None;
};
self.first_window_in(nid)
}
pub fn first_window_in(&self, nid: NodeId) -> Option<T> {
let node = self.nodes.get(&nid)?;
match &node.content {
NodeContent::Window(window) => Some(window.wid),
NodeContent::Container(container) => {
let first = *container.children.first()?;
self.first_window_in(first)
}
}
}
pub fn move_window(&mut self, direction: Direction) -> bool
where
T: core::fmt::Debug,
{
if let Some(container_nid) = self.move_window_inner(direction) {
self.invalidate_layout(container_nid);
self.fixup_containers(self.root);
self.dump(self.root, 0);
true
} else {
false
}
}
fn dump(&self, nid: NodeId, depth: usize)
where
T: core::fmt::Debug,
{
let Some(node) = self.nodes.get(&nid) else {
return;
};
let rect = node.layout.get();
match &node.content {
NodeContent::Window(window) => {
log::info!(
"{:depth$}Window #{:?} {rect:?}",
"",
window.wid,
depth = depth * 2
);
}
NodeContent::Container(container) => {
log::info!("{:depth$}Container {rect:?} {{", "", depth = depth * 2);
for &child_nid in container.children.iter() {
self.dump(child_nid, depth + 1);
}
log::info!("{:depth$}}}", "", depth = depth * 2);
}
}
}
// pub fn walk_windows<F: FnMut(T)>(&self, mut mapper: F) {
// self.walk_windows_inner(&mut mapper, self.root)
// }
// fn walk_windows_inner<F: FnMut(T)>(&self, mapper: &mut F, nid: NodeId) {
// // let Some(node) = self.nodes.get(&nid) else {
// // return;
// // };
// // match &node.content {
// // NodeContent::Container(container) => {
// // for &child_nid in container.children.iter() {
// // self.walk_windows_inner(mapper, child_nid);
// // }
// // }
// // NodeContent::Window(window) => {
// // mapper(window.wid);
// // }
// // }
// }
fn invalidate_layout(&mut self, nid: NodeId) {
for reservation in self.reservations_top.iter() {
reservation.layout.clear();
}
self.invalidate_layout_inner(nid);
self.update_layout();
}
fn invalidate_layout_inner(&self, nid: NodeId) {
let Some(node) = self.nodes.get(&nid) else {
return;
};
node.layout.clear();
match &node.content {
NodeContent::Container(container) => {
for &child_nid in container.children.iter() {
self.invalidate_layout_inner(child_nid);
}
}
_ => (),
}
}
fn fixup_containers(&mut self, nid: NodeId) {
let Some(node) = self.nodes.get_mut(&nid) else {
return;
};
if let NodeContent::Container(container) = &mut node.content {
if container.children.is_empty() {
let Some(parent_nid) = node.parent else {
return;
};
// Remove the empty container
if let Some(parent_container) = self
.nodes
.get_mut(&parent_nid)
.and_then(Node::as_container_mut)
{
parent_container.children.retain(|&n| n != nid);
self.nodes.remove(&nid);
self.invalidate_layout(parent_nid);
}
} else if container.children.len() == 1 {
// Remove the empty container and reparent its only child
if let Some(parent_nid) = node.parent {
let child_nid = container.children.remove(0);
let [Some(child), Some(parent)] =
self.nodes.get_many_mut([&child_nid, &parent_nid])
else {
return;
};
child.parent = Some(parent_nid);
if let Some(parent_container) = parent.as_container_mut() {
parent_container.children.retain(|&n| n != nid);
parent_container.children.push(child_nid);
}
self.invalidate_layout(parent_nid);
}
} else {
// TODO borrowing stuff
let children = container.children.clone();
for child_nid in children {
self.fixup_containers(child_nid);
}
}
}
}
fn move_window_inner(&mut self, direction: Direction) -> Option<NodeId> {
let current_nid = self.focus?;
let current_node = self.nodes.get(&current_nid)?;
let parent_nid = current_node.parent?;
let parent_node = self.nodes.get_mut(&parent_nid)?;
let parents_parent_nid = parent_node.parent;
let parent_container = parent_node.as_container_mut()?;
let (orientation, delta) = direction.split();
if parent_container.orientation == orientation {
let position_in_parent = parent_container
.children
.iter()
.position(|&n| n == current_nid)?;
// TODO check if this item is also a container
if delta < 0 && position_in_parent > 0 {
parent_container
.children
.swap(position_in_parent - 1, position_in_parent);
Some(parent_nid)
} else if delta > 0 && position_in_parent < parent_container.children.len() - 1 {
parent_container
.children
.swap(position_in_parent, position_in_parent + 1);
Some(parent_nid)
} else {
None
}
} else if let Some(parents_parent_nid) = parents_parent_nid {
// Go to parent's parent
let parents_parent_node = self.nodes.get_mut(&parents_parent_nid)?;
let parents_parent_container = parents_parent_node.as_container_mut()?;
assert_eq!(parents_parent_container.orientation, orientation);
todo!()
} else {
self.last_node_id += 1;
let new_root_nid = self.last_node_id;
// Remove child from parent
parent_container.children.retain(|&n| n != current_nid);
self.nodes.get_mut(&current_nid)?.parent = Some(new_root_nid);
self.nodes.get_mut(&self.root)?.parent = Some(new_root_nid);
let children = if delta > 0 {
vec![self.root, current_nid]
} else {
vec![current_nid, self.root]
};
// Create a new root
let new_root = Node {
parent: None,
layout: NodeLayout {
rect: Cell::new((None, false)),
},
content: NodeContent::Container(ContainerNode {
children,
orientation,
}),
};
self.root = new_root_nid;
self.nodes.insert(new_root_nid, new_root);
Some(self.root)
}
}
pub fn focus_window(&mut self, wid: T) -> (Option<T>, Option<NodeId>) {
let old = self.focus.take().and_then(|nid| self.wid(nid));
let new = self.wid_to_nid.get(&wid).copied();
self.focus = new;
(old, new)
}
pub fn focused_window(&self) -> Option<T> {
let nid = self.focus?;
Some(self.nodes.get(&nid)?.as_window()?.wid)
}
pub fn window(&self, wid: T) -> Option<&Node<T>> {
let nid = *self.wid_to_nid.get(&wid)?;
self.nodes.get(&nid)
}
pub fn reservation(&self, wid: T) -> Option<&Reservation<T>> {
self.reservations_top.iter().find(|r| r.wid == wid)
}
pub fn window_frame(&self, wid: T) -> Option<Rect> {
if let Some(reservation) = self.reservation(wid) {
reservation.layout.get()
} else {
let node = self.window(wid)?;
node.layout.get()
}
}
pub fn resize(&mut self, w: u32, h: u32) {
self.width = w;
self.height = h;
self.update_layout();
}
pub fn update_layout(&mut self) {
let mut res_y = 0;
for reservation in self.reservations_top.iter() {
reservation.layout.set(Rect {
x: 0,
y: res_y,
w: self.width,
h: reservation.size,
});
res_y += reservation.size;
}
self.reservation_top = res_y;
self.update_layout_for(
self.root,
Rect {
x: self.margin,
y: self.margin + res_y,
w: self.width - self.margin * 2,
h: self.height - self.margin * 2 - res_y,
},
);
}
pub fn create_window(&mut self, wid: T) -> bool {
self.create_window_in(self.root, wid)
}
pub fn remove_window(&mut self, wid: T) -> bool {
let Some(nid) = self.wid_to_nid.remove(&wid) else {
return false;
};
if self.remove_inner(nid) {
self.fixup_containers(self.root);
self.update_layout();
true
} else {
false
}
}
fn create_window_in(&mut self, container_nid: NodeId, wid: T) -> bool {
self.last_node_id += 1;
let nid = self.last_node_id;
self.nodes.insert(
nid,
Node {
parent: Some(container_nid),
layout: NodeLayout {
rect: Cell::new((None, false)),
},
content: NodeContent::Window(WindowNode { wid }),
},
);
self.wid_to_nid.insert(wid, nid);
if !self.add_child_to(container_nid, nid) {
self.wid_to_nid.remove(&wid);
self.nodes.remove(&nid);
return false;
}
self.update_layout();
true
}
fn wid(&self, nid: NodeId) -> Option<T> {
self.nodes.get(&nid).and_then(|node| match &node.content {
NodeContent::Window(window) => Some(window.wid),
_ => None,
})
}
fn update_layout_for(&self, node_id: NodeId, rect: Rect) {
let Some(node) = self.nodes.get(&node_id) else {
log::warn!("update_layout_for: no node {node_id}");
return;
};
node.layout.set(rect);
match &node.content {
NodeContent::Container(container) => {
if container.children.is_empty() {
return;
}
match container.orientation {
Orientation::Vertical => {
let hstep = (rect.h - self.spacing * container.children.len() as u32)
/ container.children.len() as u32;
let ystep = hstep + self.spacing;
for (i, &child_nid) in container.children.iter().enumerate() {
let i = i as u32;
let child_rect = Rect {
x: rect.x,
y: rect.y + i * ystep,
w: rect.w,
h: hstep,
};
log::info!("Frame #{child_nid} size: {child_rect:?}");
self.update_layout_for(child_nid, child_rect);
}
}
Orientation::Horizontal => {
let wstep = (rect.w - self.spacing * container.children.len() as u32)
/ container.children.len() as u32;
let xstep = wstep + self.spacing;
for (i, &child_nid) in container.children.iter().enumerate() {
let i = i as u32;
let child_rect = Rect {
x: rect.x + i * xstep,
y: rect.y,
w: wstep,
h: rect.h,
};
log::info!("Frame #{child_nid} size: {child_rect:?}");
self.update_layout_for(child_nid, child_rect);
}
}
}
}
_ => (),
}
}
fn remove_inner(&mut self, node_id: NodeId) -> bool {
if node_id == self.root {
return false;
}
let Some(node) = self.nodes.remove(&node_id) else {
return false;
};
let container_nid = unsafe { node.parent.unwrap_unchecked() };
let Some(container_node) = self.nodes.get_mut(&container_nid) else {
return false;
};
let NodeContent::Container(container) = &mut container_node.content else {
return false;
};
container_node.layout.clear();
container.children.retain(|&c| c != node_id);
// Only root container can be empty
debug_assert!(!(container_node.parent.is_some() && container.children.is_empty()));
if container.children.len() == 1 && container_node.parent.is_some() {
// Lift the remaining node to the parent container
todo!()
}
true
}
fn add_child_to(&mut self, container_nid: NodeId, child_nid: NodeId) -> bool {
let [Some(container_node), Some(child_node)] =
self.nodes.get_many_mut([&container_nid, &child_nid])
else {
return false;
};
let NodeContent::Container(container) = &mut container_node.content else {
return false;
};
container_node.layout.clear();
child_node.layout.clear();
child_node.parent = Some(container_nid);
container.children.push(child_nid);
true
}
}
impl Direction {
pub fn split(&self) -> (Orientation, isize) {
match self {
Self::Left => (Orientation::Horizontal, -1),
Self::Right => (Orientation::Horizontal, 1),
Self::Up => (Orientation::Vertical, -1),
Self::Down => (Orientation::Vertical, 1),
}
}
}

View File

@ -6,3 +6,4 @@ pub(crate) mod sys;
pub mod io;
pub mod net;
pub mod mem;
pub mod signal;

View File

@ -0,0 +1,6 @@
use crate::sys;
pub fn set_sigint_handler(handler: fn()) {
sys::set_sigint_handler(handler);
}

View File

@ -1,39 +1,75 @@
use std::{
ffi::c_void,
io,
ops::{Deref, DerefMut},
os::fd::{AsRawFd, OwnedFd, RawFd},
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
ptr::null_mut,
};
use crate::sys;
pub struct SharedMemoryImpl {}
pub struct SharedMemoryImpl {
fd: OwnedFd,
size: usize,
}
pub struct FileMappingImpl {}
pub struct FileMappingImpl {
fd: OwnedFd,
pointer: *mut c_void,
size: usize,
}
impl sys::SharedMemory for SharedMemoryImpl {
type Mapping = FileMappingImpl;
fn map(self) -> io::Result<Self::Mapping> {
todo!()
<FileMappingImpl as sys::FileMapping>::map(self.fd, self.size)
}
fn new(size: usize) -> io::Result<Self> {
let _ = size;
todo!()
let fd = unsafe { libc::memfd_create(c"cross-shm".as_ptr(), libc::MFD_CLOEXEC) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
if unsafe { libc::ftruncate(fd.as_raw_fd(), size as i64) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(Self { fd, size })
}
}
impl AsRawFd for SharedMemoryImpl {
fn as_raw_fd(&self) -> RawFd {
todo!()
self.fd.as_raw_fd()
}
}
impl sys::FileMapping for FileMappingImpl {
fn map<F: Into<OwnedFd>>(file: F, size: usize) -> io::Result<Self> {
let _ = file;
let _ = size;
todo!()
let fd: OwnedFd = file.into();
let pointer = unsafe {
libc::mmap(
null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd.as_raw_fd(),
0,
)
};
if pointer == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
Ok(Self { fd, pointer, size })
}
}
impl Drop for FileMappingImpl {
fn drop(&mut self) {
unsafe {
libc::munmap(self.pointer, self.size);
}
}
}
@ -41,18 +77,18 @@ impl Deref for FileMappingImpl {
type Target = [u8];
fn deref(&self) -> &Self::Target {
todo!()
unsafe { core::slice::from_raw_parts(self.pointer.cast(), self.size) }
}
}
impl DerefMut for FileMappingImpl {
fn deref_mut(&mut self) -> &mut Self::Target {
todo!()
unsafe { core::slice::from_raw_parts_mut(self.pointer.cast(), self.size) }
}
}
impl AsRawFd for FileMappingImpl {
fn as_raw_fd(&self) -> RawFd {
todo!()
self.fd.as_raw_fd()
}
}

View File

@ -6,6 +6,8 @@ pub mod socket;
pub mod term;
pub mod timer;
use std::{ffi::c_int, sync::Mutex};
pub use mem::{FileMappingImpl, SharedMemoryImpl};
pub use pid::PidFdImpl;
pub use pipe::PipeImpl;
@ -13,3 +15,17 @@ pub use poll::PollImpl;
pub use socket::{BorrowedAddressImpl, LocalPacketSocketImpl, OwnedAddressImpl};
pub use term::RawStdinImpl;
pub use timer::TimerFdImpl;
fn dummy_sigint() {}
static SIGINT_HANDLER: Mutex<fn()> = Mutex::new(dummy_sigint);
extern "C" fn sigint_proxy(_: c_int) {
let handler = *SIGINT_HANDLER.lock().unwrap();
handler();
}
pub fn set_sigint_handler(handler: fn()) {
*SIGINT_HANDLER.lock().unwrap() = handler;
unsafe { libc::signal(libc::SIGINT, sigint_proxy as usize) };
}

View File

@ -13,3 +13,6 @@ pub use pipe::PipeImpl;
pub use term::RawStdinImpl;
pub use socket::{LocalPacketSocketImpl, OwnedAddressImpl, BorrowedAddressImpl};
pub use mem::{SharedMemoryImpl, FileMappingImpl};
pub fn set_sigint_handler(_handler: fn()) {
}

View File

@ -4,6 +4,14 @@ version = "0.1.0"
edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
[[example]]
name = "bar"
required-features = ["client", "client_raqote"]
[[example]]
name = "window"
required-features = ["client", "client_raqote"]
[dependencies]
cross.workspace = true
uipc.workspace = true
@ -11,10 +19,14 @@ yggdrasil-abi.workspace = true
serde.workspace = true
thiserror.workspace = true
log.workspace = true
# client_raqote
raqote = { version = "0.8.3", default-features = false, optional = true }
[dev-dependencies]
raqote = { version = "0.8.3", default-features = false }
[features]
default = []
client_raqote = ["client", "raqote"]

View File

@ -0,0 +1,66 @@
use std::{
env,
process::ExitCode,
sync::atomic::{AtomicBool, Ordering},
};
use libcolors::{
application::{
window::{EventOutcome, Window},
Application,
},
error::Error,
message::{CreateWindowInfo, WindowType},
};
use raqote::{Color, DrawTarget, Gradient, GradientStop, Point, SolidSource, Source, Spread};
fn run_bar() -> Result<ExitCode, Error> {
let mut app = Application::new()?;
let mut window = Window::new_with_info(
&app,
CreateWindowInfo {
ty: WindowType::Reservation(32),
},
)?;
window.set_on_redraw_requested(|surface: &mut DrawTarget<&mut [u32]>| {
let source = Source::new_linear_gradient(
Gradient {
stops: vec![
GradientStop {
position: 0.0,
color: Color::new(255, 255, 0, 0),
},
GradientStop {
position: 1.0,
color: Color::new(255, 255, 255, 0),
},
],
},
Point::new(0.0, 0.0),
Point::new(0.0, surface.height() as _),
Spread::Pad,
);
surface.fill_rect(
0.0,
0.0,
surface.width() as _,
surface.height() as _,
&source,
&Default::default(),
);
});
app.add_window(window);
Ok(app.run())
}
fn main() -> ExitCode {
match run_bar() {
Ok(code) => code,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}

View File

@ -0,0 +1,61 @@
use std::{
env,
process::ExitCode,
sync::atomic::{AtomicBool, Ordering},
};
use libcolors::{
application::{
window::{EventOutcome, Window},
Application,
},
error::Error, message::{CreateWindowInfo, WindowType},
};
use raqote::{DrawTarget, SolidSource};
fn run_window() -> Result<ExitCode, Error> {
static FOCUSED: AtomicBool = AtomicBool::new(false);
println!("libcolors test application");
let mut app = Application::new()?;
let mut window = Window::new(&app)?;
window.set_on_redraw_requested(|surface: &mut DrawTarget<&mut [u32]>| {
let color = if FOCUSED.load(Ordering::Acquire) {
SolidSource::from_unpremultiplied_argb(255, 255, 0, 0)
} else {
SolidSource::from_unpremultiplied_argb(255, 0, 0, 255)
};
let border = 8;
let border_color = SolidSource::from_unpremultiplied_argb(255, 0, 255, 0);
let w = surface.width();
let h = surface.height();
surface.clear(border_color);
surface.fill_rect(
border as _,
border as _,
(w - border * 2) as _,
(h - border * 2) as _,
&color.into(),
&Default::default(),
);
});
window.set_on_focus_changed(|focused| {
FOCUSED.store(focused, Ordering::Release);
EventOutcome::Redraw
});
app.add_window(window);
Ok(app.run())
}
fn main() -> ExitCode {
match run_window() {
Ok(code) => code,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}

View File

@ -1,12 +1,10 @@
use std::{
collections::VecDeque,
os::{
fd::{AsRawFd, RawFd},
yggdrasil::io::poll::PollChannel,
},
os::fd::{AsRawFd, RawFd},
time::Duration,
};
use cross::io::Poll;
use uipc::{Channel, Receiver, Sender};
use crate::{
@ -19,7 +17,8 @@ pub struct Connection {
sender: Sender<ClientMessage>,
receiver: Receiver<ServerMessage>,
event_queue: VecDeque<Event>,
poll: PollChannel,
poll: Poll,
// poll: PollChannel,
timeout: Duration,
}
@ -28,10 +27,10 @@ impl Connection {
let channel = Channel::connect(crate::CHANNEL_NAME)?;
let (sender, receiver) = channel.split();
let timeout = Duration::from_secs(1);
let mut poll = PollChannel::new()?;
let mut poll = Poll::new()?;
let event_queue = VecDeque::new();
poll.add(receiver.as_raw_fd())?;
poll.add(&receiver)?;
Ok(Self {
sender,
@ -51,7 +50,7 @@ impl Connection {
predicate: F,
) -> Result<T, Error> {
loop {
let Some((_, Ok(_))) = self.poll.wait(Some(self.timeout), true)? else {
let Some(_) = self.poll.wait(Some(self.timeout))? else {
return Err(Error::CommunicationTimeout);
};
@ -74,11 +73,8 @@ impl Connection {
}
loop {
match self.poll.wait(Some(self.timeout), true)? {
Some((_, Ok(_))) => (),
Some((_, Err(e))) => {
todo!("Poll error: {e:?}")
}
match self.poll.wait(Some(self.timeout))? {
Some(_) => (),
None => continue,
}

View File

@ -1,9 +1,11 @@
use std::{
collections::BTreeMap,
process::ExitCode,
sync::{Arc, Mutex},
sync::{atomic::{AtomicBool, Ordering}, Arc, Mutex},
};
use cross::signal::set_sigint_handler;
use crate::{
error::Error,
event::{Event, EventData, WindowEvent},
@ -20,8 +22,16 @@ pub struct Application<'a> {
windows: BTreeMap<u32, Window<'a>>,
}
static EXIT_SIGNAL: AtomicBool = AtomicBool::new(false);
fn sigint_handler() {
EXIT_SIGNAL.store(true, Ordering::Release);
}
impl<'a> Application<'a> {
pub fn new() -> Result<Self, Error> {
set_sigint_handler(sigint_handler);
let mut connection = Connection::new()?;
connection.connect()?;
@ -37,9 +47,10 @@ impl<'a> Application<'a> {
}
fn run_inner(mut self) -> Result<ExitCode, Error> {
loop {
while !EXIT_SIGNAL.load(Ordering::Acquire) {
self.poll_events()?;
}
Ok(ExitCode::SUCCESS)
}
pub fn handle_event(&mut self, event: Event) -> Result<(), Error> {
@ -47,7 +58,7 @@ impl<'a> Application<'a> {
if let Some(window) = self.windows.get_mut(&window_id) {
window.handle_event(ev)?;
} else {
debug_trace!("Unexpected window_id received: {:?}", window_id);
log::warn!("Unknown window ID received: {window_id}");
}
}
@ -74,8 +85,8 @@ impl<'a> Application<'a> {
pub fn run(self) -> ExitCode {
match self.run_inner() {
Ok(exit) => exit,
Err(e) => {
debug_trace!("Application finished with error {:?}", e);
Err(error) => {
log::error!("Application finished with error: {error}");
ExitCode::FAILURE
}
}

View File

@ -5,7 +5,7 @@ use cross::mem::FileMapping;
use crate::{
error::Error,
event::{EventData, KeyInput, WindowEvent},
message::ClientMessage,
message::{ClientMessage, CreateWindowInfo},
};
use super::{connection::Connection, Application};
@ -15,7 +15,7 @@ pub trait OnKeyInput = Fn(KeyInput) -> EventOutcome;
pub trait OnResized = Fn(u32, u32) -> EventOutcome;
pub trait OnFocusChanged = Fn(bool) -> EventOutcome;
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
pub trait OnRedrawRequested = Fn(&mut raqote::DrawTarget<&mut [u32]>);
#[cfg(not(feature = "client_raqote"))]
@ -32,7 +32,7 @@ pub struct Window<'a> {
connection: Arc<Mutex<Connection>>,
window_id: u32,
surface_mapping: FileMapping,
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
surface_draw_target: raqote::DrawTarget<&'a mut [u32]>,
#[cfg(not(feature = "client_raqote"))]
surface_data: &'a mut [u32],
@ -47,11 +47,11 @@ pub struct Window<'a> {
on_focus_changed: Box<dyn OnFocusChanged>,
}
impl Window<'_> {
pub fn new(application: &Application) -> Result<Self, Error> {
impl<'a> Window<'a> {
pub fn new_with_info(application: &Application, info: CreateWindowInfo) -> Result<Self, Error> {
let mut connection = application.connection.lock().unwrap();
connection.send(&ClientMessage::CreateWindow)?;
connection.send(&ClientMessage::CreateWindow(info))?;
let (create_info, surface_shm_fd) = connection.filter_events(|r| match r.data {
EventData::NewWindowInfo(info) => {
@ -71,7 +71,7 @@ impl Window<'_> {
)
};
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
let surface_draw_target = raqote::DrawTarget::from_backing(
create_info.width as _,
create_info.height as _,
@ -84,7 +84,7 @@ impl Window<'_> {
width: create_info.width,
height: create_info.height,
surface_mapping,
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
surface_draw_target,
#[cfg(not(feature = "client_raqote"))]
surface_data,
@ -95,8 +95,9 @@ impl Window<'_> {
// Do nothing
EventOutcome::Destroy
}),
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
on_redraw_requested: Box::new(|dt| {
use raqote::SolidSource;
dt.clear(SolidSource::from_unpremultiplied_argb(255, 127, 127, 127));
}),
#[cfg(not(feature = "client_raqote"))]
@ -104,11 +105,15 @@ impl Window<'_> {
dt.fill(0xFF888888);
}),
on_key_input: Box::new(|_ev| EventOutcome::None),
on_resized: Box::new(|_w, _h| EventOutcome::None),
on_resized: Box::new(|_w, _h| EventOutcome::Redraw),
on_focus_changed: Box::new(|_| EventOutcome::None),
})
}
pub fn new(application: &Application) -> Result<Self, Error> {
Self::new_with_info(application, Default::default())
}
pub fn id(&self) -> u32 {
self.window_id
}
@ -138,7 +143,7 @@ impl Window<'_> {
}
pub fn redraw(&mut self) -> Result<(), Error> {
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
{
let dt = &mut self.surface_draw_target;
(self.on_redraw_requested)(dt);
@ -159,6 +164,9 @@ impl Window<'_> {
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,
@ -166,7 +174,7 @@ impl Window<'_> {
)
};
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
{
let new_draw_target =
raqote::DrawTarget::from_backing(width as _, height as _, new_surface_data);
@ -197,7 +205,7 @@ impl Window<'_> {
Ok(outcome)
}
#[cfg(feature = "client_raqote")]
#[cfg(any(feature = "client_raqote", rust_analyzer))]
pub fn as_draw_target(&mut self) -> &mut raqote::DrawTarget<&'a mut [u32]> {
&mut self.surface_draw_target
}

View File

@ -1,9 +1,11 @@
use std::os::fd::OwnedFd;
pub use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
// pub use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
use serde::{Deserialize, Serialize};
use crate::input::Key;
#[derive(Debug, Serialize, Deserialize)]
pub struct WindowInfo {
pub window_id: u32,
@ -23,16 +25,22 @@ pub struct KeyModifiers {
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyEvent {
pub modifiers: KeyModifiers,
pub key: KeyboardKey,
pub key: Key
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyInput {
pub modifiers: KeyModifiers,
pub key: KeyboardKey,
pub key: Key,
pub input: Option<char>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyboardKeyEvent {
pub key: Key,
pub state: bool
}
#[derive(Debug, Serialize, Deserialize)]
pub enum WindowEvent {
KeyPressed(KeyEvent),
@ -90,6 +98,12 @@ impl KeyModifiers {
alt: true,
};
pub const ALT_SHIFT: Self = Self {
shift: true,
ctrl: false,
alt: true,
};
pub const NONE: Self = Self {
shift: false,
ctrl: false,

View File

@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Key {
Char(u8),
LControl,
RControl,
LShift,
RShift,
LAlt,
RAlt,
PageUp,
PageDown,
Left,
Right,
Up,
Down,
Escape,
Enter,
Home,
End,
Backspace,
}

View File

@ -13,3 +13,4 @@ pub mod error;
pub mod event;
pub mod message;
pub mod input;

View File

@ -7,10 +7,22 @@ pub enum ServerMessage {
Event(EventData),
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub enum WindowType {
#[default]
Default,
Reservation(u32),
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct CreateWindowInfo {
pub ty: WindowType,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum ClientMessage {
ClientHello,
CreateWindow,
CreateWindow(CreateWindowInfo),
BlitWindow {
window_id: u32,
x: u32,

View File

@ -0,0 +1,16 @@
[package]
name = "logsink"
version = "0.1.0"
edition = "2021"
[dependencies]
log.workspace = true
[target.'cfg(unix)'.dependencies]
env_logger.workspace = true
[dev-dependencies]
env_logger.workspace = true
[lints]
workspace = true

View File

@ -0,0 +1,11 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
pub mod yggdrasil;
#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
pub use yggdrasil::*;
#[cfg(any(rust_analyzer, unix))]
pub fn setup_logging() {
env_logger::init();
}

View File

@ -0,0 +1,30 @@
use std::io::stdout;
use log::LevelFilter;
struct LogSink;
impl log::Log for LogSink {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
debug_trace!("[{}] {}", record.level(), record.args());
use std::io::Write;
let mut stdout = stdout();
writeln!(stdout, "[{}] {}", record.level(), record.args()).ok();
}
fn flush(&self) {
}
}
static SINK: LogSink = LogSink;
pub fn setup_logging() {
log::set_max_level(LevelFilter::Debug);
log::set_logger(&SINK).unwrap();
}

View File

@ -29,7 +29,7 @@ use libcolors::{
window::{EventOutcome, Window},
Application,
},
event::{KeyModifiers, KeyboardKey},
event::KeyModifiers, input::Key,
};
use state::{Cursor, State};
@ -85,7 +85,6 @@ impl DrawState {
dt.fill(default_bg);
}
if cursor_dirty {
state.buffer.set_row_dirty(self.old_cursor.row);
}
@ -232,32 +231,32 @@ impl Terminal<'_> {
need_redraw = s.scroll_end();
} else {
match (ev.modifiers, ev.key) {
(KeyModifiers::NONE, KeyboardKey::Escape) => {
(KeyModifiers::NONE, Key::Escape) => {
pty_master.write_all(b"\x1B").unwrap();
need_redraw = s.scroll_end();
}
(KeyModifiers::NONE, KeyboardKey::Backspace) => {
(KeyModifiers::NONE, Key::Backspace) => {
pty_master.write_all(&[termios.chars.erase]).unwrap();
need_redraw = s.scroll_end();
}
(KeyModifiers::CTRL, KeyboardKey::Char(b'c')) => {
(KeyModifiers::CTRL, Key::Char(b'c')) => {
pty_master.write_all(&[termios.chars.interrupt]).unwrap();
need_redraw = s.scroll_end();
}
(KeyModifiers::CTRL, KeyboardKey::Char(b'd')) => {
(KeyModifiers::CTRL, Key::Char(b'd')) => {
pty_master.write_all(&[termios.chars.eof]).unwrap();
need_redraw = s.scroll_end();
}
(KeyModifiers::SHIFT, KeyboardKey::PageUp) => {
(KeyModifiers::SHIFT, Key::PageUp) => {
need_redraw = s.scroll_up();
}
(KeyModifiers::SHIFT, KeyboardKey::PageDown) => {
(KeyModifiers::SHIFT, Key::PageDown) => {
need_redraw = s.scroll_down();
}
(KeyModifiers::SHIFT, KeyboardKey::Home) => {
(KeyModifiers::SHIFT, Key::Home) => {
need_redraw = s.scroll_home();
}
(KeyModifiers::SHIFT, KeyboardKey::End) => {
(KeyModifiers::SHIFT, Key::End) => {
need_redraw = s.scroll_end();
}
_ => (),

View File

@ -181,6 +181,9 @@ impl Buffer {
}
pub fn set_row_dirty(&mut self, row: usize) {
if row >= self.rows.len() {
return;
}
self.rows[row].dirty = true;
}