Skip to content

Commit ac10a1b

Browse files
committed
Basic mouse-based paint program
1 parent bb56053 commit ac10a1b

9 files changed

Lines changed: 269 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ members = [
1414
"crates/initfs",
1515
"crates/kernel",
1616
"crates/lz4",
17+
"crates/paint",
1718
"crates/shell",
1819
"crates/ulib",
1920
]

crates/display-server/src/main.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,11 @@ fn handle_conns(mut fb: framebuffer::Framebuffer, server_socket: FileDesc) {
547547
let queue = client.handle.server_to_client_queue();
548548
queue.try_send_data(event).ok();
549549
}
550-
HoveredState::None => (),
550+
HoveredState::None => {
551+
if button == 3 && pressed {
552+
spawn_paint();
553+
}
554+
}
551555
_ => (),
552556
}
553557
}
@@ -810,6 +814,22 @@ fn spawn_console() {
810814
.unwrap();
811815
}
812816

817+
fn spawn_paint() {
818+
let path = b"/paint";
819+
let file = ulib::sys::openat(3, path, 0, 0).unwrap();
820+
ulib::sys::spawn_elf(&ulib::sys::SpawnArgs {
821+
fd: file,
822+
stdin: None,
823+
stdout: None,
824+
stderr: None,
825+
args: &[ulib::sys::ArgStr {
826+
len: path.len(),
827+
ptr: path.as_ptr(),
828+
}],
829+
})
830+
.unwrap();
831+
}
832+
813833
fn remap_keycode(code: isize) -> proto::ScanCode {
814834
use proto::ScanCode;
815835
match code {

crates/kernel/scripts/compile-init.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ FS_PATH="../../disk-image"
88
DESTDIR="$FS_PATH" ../shell/build.sh
99
DESTDIR="$FS_PATH" ../console/build.sh
1010
DESTDIR="$FS_PATH" ../display-server/build.sh
11+
DESTDIR="$FS_PATH" ../paint/build.sh
1112
../init/build.sh

crates/paint/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.elf

crates/paint/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "paint"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
bytemuck = { version = "1.19.0", default-features = false }
8+
display-client = { path = "../display-client" }
9+
lz4 = { path = "../lz4" }
10+
ulib = { path = "../ulib", features = ["heap-impl"] }
11+
gfx = { path = "../gfx" }
12+
glam = { version = "0.30", default-features = false, features = ["nostd-libm"] }
13+
14+
[dev-dependencies]
15+
ulib = { path = "../ulib", features = ["test"] }

crates/paint/build.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
let crate_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
3+
println!("cargo::rerun-if-changed=../ulib/script.ld");
4+
println!("cargo::rustc-link-arg-bins=-T{crate_root}/../ulib/script.ld");
5+
println!("cargo::rustc-link-arg-bins=-n");
6+
}

crates/paint/build.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
3+
set -ex
4+
cd "$(dirname "$0")"
5+
6+
BIN="paint"
7+
TARGET=aarch64-unknown-none-softfloat
8+
PROFILE=${PROFILE-"release"}
9+
10+
cargo rustc --profile="${PROFILE}" \
11+
--target="${TARGET}" \
12+
--bin="${BIN}" \
13+
-- -C relocation-model=static
14+
15+
if test "$PROFILE" = "dev" ; then
16+
BINARY=../../target/${TARGET}/debug/${BIN}
17+
else
18+
BINARY=../../target/${TARGET}/${PROFILE}/${BIN}
19+
fi
20+
21+
cp "${BINARY}" "${BIN}".elf
22+
23+
if test -n "$DESTDIR" ; then
24+
cp "${BINARY}" "$DESTDIR/"
25+
fi

crates/paint/src/main.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#![no_std]
2+
#![cfg_attr(not(test), no_main)]
3+
4+
extern crate alloc;
5+
extern crate display_client;
6+
7+
#[allow(unused_imports)]
8+
#[macro_use]
9+
extern crate ulib;
10+
11+
use core::ops::ControlFlow;
12+
13+
use display_client::proto;
14+
use glam::IVec2;
15+
16+
fn draw_pixel(fb: &mut [u32], stride: usize, pos: IVec2, color: u32) {
17+
let height = fb.len() as i32 / stride as i32;
18+
let pos = pos.clamp(IVec2::ZERO, IVec2::new(stride as i32, height as i32));
19+
fb[(pos.y as usize * stride + pos.x as usize).clamp(0, fb.len() - 1)] = color;
20+
}
21+
22+
fn get_pixel(fb: &mut [u32], stride: usize, pos: IVec2) -> u32 {
23+
let height = fb.len() as i32 / stride as i32;
24+
let pos = pos.clamp(IVec2::ZERO, IVec2::new(stride as i32, height as i32));
25+
fb[(pos.y as usize * stride + pos.x as usize).clamp(0, fb.len() - 1)]
26+
}
27+
28+
fn draw_circle(fb: &mut [u32], stride: usize, pos: IVec2, color: u32, r: u32) {
29+
let r = r as i32;
30+
for x in -r..=r {
31+
for y in -r..=r {
32+
if x * x + y * y <= r {
33+
draw_pixel(fb, stride, pos + IVec2::new(x, y), color);
34+
}
35+
}
36+
}
37+
}
38+
39+
#[no_mangle]
40+
fn main(argc: usize, argv: *const *const u8) {
41+
let argv_array = unsafe { core::slice::from_raw_parts(argv, argc) };
42+
let _args = argv_array
43+
.iter()
44+
.copied()
45+
.map(|arg| unsafe { core::ffi::CStr::from_ptr(arg) }.to_bytes())
46+
.map(|arg| core::str::from_utf8(arg).unwrap())
47+
.collect::<alloc::vec::Vec<_>>();
48+
49+
let mut buf = display_client::connect(512, 384);
50+
buf.set_title("paint".as_bytes());
51+
52+
let (width, height) = (
53+
buf.video_meta.width as usize,
54+
buf.video_meta.height as usize,
55+
);
56+
let row_stride = buf.video_meta.row_stride as usize / 4;
57+
58+
let mut down = false;
59+
let mut pos = IVec2::new(0, 0);
60+
let mut radius = 3;
61+
let mut color = 0xFF000000;
62+
63+
buf.video_mem().fill(0xFFFFFFFF);
64+
65+
let color_palette = [
66+
0xFF000000, 0xFFFFFFFF, 0xFF464646, 0xFFDCDCDC, 0xFF787878, 0xFFB4B4B4, 0xFF990030,
67+
0xFF9C5A3C, 0xFFED1C24, 0xFFFFA3B1, 0xFFFF7E00, 0xFFE5AA7A, 0xFFFFC20E, 0xFFF5E49C,
68+
0xFFFFF200, 0xFFFFF9BD, 0xFFA8E61D, 0xFFD3F9BC, 0xFF22B14C, 0xFF9DBB61, 0xFF00B7EF,
69+
0xFF99D9EA, 0xFF4D6DF3, 0xFF709AD1, 0xFF2F3699, 0xFF546D8E, 0xFF6F3198, 0xFFB5A5D5,
70+
];
71+
72+
let fb = buf.video_mem();
73+
let palette_height = 16;
74+
let palette_elems = 14;
75+
for r in height - palette_height..height {
76+
for c in 0..width {
77+
let idx = (c * palette_elems) / width;
78+
fb[r * row_stride + c] = color_palette[idx * 2];
79+
}
80+
}
81+
for r in height - 2 * palette_height..height - palette_height {
82+
for c in 0..width {
83+
let idx = (c * palette_elems) / width;
84+
fb[r * row_stride + c] = color_palette[idx * 2 + 1];
85+
}
86+
}
87+
88+
'outer: loop {
89+
while let Some(ev) = buf.server_to_client_queue().try_recv() {
90+
match ev.kind {
91+
proto::EventKind::INPUT => {
92+
use proto::EventData;
93+
let data = proto::InputEvent::parse(&ev).expect("TODO");
94+
if let ControlFlow::Break(_) = handle_input(
95+
data,
96+
&mut buf,
97+
row_stride,
98+
&mut down,
99+
&mut pos,
100+
&mut color,
101+
&mut radius,
102+
) {
103+
break 'outer;
104+
}
105+
}
106+
proto::EventKind::REQUEST_CLOSE => {
107+
break 'outer;
108+
}
109+
_ => (),
110+
}
111+
}
112+
113+
buf.client_to_server_queue()
114+
.try_send(proto::Event {
115+
kind: proto::EventKind::PRESENT,
116+
data: [0; 7],
117+
})
118+
.ok();
119+
120+
// signal(video)? for sync
121+
ulib::sys::sem_down(buf.get_sem_fd(buf.present_sem)).unwrap();
122+
}
123+
124+
buf.client_to_server_queue()
125+
.try_send(proto::Event {
126+
kind: proto::EventKind::DISCONNECT,
127+
data: [0; 7],
128+
})
129+
.ok();
130+
}
131+
132+
fn handle_input(
133+
data: proto::InputEvent,
134+
buf: &mut proto::BufferHandle,
135+
row_stride: usize,
136+
down: &mut bool,
137+
pos: &mut IVec2,
138+
color: &mut u32,
139+
radius: &mut u32,
140+
) -> ControlFlow<()> {
141+
if data.kind == proto::InputEvent::KIND_KEY {
142+
match proto::ScanCode(data.data2) {
143+
proto::ScanCode::ESCAPE | proto::ScanCode::Q => {
144+
return ControlFlow::Break(());
145+
}
146+
_ => (),
147+
}
148+
} else if data.kind == proto::InputEvent::KIND_MOUSE {
149+
let mode = data.data1;
150+
let x = data.data2 as i32;
151+
let y = data.data3 as i32;
152+
let new_pos = IVec2::new(x, y);
153+
let button = data.data4;
154+
match mode {
155+
proto::InputEvent::MODE_MOUSE_MOVE => {
156+
if *down {
157+
let distance = (new_pos - *pos).abs().max_element();
158+
// TODO: sqrt
159+
for i in (0..=128).step_by((128 / distance.max(1) as usize).max(1)) {
160+
let p = (*pos * (128 - i) + new_pos * i) / 128;
161+
draw_circle(buf.video_mem(), row_stride, p, *color, *radius);
162+
}
163+
}
164+
}
165+
proto::InputEvent::MODE_MOUSE_DOWN => {
166+
if button == 1 {
167+
*down = true;
168+
draw_circle(buf.video_mem(), row_stride, new_pos, *color, *radius);
169+
}
170+
if button == 3 {
171+
*color = get_pixel(buf.video_mem(), row_stride, new_pos);
172+
}
173+
}
174+
proto::InputEvent::MODE_MOUSE_UP => {
175+
if button == 1 {
176+
*down = false;
177+
}
178+
}
179+
_ => (),
180+
}
181+
*pos = new_pos;
182+
} else if data.kind == proto::InputEvent::KIND_SCROLL {
183+
// TODO: log scale using only integers
184+
*radius = radius.saturating_add_signed(data.data1 as i32);
185+
}
186+
ControlFlow::Continue(())
187+
}

0 commit comments

Comments
 (0)