Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ members = [
"crates/kernel",
"crates/lz4",
"crates/shell",
"crates/show",
"crates/ulib",
]

Expand Down
1 change: 1 addition & 0 deletions crates/kernel/scripts/compile-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ FS_PATH="../../disk-image"
DESTDIR="$FS_PATH" ../shell/build.sh
DESTDIR="$FS_PATH" ../console/build.sh
DESTDIR="$FS_PATH" ../display-server/build.sh
DESTDIR="$FS_PATH" ../show/build.sh
../init/build.sh
2 changes: 2 additions & 0 deletions crates/kernel/scripts/run-usb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ QEMU_DEVICES="-usb -device usb-kbd -device usb-mouse
-device usb-net,netdev=net0 -netdev user,id=net0,hostfwd=tcp::2222-:22" \
"$(dirname "$0")/run.sh"

#QEMU_DISPLAY=${QEMU_DISPLAY-"default"} QEMU_DEVICES="-usb -device usb-kbd -device usb-mouse" "$(dirname "$0")/run.sh"
#QEMU_DISPLAY=${QEMU_DISPLAY-"default"} QEMU_DEVICES="-usb -device usb-kbd -device usb-mouse -device usb-net,netdev=net0 -netdev user,id=net0,hostfwd=tcp::2222-:22 -object filter-dump,id=f1,netdev=net0,file=net0.pcap" "$(dirname "$0")/run.sh"
# QEMU_DISPLAY=${QEMU_DISPLAY-"default"} QEMU_DEVICES="-usb -device usb-kbd -device usb-mouse -device usb-net,netdev=net0 -netdev tap,id=net0,ifname=tap0,script=no,downscript=no -object filter-dump,id=f1,netdev=net0,file=net0.pcap" "$(dirname "$0")/run.sh"

# QEMU_DISPLAY=${QEMU_DISPLAY-"default"} QEMU_DEVICES="-usb -device usb-kbd -device usb-mouse -device usb-net,netdev=net0 -netdev bridge,id=net0,br=br0 -object filter-dump,id=f1,netdev=net0,file=net0.pcap" "$(dirname "$0")/run.sh"
Expand Down
1 change: 1 addition & 0 deletions crates/show/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.elf
14 changes: 14 additions & 0 deletions crates/show/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "show"
version = "0.1.0"
edition = "2021"

[dependencies]
bytemuck = { version = "1.19.0", default-features = false }
display-client = { path = "../display-client" }
lz4 = { path = "../lz4" }
ulib = { path = "../ulib", features = ["heap-impl"] }
gfx = { path = "../gfx" }

[dev-dependencies]
ulib = { path = "../ulib", features = ["test"] }
6 changes: 6 additions & 0 deletions crates/show/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
let crate_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo::rerun-if-changed=../ulib/script.ld");
println!("cargo::rustc-link-arg-bins=-T{crate_root}/../ulib/script.ld");
println!("cargo::rustc-link-arg-bins=-n");
}
25 changes: 25 additions & 0 deletions crates/show/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

set -ex
cd "$(dirname "$0")"

BIN="show"
TARGET=aarch64-unknown-none-softfloat
PROFILE=${PROFILE-"release"}

cargo rustc --profile="${PROFILE}" \
--target="${TARGET}" \
--bin="${BIN}" \
-- -C relocation-model=static

if test "$PROFILE" = "dev" ; then
BINARY=../../target/${TARGET}/debug/${BIN}
else
BINARY=../../target/${TARGET}/${PROFILE}/${BIN}
fi

cp "${BINARY}" "${BIN}".elf

if test -n "$DESTDIR" ; then
cp "${BINARY}" "$DESTDIR/"
fi
243 changes: 243 additions & 0 deletions crates/show/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
#![no_std]
#![cfg_attr(not(test), no_main)]

extern crate alloc;
extern crate display_client;

#[macro_use]
extern crate ulib;

use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use display_client::proto;

use ulib::sys::FileDesc;

// TODO: stat for file length
fn read_all(fd: FileDesc) -> alloc::vec::Vec<u8> {
let mut out = alloc::vec::Vec::new();
let mut buf = [0u8; 512];
let mut offset = 0;
loop {
match ulib::sys::pread(fd, &mut buf, offset) {
Ok(0) => break,
Ok(len) => {
out.extend(&buf[..len]);
offset += len as u64;
}
Err(e) => {
println!("Error reading file: {e}");
ulib::sys::exit(1);
}
}
}
out
}

fn load_image(file: u32) -> (usize, usize, Vec<u32>) {
let img = read_all(file);
let (header, data) = gfx::format::qoi::read_qoi_header(&img).unwrap();
let width = header.width as usize;
let height = header.height as usize;
let mut output = alloc::vec![0u32; width * height];
gfx::format::qoi::decode_qoi(&header, data, &mut output, width);

(width, height, output)
}

fn list_dir(dir: u32) -> Vec<String> {
let mut cookie = 0;
let mut data_backing = [0u64; 8192 / 8];
let data = cast_slice(&mut data_backing);

fn cast_slice<'a>(s: &'a mut [u64]) -> &'a mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(s.as_mut_ptr().cast::<u8>(), s.len() * size_of::<u64>())
}
}

#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct DirEntry {
pub inode: u64,
pub next_entry_cookie: u64,
pub rec_len: u16,
pub name_len: u16,
pub file_type: u8,
pub name: [u8; 3],
// Name is an arbitrary size array; the record is always padded with
// 0 bytes such that rec_len is a multiple of 8 bytes.
}

let mut filenames = Vec::new();

'outer: loop {
match ulib::sys::pread(dir, data, cookie) {
Err(e) => {
println!("Error reading dir: {e}");
ulib::sys::exit(1);
}
Ok(0) => break,
Ok(len) => {
let mut i = 0;
while i < len as usize {
let slice = &data[i..];
assert!(slice.len() >= size_of::<DirEntry>());
let entry = unsafe { *slice.as_ptr().cast::<DirEntry>() };

let name_off = core::mem::offset_of!(DirEntry, name);
let name = &slice[name_off..][..entry.name_len as usize];

let name = core::str::from_utf8(name).unwrap();
filenames.push(name.to_owned());

i += entry.rec_len as usize;
cookie = entry.next_entry_cookie;
}
if cookie == 0 {
break 'outer;
}
}
}
}

filenames
}

#[no_mangle]
fn main(argc: usize, argv: *const *const u8) {
let argv_array = unsafe { core::slice::from_raw_parts(argv, argc) };
let args = argv_array
.iter()
.copied()
.map(|arg| unsafe { core::ffi::CStr::from_ptr(arg) }.to_bytes())
.map(|arg| core::str::from_utf8(arg).unwrap())
.collect::<alloc::vec::Vec<_>>();

let file = args[1];

let mut dir_fd = 3;
let mut idx = 0;
let mut files = Vec::new();

let mut img_data;
let mut img_width;
let mut img_height;

let mut last_idx = idx;

if file.ends_with("qoi") {
files.push(file.to_owned());
let Ok(file) = ulib::sys::openat(3, file.as_bytes(), 0, 0) else {
println!("Error opening file {}", file);
ulib::sys::exit(1);
};
(img_width, img_height, img_data) = load_image(file);
} else if let Ok(dir) = ulib::sys::openat(3, alloc::format!("{file}/").as_bytes(), 0, 0) {
files = list_dir(dir);
files.retain(|f| f.ends_with("qoi"));
files.sort();
println!("Loaded files: {:?}", files);
dir_fd = dir;

let Ok(file) = ulib::sys::openat(dir_fd, files[0].as_bytes(), 0, 0) else {
println!("Error opening file {}", file);
ulib::sys::exit(1);
};
(img_width, img_height, img_data) = load_image(file);
} else {
println!("Unknown file format, exiting.");
ulib::sys::exit(1);
}

let mut buf = display_client::connect(img_width as u16, img_height as u16);
buf.set_title(alloc::format!("show - {}", file).as_bytes());

let (width, height) = (
buf.video_meta.width as usize,
buf.video_meta.height as usize,
);
let row_stride = buf.video_meta.row_stride as usize / 4;

'outer: loop {
while let Some(ev) = buf.server_to_client_queue().try_recv() {
match ev.kind {
proto::EventKind::INPUT => {
use proto::EventData;
let data = proto::InputEvent::parse(&ev).expect("TODO");
if data.kind == proto::InputEvent::KIND_KEY && data.data1 == 1 {
match proto::ScanCode(data.data2) {
proto::ScanCode::ESCAPE | proto::ScanCode::Q => {
break 'outer;
}
proto::ScanCode::RIGHT => {
idx = (idx + 1) % files.len();
}
proto::ScanCode::LEFT => {
idx = (idx + files.len() - 1) % files.len();
}
_ => (),
}
} else if data.kind == proto::InputEvent::KIND_MOUSE && data.data1 == 2 {
match data.data4 {
1 => {
// Mouse1 down
idx = (idx + 1) % files.len();
}
2 => {
// Mouse2 down
idx = (idx + files.len() - 1) % files.len();
}
_ => (),
}
}
}
proto::EventKind::REQUEST_CLOSE => {
break 'outer;
}
_ => (),
}
}

idx = idx % files.len();
if idx != last_idx {
let start = unsafe { ulib::sys::sys_get_time_ms() };
if let Ok(file) = ulib::sys::openat(dir_fd, files[idx].as_bytes(), 0, 0) {
drop(img_data);
(img_width, img_height, img_data) = load_image(file);
let end = unsafe { ulib::sys::sys_get_time_ms() };
println!("Loading image took {}ms", end - start);
last_idx = idx;
} else {
println!("Error opening file {}", file);
idx = last_idx;
}
}

let x = width.saturating_sub(img_width) / 2;
let y = height.saturating_sub(img_height) / 2;

let fb = buf.video_mem();
gfx::blit_buffer(
fb, width, height, row_stride, x, y, &img_data, img_width, img_height, img_width,
);

buf.client_to_server_queue()
.try_send(proto::Event {
kind: proto::EventKind::PRESENT,
data: [0; 7],
})
.ok();

// signal(video)? for sync
ulib::sys::sem_down(buf.get_sem_fd(buf.present_sem)).unwrap();
}

buf.client_to_server_queue()
.try_send(proto::Event {
kind: proto::EventKind::DISCONNECT,
data: [0; 7],
})
.ok();
}
Loading