diff --git a/backends/qtfb-clients/c/Cargo.lock b/backends/qtfb-clients/c/Cargo.lock new file mode 100644 index 0000000..80c4ad8 --- /dev/null +++ b/backends/qtfb-clients/c/Cargo.lock @@ -0,0 +1,31 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "qtfb-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "libc", +] + +[[package]] +name = "qtfb-client-c" +version = "0.1.0" +dependencies = [ + "libc", + "qtfb-client", +] diff --git a/backends/qtfb-clients/c/Cargo.toml b/backends/qtfb-clients/c/Cargo.toml new file mode 100644 index 0000000..8ec3240 --- /dev/null +++ b/backends/qtfb-clients/c/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "qtfb-client-c" +version = "0.1.0" +edition = "2021" +[features] +capi = [] + +[dependencies] +qtfb-client = { path = "../rust" } +libc = "0.2.186" \ No newline at end of file diff --git a/backends/qtfb-clients/c/cbindgen.toml b/backends/qtfb-clients/c/cbindgen.toml new file mode 100644 index 0000000..a3a9115 --- /dev/null +++ b/backends/qtfb-clients/c/cbindgen.toml @@ -0,0 +1,5 @@ +language = "C" + +# An optional name to use as an include guard +# default: doesn't emit an include guard +include_guard = "appload_bindings" \ No newline at end of file diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs new file mode 100644 index 0000000..a16d3e5 --- /dev/null +++ b/backends/qtfb-clients/c/src/lib.rs @@ -0,0 +1,108 @@ +mod dumbstupidhack { + pub(crate) use qtfb_client::ClientConnection as QTFB_ClientConnection; + pub(crate) use qtfb_client::InputMessage as QTFB_InputMessage; +} +mod capi { + use std::ffi::c_void; + use std::ptr::null; + use libc::c_int; + use qtfb_client; + use qtfb_client::RefreshMode; + use qtfb_client::user_input::InputEvent; + use crate::dumbstupidhack; + + + pub const QTFB_DEFAULT_SCENE: u32 = 245209899; + #[no_mangle] + pub static QTFB_SOCKET_PATH: &[u8] = b"/tmp/qtfb.sock\0"; + pub const QTFB_MESSAGE_INITIALIZE: u8 = 0; + pub const QTFB_MESSAGE_UPDATE: u8 = 1; + pub const QTFB_MESSAGE_CUSTOM_INITIALIZE: u8 = 2; + pub const QTFB_UPDATE_ALL: i32 = 0; + pub const QTFB_UPDATE_PARTIAL: i32 = 1; + pub const QTFB_FBFMT_RM2FB: u8 = 0; + pub const QTFB_FBFMT_RMPP_RGB888: u8 = 1; + pub const QTFB_FBFMT_RMPP_RGBA8888: u8 = 2; + + pub type QTFB_FBKey = u32; + + pub type QTFB_ClientConnection = c_void; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct QTFB_InputMessage { + input_type: u32, + dev_id: u32, + x: u32, y: u32, d: u32 + } + + + #[repr(C)] + struct QTFB_CustomResolution { + width: u16, + height: u16, + } + + #[no_mangle] + unsafe extern "C" fn qtfb_create_connection<'a>(framebuffer_id: QTFB_FBKey, + shm_type: u8, + custom_resolution: *const QTFB_CustomResolution, + ) -> *mut dumbstupidhack::QTFB_ClientConnection<'a> { + match dumbstupidhack::QTFB_ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.width, t.height)))) { + Ok(val) => Box::into_raw(Box::new(val)), + Err(err) => { + eprintln!("Couldnt open connection: {}", err); + null::<*mut dumbstupidhack::QTFB_ClientConnection>() as *mut dumbstupidhack::QTFB_ClientConnection + } + } + } + #[no_mangle] + unsafe extern "C" fn qtfb_send_complete_update(connection: *mut dumbstupidhack::QTFB_ClientConnection) -> c_int { + connection.as_ref().unwrap().send_complete_update().map_or_else(|e| e.raw_os_error().unwrap_or(-1), |_| 0) + } + #[no_mangle] + unsafe extern "C" fn qtfb_send_partial_update(connection: *mut dumbstupidhack::QTFB_ClientConnection, x: i32, y: i32, w: i32, h: i32) -> c_int { + connection.as_ref().unwrap().send_partial_update(x, y, w, h).map_or_else(|e| e.raw_os_error().unwrap_or(-1), |_| 0) + } + #[no_mangle] + unsafe extern "C" fn qtfb_destroy_connection(connection: *mut dumbstupidhack::QTFB_ClientConnection) { + if connection.is_null() { + return; + } + let _ = Box::from_raw(connection); + } + #[no_mangle] + extern "C" fn qtfb_get_socket_path() -> *const libc::c_char { + QTFB_SOCKET_PATH.as_ptr() as *const libc::c_char + } + + #[no_mangle] + unsafe extern "C" fn qtfb_get_buffer(connection: *mut dumbstupidhack::QTFB_ClientConnection) -> *const u8 { + return connection.as_ref().unwrap().shm.as_ptr() + } + + #[no_mangle] + unsafe extern "C" fn qtfb_set_refresh_mode(connection: *mut dumbstupidhack::QTFB_ClientConnection, mode: u32) -> c_int { + let mode_enum = match mode { + 0 => RefreshMode::UltraFast, + 1 => RefreshMode::Fast, + 2 => RefreshMode::Animate, + 3 => RefreshMode::Content, + 4 => RefreshMode::UI, + _ => return -1, + }; + return connection.as_mut().unwrap().set_refresh_mode(mode_enum).map_or_else(|e| e.raw_os_error().unwrap_or(-1), |_| 0); + } + + #[no_mangle] + unsafe extern "C" fn qtfb_poll_input(connection: *mut dumbstupidhack::QTFB_ClientConnection, message: *const dumbstupidhack::QTFB_InputMessage) -> bool { + match (connection.as_ref().unwrap().poll_input()) { + Ok(val) => { + let dst_ptr = message as *mut qtfb_client::InputMessage; + std::ptr::write(dst_ptr, val); + true + }, + Err(_) => false + } + } +} \ No newline at end of file diff --git a/backends/qtfb-clients/rust/Cargo.toml b/backends/qtfb-clients/rust/Cargo.toml new file mode 100644 index 0000000..bdc509b --- /dev/null +++ b/backends/qtfb-clients/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "qtfb-client" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.94" +libc = "0.2.169" diff --git a/backends/qtfb-clients/rust/src/lib.rs b/backends/qtfb-clients/rust/src/lib.rs index 7b78ff9..d590efc 100644 --- a/backends/qtfb-clients/rust/src/lib.rs +++ b/backends/qtfb-clients/rust/src/lib.rs @@ -1,3 +1,6 @@ +pub mod user_input; + +use std::cmp::PartialEq; use anyhow::{Error, Result}; use libc::{ c_void, mmap, munmap, sockaddr_un, socket, AF_UNIX, MAP_FAILED, MAP_SHARED, PROT_READ, @@ -11,13 +14,16 @@ use std::mem::transmute; use std::os::unix::io::{AsRawFd, RawFd}; use std::ptr; use std::slice; +use crate::user_input::InputEvent; pub mod constants { pub const DEFAULT_SCENE: u32 = 245209899; pub const SOCKET_PATH: &str = "/tmp/qtfb.sock"; pub const MESSAGE_INITIALIZE: u8 = 0; + pub const MESSAGE_INPUT: u8 = 4; pub const MESSAGE_UPDATE: u8 = 1; pub const MESSAGE_CUSTOM_INITIALIZE: u8 = 2; + pub const MESSAGE_SET_REFRESH_MODE: u8 = 5; pub const UPDATE_ALL: i32 = 0; pub const UPDATE_PARTIAL: i32 = 1; pub const FBFMT_RM2FB: u8 = 0; @@ -27,6 +33,15 @@ pub mod constants { pub type FBKey = u32; } + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct InputMessage { + input_type: InputEvent, + dev_id: u32, + x: u32, y: u32, d: u32 +} + pub type FBKey = u32; #[repr(C)] @@ -67,6 +82,7 @@ union ClientMessageContents { init: InitMessageContents, update: UpdateRegionMessageContents, custom_init: CustomInitMessageContents, + refresh_mode: i32 } #[repr(C)] @@ -75,16 +91,39 @@ struct ClientMessage { contents: ClientMessageContents, } +#[repr(C)] +union ServerMessageContents { + init: InitMessageResponseContents, + input: InputMessage, +} + #[repr(C)] struct ServerMessage { msg_type: u8, - init: InitMessageResponseContents, + contents: ServerMessageContents, } pub struct ClientConnection<'a> { fd: RawFd, + current_refresh_mode: RefreshMode, pub shm: &'a mut [u8], } +#[repr(u32)] +#[derive(Clone, Copy, PartialEq)] +pub enum RefreshMode { + UltraFast=0, + Fast=1, + Animate=2, + Content=3, + UI=4 +} + + + +#[derive(Debug, Clone)] +struct InvalidMessage; + + impl<'a> ClientConnection<'a> { pub fn new( @@ -158,10 +197,10 @@ impl<'a> ClientConnection<'a> { let mut server_message = ServerMessage { msg_type: 0, - init: InitMessageResponseContents { + contents: ServerMessageContents { init: InitMessageResponseContents { shm_key_defined: 0, shm_size: 0, - }, + }}, }; let recv_res = unsafe { @@ -176,14 +215,17 @@ impl<'a> ClientConnection<'a> { if recv_res < 1 { return Err(Error::new(io::Error::last_os_error())); } - - let shm_name = format!("/dev/shm/qtfb_{}", server_message.init.shm_key_defined); + if (server_message.msg_type != constants::MESSAGE_INITIALIZE) { + return Err(Error::from(io::Error::other("your message here"))); + } + let init_message = unsafe{ server_message.contents.init}; + let shm_name = format!("/dev/shm/qtfb_{}", init_message.shm_key_defined); let shm_fd = OpenOptions::new().read(true).write(true).open(&shm_name)?; let shm_ptr = unsafe { mmap( ptr::null_mut(), - server_message.init.shm_size, + init_message.shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd.as_raw_fd(), @@ -196,9 +238,9 @@ impl<'a> ClientConnection<'a> { } let shm = - unsafe { slice::from_raw_parts_mut(shm_ptr as *mut u8, server_message.init.shm_size) }; + unsafe { slice::from_raw_parts_mut(shm_ptr as *mut u8, init_message.shm_size) }; - Ok(Self { fd, shm }) + Ok(Self { fd, current_refresh_mode: RefreshMode::UI, shm }) } pub fn send_complete_update(&self) -> io::Result<()> { @@ -250,6 +292,56 @@ impl<'a> ClientConnection<'a> { } Ok(()) } + + pub fn poll_input(&self) -> Result { + + let mut server_message = ServerMessage { + msg_type: 0, + contents: ServerMessageContents { input: InputMessage { + input_type: InputEvent::BtnXLeft, + dev_id: 0, + x: 0, + y: 0, + d: 0, + }}, + }; + + let recv_res = unsafe { + libc::recv( + self.fd, + &mut server_message as *mut _ as *mut c_void, + mem::size_of::(), + libc::MSG_DONTWAIT, + ) + }; + + if recv_res < 1 { + return Err(Error::new(io::Error::last_os_error())); + } + if (server_message.msg_type != constants::MESSAGE_INPUT) { + return Err(Error::from(io::Error::other("your message here"))); + } + return Ok(unsafe{ server_message.contents.input}); + + } + + pub fn set_refresh_mode(&mut self, refresh_mode: RefreshMode) -> io::Result<()> { + if (self.current_refresh_mode == refresh_mode) {return Ok(())} + let message = ClientMessage { + msg_type: constants::MESSAGE_SET_REFRESH_MODE, + contents: ClientMessageContents { + refresh_mode: refresh_mode as i32, + }, + }; + + match self.send_message(&message) { + Ok(_) => { + self.current_refresh_mode = refresh_mode; + Ok(()) + }, + Err(e) => Err(e), + } + } } impl<'a> Drop for ClientConnection<'a> { diff --git a/backends/qtfb-clients/rust/src/user_input.rs b/backends/qtfb-clients/rust/src/user_input.rs new file mode 100644 index 0000000..27ead47 --- /dev/null +++ b/backends/qtfb-clients/rust/src/user_input.rs @@ -0,0 +1,109 @@ +/// everything below here is generated by my local Qwen3.5 with the following prompt: +/// I have the following C constants.h file can you give me a rust file that represents that as an enum and has functions to convert back and forth? +/// (all the INPUT_* const in commons.h) +/// I ain't typing that shit 🗿 (i did cross check it though) + +/// Represents the input event types defined in the C constants.h file. +/// +/// Uses `#[repr(u32)]` to ensure the integer values match the C macros exactly. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u32)] +pub enum InputEvent { + // X Buttons (Mouse/Touchpad Buttons) + BtnXLeft = 0, + BtnXHome = 1, + BtnXRight = 2, + + // Touch Events + TouchPress = 0x10, + TouchRelease = 0x11, + TouchUpdate = 0x12, + + // Pen Events + PenPress = 0x20, + PenRelease = 0x21, + PenUpdate = 0x22, + + // General Buttons + BtnPress = 0x30, + BtnRelease = 0x31, + + // Virtual Keyboard (VKB) Press/Release + VkbPress = 0x40, + VkbRelease = 0x41, + + // Keyboard Modifiers/Keys + VkbShiftMod = 0x100_000, + VkbCtrlMod = 0x200_000, + VkbAltMod = 0x400_000, + VkbDel = 0x7f, + VkbPgUp = 0x80, + VkbPgDown = 0x81, + VkbDown = 0x82, + VkbUp = 0x83, + VkbLeft = 0x84, + VkbRight = 0x85, + VkbHome = 0x86, + VkbEnd = 0x87, +} + +impl InputEvent { + /// Converts a raw u32 integer value into an `InputEvent` enum variant. + /// Returns `None` if the value is not a defined constant. + pub fn from_u32(code: u32) -> Option { + match code { + // X Buttons + 0 => Some(Self::BtnXLeft), + 1 => Some(Self::BtnXHome), + 2 => Some(Self::BtnXRight), + + // Touch + 0x10 => Some(Self::TouchPress), + 0x11 => Some(Self::TouchRelease), + 0x12 => Some(Self::TouchUpdate), + + // Pen + 0x20 => Some(Self::PenPress), + 0x21 => Some(Self::PenRelease), + 0x22 => Some(Self::PenUpdate), + + // Buttons + 0x30 => Some(Self::BtnPress), + 0x31 => Some(Self::BtnRelease), + + // VKB Press/Release + 0x40 => Some(Self::VkbPress), + 0x41 => Some(Self::VkbRelease), + + // Modifiers/Keys + 0x100_000 => Some(Self::VkbShiftMod), + 0x200_000 => Some(Self::VkbCtrlMod), + 0x400_000 => Some(Self::VkbAltMod), + 0x7f => Some(Self::VkbDel), + 0x80 => Some(Self::VkbPgUp), + 0x81 => Some(Self::VkbPgDown), + 0x82 => Some(Self::VkbDown), + 0x83 => Some(Self::VkbUp), + 0x84 => Some(Self::VkbLeft), + 0x85 => Some(Self::VkbRight), + 0x86 => Some(Self::VkbHome), + 0x87 => Some(Self::VkbEnd), + _ => None, + } + } + + /// Converts the enum variant back to its raw u32 integer value. + /// Because of `#[repr(u32)]`, this is equivalent to `self as u32`. + pub fn to_u32(self) -> u32 { + self as u32 + } +} + +// Implementation of standard traits for easier usage with `try_from` +impl TryFrom for InputEvent { + type Error = (); + + fn try_from(code: u32) -> Result { + Self::from_u32(code).ok_or(()) + } +} diff --git a/examples/qtfb/rust/src/main.rs b/examples/qtfb/rust/src/main.rs index 02c2afc..a833c9b 100644 --- a/examples/qtfb/rust/src/main.rs +++ b/examples/qtfb/rust/src/main.rs @@ -1,13 +1,17 @@ use qtfb_client::ClientConnection; +use std::env; fn main() { + let key: u32 = env::var("QTFB_KEY").expect("couldnt find QTFB_KEY environment variable. Are you sure you launched from appload?").parse::().expect("qtfb not a number"); let client = ClientConnection::new( - qtfb_client::constants::DEFAULT_SCENE, + key, qtfb_client::constants::FBFMT_RMPP_RGB888, None ).unwrap(); + println!("hii1"); let file_contents = std::fs::read("a.raw").unwrap(); client.shm[0..file_contents.len()].copy_from_slice(&file_contents); - client.send_complete_update().unwrap(); + let res = client.send_complete_update().unwrap(); + println!("hii2 {:?}", res); std::thread::sleep(std::time::Duration::from_secs(10)); }