diff --git a/src/hzclient.rs b/src/hzclient.rs index 6444ed1..59aada2 100644 --- a/src/hzclient.rs +++ b/src/hzclient.rs @@ -1,79 +1,121 @@ -use std::{ffi, path::Path}; +use std::ffi; -#[repr(u32)] -pub enum ResultCode { - Unchanged, - Changed, - Fail, +pub(crate) mod sys { + use std::ffi; + + #[repr(u32)] + #[allow(unused)] // it IS used, just not by Rust + pub enum ResultCode { + Unchanged, + Changed, + Fail, + } + + extern "C" { + pub(crate) fn rust_new_hazelnut_client() -> *mut ffi::c_void; + pub(crate) fn rust_destroy_hazelnut_client(client: *mut ffi::c_void); + + pub(crate) fn rust_hazelnut_client_open( + client: *mut ffi::c_void, + url: *const ffi::c_char, + ) -> bool; + + pub(crate) fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ResultCode; + + // Explicitly lock/unlock. + pub(crate) fn rust_hazelnut_client_lock(client: *mut ffi::c_void); + pub(crate) fn rust_hazelnut_client_unlock(client: *mut ffi::c_void); + + // Use only when locked + pub(crate) fn rust_hazelnut_client_get_framebuffer(client: *mut ffi::c_void) -> *mut u32; + pub(crate) fn rust_hazelnut_client_get_width(client: *mut ffi::c_void) -> u32; + pub(crate) fn rust_hazelnut_client_get_height(client: *mut ffi::c_void) -> u32; + } } -extern "C" { - fn rust_new_hazelnut_client() -> *mut ffi::c_void; - fn rust_destroy_hazelnut_client(client: *mut ffi::c_void); - - fn rust_hazelnut_client_open(client: *mut ffi::c_void, url: *const ffi::c_char) -> bool; - - fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ResultCode; - - fn rust_hazelnut_client_lock(client: *mut ffi::c_void); - fn rust_hazelnut_client_unlock(client: *mut ffi::c_void); - - fn rust_hazelnut_client_get_framebuffer(client: *mut ffi::c_void) -> *mut u32; - fn rust_hazelnut_client_get_width(client: *mut ffi::c_void) -> u32; - fn rust_hazelnut_client_get_height(client: *mut ffi::c_void) -> u32; -} +pub type ResultCode = sys::ResultCode; +/// A Hazelnut IVSHMEM client. Wraps the C++ code (see ./rust_wrapper.cpp and the shared sources) +/// so that we can use it in Rust, semi-safely. (Note that I really do not like the current +/// state of these bindings, and they were written JUST to get working. +/// I am aware they are not very idiomatic and of other problems with them.) pub struct HazelnutClient(*mut std::ffi::c_void); impl HazelnutClient { + /// Creates a new client. pub fn new() -> HazelnutClient { - unsafe { Self(rust_new_hazelnut_client()) } - } - - pub fn open(&mut self, path: String) -> bool { - let cstr = ffi::CString::new(path).expect("dumbass"); - unsafe { - return rust_hazelnut_client_open(self.0, cstr.as_ptr()); - } + let client = sys::rust_new_hazelnut_client(); + assert!( + !client.is_null(), + "C++ code failed to allocate a client object" + ); + Self(client) + } } + /// Opens the given IVSHMEM shmem file. + pub fn open(&mut self, path: &String) -> bool { + let cstr = ffi::CString::new(path.clone()).expect("dumbass"); + + // FIXME: this really should work by FD so we can just return io::Result<> or something, but bleh + // for now it's "fine", also it's a path in the shared sources currently. + unsafe { + return sys::rust_hazelnut_client_open(self.0, cstr.as_ptr()); + } + } + + /// Ticks this client. + /// + /// # Notes + /// If this returns [ResultCode::Changed], then the Hazelnut lock (see [HazelnutClient::lock] and [HazelnutClient::unlock]) is still held, + /// so you will need to unlock after your processing and let the guest VM continue processing. pub fn tick_one(&mut self) -> ResultCode { unsafe { - return rust_hazelnut_client_tick(self.0); + return sys::rust_hazelnut_client_tick(self.0); } } + /// Explicitly lock the Hazelnut IVSHMEM memory. + /// During this, the guest VM will wait for us to release the lock, and we can read from the memory without + /// risk of it being changed. pub fn lock(&mut self) { unsafe { - return rust_hazelnut_client_lock(self.0); + return sys::rust_hazelnut_client_lock(self.0); } } + /// Unlock the Hazelnut IVSHMEM memory. pub fn unlock(&mut self) { unsafe { - return rust_hazelnut_client_unlock(self.0); + return sys::rust_hazelnut_client_unlock(self.0); } } - /// only use while lock is held! + /// Obtains the framebuffer dimensions. + /// + /// # Safety + /// Only use while the Hazelnut atomic lock is held! pub fn dimensions(&mut self) -> (u32, u32) { let tup = unsafe { - let width = rust_hazelnut_client_get_width(self.0); - let height = rust_hazelnut_client_get_height(self.0); + let width = sys::rust_hazelnut_client_get_width(self.0); + let height = sys::rust_hazelnut_client_get_height(self.0); (width, height) }; tup } - /// only use while lock is held! + /// Obtains a mutable slice containing the framebuffer. + /// + /// # Safety + /// Only use while the Hazelnut atomic lock is held! pub fn framebuffer(&mut self) -> &mut [u32] { let sl = unsafe { - let fb_ptr = rust_hazelnut_client_get_framebuffer(self.0); + let fb_ptr = sys::rust_hazelnut_client_get_framebuffer(self.0); let dim = self.dimensions(); - std::slice::from_raw_parts_mut(fb_ptr, ((dim.0 * dim.1) as usize * 4)) + std::slice::from_raw_parts_mut(fb_ptr, (dim.0 * dim.1) as usize * 4) }; sl } @@ -82,7 +124,7 @@ impl HazelnutClient { impl Drop for HazelnutClient { fn drop(&mut self) { unsafe { - rust_destroy_hazelnut_client(self.0); + sys::rust_destroy_hazelnut_client(self.0); } self.0 = std::ptr::null_mut(); } diff --git a/src/main.rs b/src/main.rs index cca54b4..beee0c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,21 +5,29 @@ mod hzclient; fn main() { let mut screen_width: u32 = 320; let mut screen_height: u32 = 200; - - let mut window = Window::new("FbcServer", 320, 200, WindowOptions::default()) - .expect("you banned forever: rules do not"); + let mut window: Option = None; let mut client = hzclient::HazelnutClient::new(); - if !client.open("/dev/shm/lg-win7".into()) { - println!("FUCK"); + let socket_path: String = "/dev/shm/lg-win7".into(); + + if !client.open(&socket_path) { + println!("Could not open IVSHMEM."); + return (); } else { - println!("Opened ivshmem!!!"); + println!("Opened IVSHMEM device."); } - //let mut argb_buffer: Vec = Vec::new(); - loop { + match window.as_mut() { + Some(window) => { + if !window.is_open() { + break; + } + } + None => {} + } + match client.tick_one() { hzclient::ResultCode::Fail => break, hzclient::ResultCode::Changed => { @@ -29,30 +37,35 @@ fn main() { screen_width = dims.0; screen_height = dims.1; - window = Window::new( - "FbcServer", - screen_width as usize, - screen_height as usize, - WindowOptions::default(), - ) - .expect("you banned forever: rules do not"); + window = Some( + Window::new( + &format!("Hazelnut client \"{socket_path}\" ({screen_width}x{screen_height})"), + screen_width as usize, + screen_height as usize, + WindowOptions::default(), + ) + .expect("Could not create window"), + ); } - window.update_with_buffer( - &client.framebuffer(), - screen_width as usize, - screen_height as usize, - ).expect("well its done anyways"); + window + .as_mut() + .unwrap() + .update_with_buffer( + &client.framebuffer(), + screen_width as usize, + screen_height as usize, + ) + .expect("Failed to update"); client.unlock(); } - hzclient::ResultCode::Unchanged => { - window.update(); - // Not needed, C++ unlocks us - //client.unlock(); - } + hzclient::ResultCode::Unchanged => match window.as_mut() { + Some(window) => { + window.update(); + } + _ => {} + }, } } - - println!("Hello, world!"); }