colors: rewrite colors, hosted testing support
This commit is contained in:
parent
82175f342e
commit
f605b0a80c
1940
userspace/Cargo.lock
generated
1940
userspace/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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)'] }
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
66
userspace/colors/src/sys/mod.rs
Normal file
66
userspace/colors/src/sys/mod.rs
Normal 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();
|
||||
}
|
||||
}
|
262
userspace/colors/src/sys/unix.rs
Normal file
262
userspace/colors/src/sys/unix.rs
Normal 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 })
|
||||
}
|
278
userspace/colors/src/sys/yggdrasil.rs
Normal file
278
userspace/colors/src/sys/yggdrasil.rs
Normal 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
|
||||
})
|
||||
}
|
23
userspace/colors/src/window.rs
Normal file
23
userspace/colors/src/window.rs
Normal 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;
|
||||
}
|
||||
}
|
691
userspace/colors/src/wm/mod.rs
Normal file
691
userspace/colors/src/wm/mod.rs
Normal 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(¤t_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(¤t_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(¤t_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),
|
||||
}
|
||||
}
|
||||
}
|
@ -6,3 +6,4 @@ pub(crate) mod sys;
|
||||
pub mod io;
|
||||
pub mod net;
|
||||
pub mod mem;
|
||||
pub mod signal;
|
||||
|
6
userspace/lib/cross/src/signal.rs
Normal file
6
userspace/lib/cross/src/signal.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use crate::sys;
|
||||
|
||||
|
||||
pub fn set_sigint_handler(handler: fn()) {
|
||||
sys::set_sigint_handler(handler);
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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) };
|
||||
}
|
||||
|
@ -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()) {
|
||||
}
|
||||
|
@ -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"]
|
||||
|
66
userspace/lib/libcolors/examples/bar.rs
Normal file
66
userspace/lib/libcolors/examples/bar.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
61
userspace/lib/libcolors/examples/window.rs
Normal file
61
userspace/lib/libcolors/examples/window.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
24
userspace/lib/libcolors/src/input.rs
Normal file
24
userspace/lib/libcolors/src/input.rs
Normal 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,
|
||||
}
|
@ -13,3 +13,4 @@ pub mod error;
|
||||
|
||||
pub mod event;
|
||||
pub mod message;
|
||||
pub mod input;
|
||||
|
@ -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,
|
||||
|
16
userspace/lib/logsink/Cargo.toml
Normal file
16
userspace/lib/logsink/Cargo.toml
Normal 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
|
11
userspace/lib/logsink/src/lib.rs
Normal file
11
userspace/lib/logsink/src/lib.rs
Normal 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();
|
||||
}
|
30
userspace/lib/logsink/src/yggdrasil.rs
Normal file
30
userspace/lib/logsink/src/yggdrasil.rs
Normal 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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
_ => (),
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user