diff --git a/Cargo.lock b/Cargo.lock index 5963f6e7..73a8b213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,18 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" name = "lz4" version = "0.1.0" +[[package]] +name = "paint" +version = "0.1.0" +dependencies = [ + "bytemuck", + "display-client", + "gfx", + "glam", + "lz4", + "ulib", +] + [[package]] name = "scopeguard" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 0f92be11..61a16844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/initfs", "crates/kernel", "crates/lz4", + "crates/paint", "crates/shell", "crates/show", "crates/ulib", diff --git a/crates/display-server/src/main.rs b/crates/display-server/src/main.rs index 675b7a82..aabb8b56 100644 --- a/crates/display-server/src/main.rs +++ b/crates/display-server/src/main.rs @@ -547,7 +547,11 @@ fn handle_conns(mut fb: framebuffer::Framebuffer, server_socket: FileDesc) { let queue = client.handle.server_to_client_queue(); queue.try_send_data(event).ok(); } - HoveredState::None => (), + HoveredState::None => { + if button == 3 && pressed { + spawn_paint(); + } + } _ => (), } } @@ -810,6 +814,22 @@ fn spawn_console() { .unwrap(); } +fn spawn_paint() { + let path = b"/paint"; + let file = ulib::sys::openat(3, path, 0, 0).unwrap(); + ulib::sys::spawn_elf(&ulib::sys::SpawnArgs { + fd: file, + stdin: None, + stdout: None, + stderr: None, + args: &[ulib::sys::ArgStr { + len: path.len(), + ptr: path.as_ptr(), + }], + }) + .unwrap(); +} + fn remap_keycode(code: isize) -> proto::ScanCode { use proto::ScanCode; match code { diff --git a/crates/kernel/scripts/compile-init.sh b/crates/kernel/scripts/compile-init.sh index 8288ac90..c2a6ab7f 100755 --- a/crates/kernel/scripts/compile-init.sh +++ b/crates/kernel/scripts/compile-init.sh @@ -8,5 +8,6 @@ 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" ../paint/build.sh DESTDIR="$FS_PATH" ../show/build.sh ../init/build.sh diff --git a/crates/paint/.gitignore b/crates/paint/.gitignore new file mode 100644 index 00000000..50992530 --- /dev/null +++ b/crates/paint/.gitignore @@ -0,0 +1 @@ +*.elf diff --git a/crates/paint/Cargo.toml b/crates/paint/Cargo.toml new file mode 100644 index 00000000..6be2d58d --- /dev/null +++ b/crates/paint/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "paint" +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" } +glam = { version = "0.30", default-features = false, features = ["nostd-libm"] } + +[dev-dependencies] +ulib = { path = "../ulib", features = ["test"] } diff --git a/crates/paint/build.rs b/crates/paint/build.rs new file mode 100644 index 00000000..fe806e18 --- /dev/null +++ b/crates/paint/build.rs @@ -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"); +} diff --git a/crates/paint/build.sh b/crates/paint/build.sh new file mode 100755 index 00000000..670b56c7 --- /dev/null +++ b/crates/paint/build.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -ex +cd "$(dirname "$0")" + +BIN="paint" +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 diff --git a/crates/paint/src/main.rs b/crates/paint/src/main.rs new file mode 100644 index 00000000..465d53a3 --- /dev/null +++ b/crates/paint/src/main.rs @@ -0,0 +1,187 @@ +#![no_std] +#![cfg_attr(not(test), no_main)] + +extern crate alloc; +extern crate display_client; + +#[allow(unused_imports)] +#[macro_use] +extern crate ulib; + +use core::ops::ControlFlow; + +use display_client::proto; +use glam::IVec2; + +fn draw_pixel(fb: &mut [u32], stride: usize, pos: IVec2, color: u32) { + let height = fb.len() as i32 / stride as i32; + let pos = pos.clamp(IVec2::ZERO, IVec2::new(stride as i32, height as i32)); + fb[(pos.y as usize * stride + pos.x as usize).clamp(0, fb.len() - 1)] = color; +} + +fn get_pixel(fb: &mut [u32], stride: usize, pos: IVec2) -> u32 { + let height = fb.len() as i32 / stride as i32; + let pos = pos.clamp(IVec2::ZERO, IVec2::new(stride as i32, height as i32)); + fb[(pos.y as usize * stride + pos.x as usize).clamp(0, fb.len() - 1)] +} + +fn draw_circle(fb: &mut [u32], stride: usize, pos: IVec2, color: u32, r: u32) { + let r = r as i32; + for x in -r..=r { + for y in -r..=r { + if x * x + y * y <= r { + draw_pixel(fb, stride, pos + IVec2::new(x, y), color); + } + } + } +} + +#[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::>(); + + let mut buf = display_client::connect(512, 384); + buf.set_title("paint".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; + + let mut down = false; + let mut pos = IVec2::new(0, 0); + let mut radius = 3; + let mut color = 0xFF000000; + + buf.video_mem().fill(0xFFFFFFFF); + + let color_palette = [ + 0xFF000000, 0xFFFFFFFF, 0xFF464646, 0xFFDCDCDC, 0xFF787878, 0xFFB4B4B4, 0xFF990030, + 0xFF9C5A3C, 0xFFED1C24, 0xFFFFA3B1, 0xFFFF7E00, 0xFFE5AA7A, 0xFFFFC20E, 0xFFF5E49C, + 0xFFFFF200, 0xFFFFF9BD, 0xFFA8E61D, 0xFFD3F9BC, 0xFF22B14C, 0xFF9DBB61, 0xFF00B7EF, + 0xFF99D9EA, 0xFF4D6DF3, 0xFF709AD1, 0xFF2F3699, 0xFF546D8E, 0xFF6F3198, 0xFFB5A5D5, + ]; + + let fb = buf.video_mem(); + let palette_height = 16; + let palette_elems = 14; + for r in height - palette_height..height { + for c in 0..width { + let idx = (c * palette_elems) / width; + fb[r * row_stride + c] = color_palette[idx * 2]; + } + } + for r in height - 2 * palette_height..height - palette_height { + for c in 0..width { + let idx = (c * palette_elems) / width; + fb[r * row_stride + c] = color_palette[idx * 2 + 1]; + } + } + + '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 let ControlFlow::Break(_) = handle_input( + data, + &mut buf, + row_stride, + &mut down, + &mut pos, + &mut color, + &mut radius, + ) { + break 'outer; + } + } + proto::EventKind::REQUEST_CLOSE => { + break 'outer; + } + _ => (), + } + } + + 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(); +} + +fn handle_input( + data: proto::InputEvent, + buf: &mut proto::BufferHandle, + row_stride: usize, + down: &mut bool, + pos: &mut IVec2, + color: &mut u32, + radius: &mut u32, +) -> ControlFlow<()> { + if data.kind == proto::InputEvent::KIND_KEY { + match proto::ScanCode(data.data2) { + proto::ScanCode::ESCAPE | proto::ScanCode::Q => { + return ControlFlow::Break(()); + } + _ => (), + } + } else if data.kind == proto::InputEvent::KIND_MOUSE { + let mode = data.data1; + let x = data.data2 as i32; + let y = data.data3 as i32; + let new_pos = IVec2::new(x, y); + let button = data.data4; + match mode { + proto::InputEvent::MODE_MOUSE_MOVE => { + if *down { + let distance = (new_pos - *pos).abs().max_element(); + // TODO: sqrt + for i in (0..=128).step_by((128 / distance.max(1) as usize).max(1)) { + let p = (*pos * (128 - i) + new_pos * i) / 128; + draw_circle(buf.video_mem(), row_stride, p, *color, *radius); + } + } + } + proto::InputEvent::MODE_MOUSE_DOWN => { + if button == 1 { + *down = true; + draw_circle(buf.video_mem(), row_stride, new_pos, *color, *radius); + } + if button == 3 { + *color = get_pixel(buf.video_mem(), row_stride, new_pos); + } + } + proto::InputEvent::MODE_MOUSE_UP => { + if button == 1 { + *down = false; + } + } + _ => (), + } + *pos = new_pos; + } else if data.kind == proto::InputEvent::KIND_SCROLL { + // TODO: log scale using only integers + *radius = radius.saturating_add_signed(data.data1 as i32); + } + ControlFlow::Continue(()) +}