Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
713 changes: 703 additions & 10 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ members = [
"./packages/protocol",
"./packages/client-cli",
"./packages/client/src-tauri",
"./packages/display",
]
exclude = [
"./packages/display", # broken for now
"./packages/kernel", # makes rust-analyzer really slow
]
resolver = "2"
Expand Down
Binary file added output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion packages/client-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ miette = { version = "7.2.0", features = ["fancy"] }
clap = { version = "4.5.1", features = ["derive", "env"] }
clap-num = "1.2.0"
anyhow = "1.0.86"
bincode = "2.0.0-rc.3"
bincode = "2.0.1"
vex-v5-qemu-host = { path = "../host" }
thiserror = "1.0.63"
log = "0.4.22"
simplelog = "0.12.2"
typed_shmem = "0.3.0"
winit = "0.30.12"
softbuffer = "0.4.6"
tiny-skia = "0.11.4"


[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "windows", target_os = "dragonfly", target_os = "freebsd"))'.dependencies]
battery = "0.7.8"
147 changes: 147 additions & 0 deletions packages/client-cli/src/display_window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use std::{num::NonZeroU32, sync::Arc};

use softbuffer::Surface;
use tiny_skia::{PixmapMut, PixmapPaint, Transform};
use tokio::{runtime::Handle, task::AbortHandle};
use vex_v5_qemu_host::{peripherals::{display::Display, touch::Touchscreen}, protocol::{geometry::Point2, touch::{TouchData, TouchEvent}}};
use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
event::{ElementState, MouseButton, TouchPhase, WindowEvent},
event_loop::ActiveEventLoop,
window::{Window, WindowId},
};

pub struct DisplayWindow {
window: Option<Arc<Window>>,
task: Option<AbortHandle>,
display: Option<Display>,
touch: Touchscreen,
}

impl DisplayWindow {
pub const fn new(display: Display, touch: Touchscreen) -> Self {
Self {
window: None,
task: None,
display: Some(display),
touch,
}
}
}

impl ApplicationHandler for DisplayWindow {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = Window::default_attributes().with_title("Display");
let win = Arc::new(event_loop.create_window(window_attributes).unwrap());
win.set_resizable(false);
win.set_max_inner_size(Some(PhysicalSize::new(Display::WIDTH, Display::HEIGHT)));
win.set_min_inner_size(Some(PhysicalSize::new(Display::WIDTH, Display::HEIGHT)));
self.window = Some(win.clone());

let mut display = self.display.take().unwrap(); // TODO: may need to bail out if this fails instead of unwrap
let mut surface =
Surface::new(&softbuffer::Context::new(win.clone()).unwrap(), win.clone()).unwrap();
self.task = Some(
Handle::current()
.spawn(async move {
while let Some(frame) = display.next_frame().await {
surface
.resize(
NonZeroU32::new(Display::WIDTH).unwrap(),
NonZeroU32::new(Display::HEIGHT).unwrap(),
)
.unwrap();

let mut surface_buffer = surface.buffer_mut().unwrap();
let surface_buffer_u8 = unsafe {
std::slice::from_raw_parts_mut(
surface_buffer.as_mut_ptr() as *mut u8,
surface_buffer.len() * 4,
)
};

let mut pixmap = PixmapMut::from_bytes(
surface_buffer_u8,
Display::WIDTH,
Display::HEIGHT,
)
.unwrap();

pixmap.draw_pixmap(
0,
0,
frame.as_ref(),
&PixmapPaint::default(),
Transform::identity(),
None,
);

// convert tiny_skia pixmap color format to softbuffer compatible format
for index in 0..(Display::WIDTH * Display::HEIGHT) as usize {
let data = pixmap.data_mut();
surface_buffer[index] = data[index * 4 + 2] as u32
| (data[index * 4 + 1] as u32) << 8
| (data[index * 4] as u32) << 16;
}

win.pre_present_notify();
surface_buffer.present().unwrap();
}
})
.abort_handle(),
);
}

fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Touch(touch) => {
Handle::current().block_on(async move {
self.touch.set_data(TouchData {
point: Point2 {
x: touch.location.x as _,
y: (touch.location.y - 32.0) as _, // TODO: determine if we need to make this work with --fullscreen once we implement that
},
event: match touch.phase {
TouchPhase::Started | TouchPhase::Moved => TouchEvent::Press,
TouchPhase::Ended | TouchPhase::Cancelled => TouchEvent::Release,
},
}).await;
});
}
WindowEvent::CursorMoved { device_id: _, position } => {
Handle::current().block_on(async move {
self.touch.set_point(Point2 {
x: position.x as _,
y: (position.y - 32.0) as _, // TODO: determine if we need to make this work with --fullscreen once we implement that
}).await;
});
}
WindowEvent::MouseInput { device_id: _, state, button: MouseButton::Left } => {
Handle::current().block_on(async move {
self.touch.set_event(match state {
ElementState::Pressed => TouchEvent::Press,
ElementState::Released => TouchEvent::Release
}).await;
});
}
_ => (),
}
}
}

impl Drop for DisplayWindow {
fn drop(&mut self) {
if let Some(task) = self.task.as_ref() {
task.abort();
}
}
}
64 changes: 35 additions & 29 deletions packages/client-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
use std::{option::Option, path::PathBuf, time::Duration};

use anyhow::Context;
use log::LevelFilter;
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
use tokio::{
io::{stdout, AsyncReadExt, AsyncWriteExt},
process::Command,
time::sleep,
};
#[cfg(any(
target_os = "macos",
target_os = "ios",
Expand All @@ -18,15 +11,27 @@ use tokio::{
))]
use battery::{
units::{
electric_potential::millivolt, ratio::part_per_hundred,
thermodynamic_temperature::degree_celsius, electric_current::milliampere
electric_current::milliampere, electric_potential::millivolt, ratio::part_per_hundred,
thermodynamic_temperature::degree_celsius,
},
Manager,
};
use log::LevelFilter;
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
use tokio::{
io::{stdout, AsyncReadExt, AsyncWriteExt},
process::Command,
time::sleep,
};
use vex_v5_qemu_host::{
brain::{Binary, Brain},
protocol::battery::BatteryData,
};
use winit::event_loop::EventLoop;

use crate::display_window::DisplayWindow;

mod display_window;

#[cfg(debug_assertions)]
const DEFAULT_KERNEL: &str = concat!(
Expand Down Expand Up @@ -98,31 +103,25 @@ async fn main() -> anyhow::Result<()> {
)
.unwrap();

let mut brain = Brain::new();
let peripherals = brain.peripherals.take().unwrap();

let mut qemu = Command::new("qemu-system-arm");
qemu.args(opt.qemu_args);
if opt.gdb {
qemu.args(["-S", "-s"]);
}

qemu.args(opt.qemu_args);

brain
.run_program(
qemu,
opt.kernel,
Binary {
path: opt.program,
load_addr: opt.load_addr.unwrap_or(0x03800000),
},
opt.link.map(|link| Binary {
path: link,
load_addr: opt.link_addr.unwrap(),
}),
)
.await
.context("Failed to start QEMU.")?;
let mut brain = Brain::new(
qemu,
opt.kernel,
Binary {
path: opt.program,
load_addr: opt.load_addr.unwrap_or(0x03800000),
},
opt.link.map(|link| Binary {
path: link,
load_addr: opt.link_addr.unwrap(),
}),
).unwrap();
let peripherals = brain.peripherals.take().unwrap();

#[cfg(any(
target_os = "macos",
Expand Down Expand Up @@ -178,6 +177,13 @@ async fn main() -> anyhow::Result<()> {
}
});

let _ = tokio::task::block_in_place(move || {
let event_loop = EventLoop::new().unwrap();
let mut app = DisplayWindow::new(peripherals.display, peripherals.touch);

event_loop.run_app(&mut app)
});

brain.wait_for_exit().await?;

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ tokio = { version = "1.39.2", features = ["full"] }
vex-v5-qemu-protocol = { path = "../../protocol", features = ["serde"] }
vex-v5-qemu-host = { path = "../../host" }
# vex-v5-display-simulator = { version = "0.1.0", path = "../../display" }
bincode = "2.0.0-rc.3"
bincode = "2.0.1"
log = "0.4.22"
time = { version = "0.3.36", features = ["formatting"] }
tauri-plugin-dialog = "2.0.0-beta.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/display/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ version = "0.1.0"
edition = "2021"

[dependencies]
ab_glyph = "0.2.28"
# fimg = { version = "0.4.45", features = ["save"], default-features = false }
fontdue = "0.9.3"
image = { version = "0.25.2", default-features = false }
tiny-skia = "0.11.4"
vex-v5-qemu-protocol = { version = "0.0.1", path = "../protocol" }

[lints]
Expand Down
Binary file added packages/display/assets/NotoSans-Regular.ttf
Binary file not shown.
File renamed without changes.
Binary file added packages/display/assets/brain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions packages/display/examples/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use vex_v5_display_simulator::{ColorTheme, DisplayRenderer, TextOptions};
use vex_v5_qemu_protocol::{
display::{Color, Shape},
geometry::Point2,
};

pub fn main() {
let mut display = DisplayRenderer::new(ColorTheme::Dark);
display.draw_header();

display.context.foreground_color = Color(0x8B0000);

let container = Shape::Rectangle {
top_left: Point2 { x: 50, y: 50 },
bottom_right: Point2 {
x: 340,
y: 120,
},
};
display.draw(container, false);

display.context.foreground_color = Color(0xFFFFFF);
display.draw(container, true);

display.draw_text(
"Memory Permission error !".to_string(),
Point2 {
x: 80,
y: 70,
},
true,
TextOptions::default(),
);

display.draw_text(
"03800128".to_string(),
Point2 {
x: 80,
y: 90,
},
true,
TextOptions::default(),
);

// display.context.foreground_color = Color(0x44FF44);
// let container = Shape::Rectangle {
// top_left: Point2 {
// x: 80,
// y: 70,
// },
// bottom_right: Point2 {
// x: 81,
// y: 71,
// },
// };
// display.draw(container, false);

let pix = display.render(false).unwrap();
pix.save_png("result.png").unwrap();
}
Loading