From 44cf9416b933c84dc9270fcbd25d4e99dc479a0d Mon Sep 17 00:00:00 2001 From: Florian <29229062+Raupinger@users.noreply.github.com> Date: Tue, 12 May 2026 18:30:55 +0200 Subject: [PATCH 01/10] Add missing Cargo.toml file in qtfb rust client --- backends/qtfb-clients/rust/Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 backends/qtfb-clients/rust/Cargo.toml 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" From d7486397de86ddd0bb5ba533152a39b6ac27fa00 Mon Sep 17 00:00:00 2001 From: Florian <29229062+Raupinger@users.noreply.github.com> Date: Tue, 12 May 2026 19:10:21 +0200 Subject: [PATCH 02/10] fix rust example not using QTFB_KEY --- examples/qtfb/rust/src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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)); } From 9ddf1eecbc3b6e47ac250f7725e6f9dad9b53138 Mon Sep 17 00:00:00 2001 From: florian Date: Tue, 12 May 2026 22:37:20 +0200 Subject: [PATCH 03/10] c bindings, initial commit --- backends/qtfb-clients/c/Cargo.lock | 31 ++++++++++++++++ backends/qtfb-clients/c/Cargo.toml | 10 ++++++ backends/qtfb-clients/c/cbindgen.toml | 5 +++ backends/qtfb-clients/c/src/lib.rs | 51 +++++++++++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 backends/qtfb-clients/c/Cargo.lock create mode 100644 backends/qtfb-clients/c/Cargo.toml create mode 100644 backends/qtfb-clients/c/cbindgen.toml create mode 100644 backends/qtfb-clients/c/src/lib.rs 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..828d8e4 --- /dev/null +++ b/backends/qtfb-clients/c/src/lib.rs @@ -0,0 +1,51 @@ +mod capi { + use libc::c_int; + use qtfb_client; + + pub const DEFAULT_SCENE: u32 = 245209899; + #[no_mangle] + pub static SOCKET_PATH: &[u8] = b"/tmp/qtfb.sock\0"; + pub const MESSAGE_INITIALIZE: u8 = 0; + pub const MESSAGE_UPDATE: u8 = 1; + pub const MESSAGE_CUSTOM_INITIALIZE: u8 = 2; + pub const UPDATE_ALL: i32 = 0; + pub const UPDATE_PARTIAL: i32 = 1; + pub const FBFMT_RM2FB: u8 = 0; + pub const FBFMT_RMPP_RGB888: u8 = 1; + pub const FBFMT_RMPP_RGBA8888: u8 = 2; + + pub type FBKey = u32; + + #[repr(C)] + struct CustomResolution { + width: u16, + height: u16, + } + + #[no_mangle] + unsafe extern "C" fn create_connection<'a>(framebuffer_id: FBKey, + shm_type: u8, + custom_resolution: *const CustomResolution, + ) -> *mut qtfb_client::ClientConnection<'a> { + Box::into_raw(Box::new(qtfb_client::ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.height, t.width)))).unwrap())) + } + #[no_mangle] + unsafe extern "C" fn send_complete_update(connection: *mut qtfb_client::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 send_partial_update(connection: *mut qtfb_client::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 destroy_connection(connection: *mut qtfb_client::ClientConnection) { + if connection.is_null() { + return; + } + let _ = Box::from_raw(connection); + } + #[no_mangle] + extern "C" fn get_socket_path() -> *const libc::c_char { + SOCKET_PATH.as_ptr() as *const libc::c_char + } +} \ No newline at end of file From 60e8d7e8ca221dda9ac92c16d6d6ed77390457fb Mon Sep 17 00:00:00 2001 From: florian Date: Wed, 13 May 2026 02:49:03 +0200 Subject: [PATCH 04/10] c bindings missing typedef --- backends/qtfb-clients/c/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs index 828d8e4..93477c5 100644 --- a/backends/qtfb-clients/c/src/lib.rs +++ b/backends/qtfb-clients/c/src/lib.rs @@ -1,4 +1,5 @@ mod capi { + use std::ffi::c_void; use libc::c_int; use qtfb_client; @@ -16,6 +17,9 @@ mod capi { pub type FBKey = u32; + pub type ClientConnection = c_void; + + #[repr(C)] struct CustomResolution { width: u16, @@ -27,6 +31,7 @@ mod capi { shm_type: u8, custom_resolution: *const CustomResolution, ) -> *mut qtfb_client::ClientConnection<'a> { + //TODO: check with and height ordered correctly Box::into_raw(Box::new(qtfb_client::ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.height, t.width)))).unwrap())) } #[no_mangle] From e955e1af4f3adca2e55e54fe1602314f69c09afc Mon Sep 17 00:00:00 2001 From: florian Date: Wed, 13 May 2026 23:26:19 +0200 Subject: [PATCH 05/10] c bindings get access to shm --- backends/qtfb-clients/c/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs index 93477c5..9cc30b3 100644 --- a/backends/qtfb-clients/c/src/lib.rs +++ b/backends/qtfb-clients/c/src/lib.rs @@ -53,4 +53,9 @@ mod capi { extern "C" fn get_socket_path() -> *const libc::c_char { SOCKET_PATH.as_ptr() as *const libc::c_char } + + #[no_mangle] + unsafe extern "C" fn get_buffer(connection: *mut qtfb_client::ClientConnection) -> *const u8 { + return connection.as_ref().unwrap().shm.as_ptr() + } } \ No newline at end of file From 90e970d4af6225b95c25ec27e93d8fbf75671af6 Mon Sep 17 00:00:00 2001 From: florian Date: Wed, 13 May 2026 23:27:43 +0200 Subject: [PATCH 06/10] c bindings function names --- backends/qtfb-clients/c/src/lib.rs | 50 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs index 9cc30b3..abec8f6 100644 --- a/backends/qtfb-clients/c/src/lib.rs +++ b/backends/qtfb-clients/c/src/lib.rs @@ -1,61 +1,65 @@ +mod dumbstupidhack { + pub(crate) use qtfb_client::ClientConnection as QTFB_ClientConnection; +} mod capi { use std::ffi::c_void; use libc::c_int; use qtfb_client; + use crate::dumbstupidhack; - pub const DEFAULT_SCENE: u32 = 245209899; + pub const QTFB_DEFAULT_SCENE: u32 = 245209899; #[no_mangle] - pub static SOCKET_PATH: &[u8] = b"/tmp/qtfb.sock\0"; - pub const MESSAGE_INITIALIZE: u8 = 0; - pub const MESSAGE_UPDATE: u8 = 1; - pub const MESSAGE_CUSTOM_INITIALIZE: u8 = 2; - pub const UPDATE_ALL: i32 = 0; - pub const UPDATE_PARTIAL: i32 = 1; - pub const FBFMT_RM2FB: u8 = 0; - pub const FBFMT_RMPP_RGB888: u8 = 1; - pub const FBFMT_RMPP_RGBA8888: u8 = 2; + 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 FBKey = u32; + pub type QTFB_FBKey = u32; - pub type ClientConnection = c_void; + pub type QTFB_ClientConnection = c_void; #[repr(C)] - struct CustomResolution { + struct QTFB_CustomResolution { width: u16, height: u16, } #[no_mangle] - unsafe extern "C" fn create_connection<'a>(framebuffer_id: FBKey, + unsafe extern "C" fn qtfb_create_connection<'a>(framebuffer_id: QTFB_FBKey, shm_type: u8, - custom_resolution: *const CustomResolution, - ) -> *mut qtfb_client::ClientConnection<'a> { + custom_resolution: *const QTFB_CustomResolution, + ) -> *mut dumbstupidhack::QTFB_ClientConnection<'a> { //TODO: check with and height ordered correctly - Box::into_raw(Box::new(qtfb_client::ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.height, t.width)))).unwrap())) + Box::into_raw(Box::new(dumbstupidhack::QTFB_ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.height, t.width)))).unwrap())) } #[no_mangle] - unsafe extern "C" fn send_complete_update(connection: *mut qtfb_client::ClientConnection) -> c_int { + 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 send_partial_update(connection: *mut qtfb_client::ClientConnection, x: i32, y: i32, w: i32, h: i32) -> c_int { + 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 destroy_connection(connection: *mut qtfb_client::ClientConnection) { + 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 get_socket_path() -> *const libc::c_char { - SOCKET_PATH.as_ptr() as *const libc::c_char + 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 get_buffer(connection: *mut qtfb_client::ClientConnection) -> *const u8 { + unsafe extern "C" fn qtfb_get_buffer(connection: *mut dumbstupidhack::QTFB_ClientConnection) -> *const u8 { return connection.as_ref().unwrap().shm.as_ptr() } } \ No newline at end of file From 7345cf57eeea992e97a0bd62b4181baa17d1fc63 Mon Sep 17 00:00:00 2001 From: florian Date: Thu, 14 May 2026 15:14:06 +0200 Subject: [PATCH 07/10] add input support to rust and c bindings --- backends/qtfb-clients/c/src/lib.rs | 27 ++++- backends/qtfb-clients/rust/src/lib.rs | 69 ++++++++++-- backends/qtfb-clients/rust/src/user_input.rs | 109 +++++++++++++++++++ 3 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 backends/qtfb-clients/rust/src/user_input.rs diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs index abec8f6..5db2be2 100644 --- a/backends/qtfb-clients/c/src/lib.rs +++ b/backends/qtfb-clients/c/src/lib.rs @@ -1,12 +1,16 @@ 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::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"; @@ -23,6 +27,14 @@ mod capi { 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 { @@ -35,8 +47,7 @@ mod capi { shm_type: u8, custom_resolution: *const QTFB_CustomResolution, ) -> *mut dumbstupidhack::QTFB_ClientConnection<'a> { - //TODO: check with and height ordered correctly - Box::into_raw(Box::new(dumbstupidhack::QTFB_ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.height, t.width)))).unwrap())) + Box::into_raw(Box::new(dumbstupidhack::QTFB_ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.width, t.height)))).unwrap())) } #[no_mangle] unsafe extern "C" fn qtfb_send_complete_update(connection: *mut dumbstupidhack::QTFB_ClientConnection) -> c_int { @@ -62,4 +73,16 @@ mod capi { 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_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/src/lib.rs b/backends/qtfb-clients/rust/src/lib.rs index 7b78ff9..941f5c1 100644 --- a/backends/qtfb-clients/rust/src/lib.rs +++ b/backends/qtfb-clients/rust/src/lib.rs @@ -1,3 +1,5 @@ +pub mod user_input; + use anyhow::{Error, Result}; use libc::{ c_void, mmap, munmap, sockaddr_un, socket, AF_UNIX, MAP_FAILED, MAP_SHARED, PROT_READ, @@ -11,11 +13,13 @@ 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 UPDATE_ALL: i32 = 0; @@ -26,6 +30,13 @@ 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; @@ -75,10 +86,16 @@ 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> { @@ -86,6 +103,9 @@ pub struct ClientConnection<'a> { pub shm: &'a mut [u8], } +#[derive(Debug, Clone)] +struct InvalidMessage; + impl<'a> ClientConnection<'a> { pub fn new( framebuffer_id: FBKey, @@ -158,10 +178,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 +196,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,7 +219,7 @@ 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 }) } @@ -250,6 +273,38 @@ 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}); + + } } 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(()) + } +} From 96c5ab2ee932ea019df69dc7ef5629e10b87dbe2 Mon Sep 17 00:00:00 2001 From: florian Date: Mon, 18 May 2026 00:37:28 +0200 Subject: [PATCH 08/10] Rust/C: add Refresh mode setting --- backends/qtfb-clients/c/src/lib.rs | 14 ++++++++++++++ backends/qtfb-clients/rust/src/lib.rs | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs index 5db2be2..4f6ec16 100644 --- a/backends/qtfb-clients/c/src/lib.rs +++ b/backends/qtfb-clients/c/src/lib.rs @@ -7,6 +7,7 @@ mod capi { use std::ptr::null; use libc::c_int; use qtfb_client; + use qtfb_client::RefreshMode; use qtfb_client::user_input::InputEvent; use crate::dumbstupidhack; @@ -74,6 +75,19 @@ mod capi { 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_ref().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()) { diff --git a/backends/qtfb-clients/rust/src/lib.rs b/backends/qtfb-clients/rust/src/lib.rs index 941f5c1..44217ab 100644 --- a/backends/qtfb-clients/rust/src/lib.rs +++ b/backends/qtfb-clients/rust/src/lib.rs @@ -22,6 +22,7 @@ pub mod constants { 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; @@ -30,6 +31,8 @@ pub mod constants { pub type FBKey = u32; } + + #[repr(C)] #[derive(Clone, Copy)] pub struct InputMessage { @@ -78,6 +81,7 @@ union ClientMessageContents { init: InitMessageContents, update: UpdateRegionMessageContents, custom_init: CustomInitMessageContents, + refresh_mode: i32 } #[repr(C)] @@ -102,6 +106,16 @@ pub struct ClientConnection<'a> { fd: RawFd, pub shm: &'a mut [u8], } +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum RefreshMode { + UltraFast=0, + Fast=1, + Animate=2, + Content=3, + UI=4 +} + #[derive(Debug, Clone)] struct InvalidMessage; @@ -305,6 +319,17 @@ impl<'a> ClientConnection<'a> { return Ok(unsafe{ server_message.contents.input}); } + + pub fn set_refresh_mode(&self, refresh_mode: RefreshMode) -> io::Result<()> { + let message = ClientMessage { + msg_type: constants::MESSAGE_SET_REFRESH_MODE, + contents: ClientMessageContents { + refresh_mode: refresh_mode as i32, + }, + }; + + self.send_message(&message) + } } impl<'a> Drop for ClientConnection<'a> { From 71837b4d2dcef60b9f7544b02ce5152be9e7c1ff Mon Sep 17 00:00:00 2001 From: florian Date: Mon, 18 May 2026 19:44:30 +0200 Subject: [PATCH 09/10] dont spam messages on refresh mode re-sets --- backends/qtfb-clients/c/src/lib.rs | 2 +- backends/qtfb-clients/rust/src/lib.rs | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs index 4f6ec16..d005324 100644 --- a/backends/qtfb-clients/c/src/lib.rs +++ b/backends/qtfb-clients/c/src/lib.rs @@ -85,7 +85,7 @@ mod capi { 4 => RefreshMode::UI, _ => return -1, }; - return connection.as_ref().unwrap().set_refresh_mode(mode_enum).map_or_else(|e| e.raw_os_error().unwrap_or(-1), |_| 0); + return connection.as_mut().unwrap().set_refresh_mode(mode_enum).map_or_else(|e| e.raw_os_error().unwrap_or(-1), |_| 0); } #[no_mangle] diff --git a/backends/qtfb-clients/rust/src/lib.rs b/backends/qtfb-clients/rust/src/lib.rs index 44217ab..d590efc 100644 --- a/backends/qtfb-clients/rust/src/lib.rs +++ b/backends/qtfb-clients/rust/src/lib.rs @@ -1,5 +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, @@ -104,10 +105,11 @@ struct ServerMessage { pub struct ClientConnection<'a> { fd: RawFd, + current_refresh_mode: RefreshMode, pub shm: &'a mut [u8], } #[repr(u32)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub enum RefreshMode { UltraFast=0, Fast=1, @@ -117,9 +119,12 @@ pub enum RefreshMode { } + #[derive(Debug, Clone)] struct InvalidMessage; + + impl<'a> ClientConnection<'a> { pub fn new( framebuffer_id: FBKey, @@ -235,7 +240,7 @@ impl<'a> ClientConnection<'a> { let shm = 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<()> { @@ -320,7 +325,8 @@ impl<'a> ClientConnection<'a> { } - pub fn set_refresh_mode(&self, refresh_mode: RefreshMode) -> io::Result<()> { + 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 { @@ -328,7 +334,13 @@ impl<'a> ClientConnection<'a> { }, }; - self.send_message(&message) + match self.send_message(&message) { + Ok(_) => { + self.current_refresh_mode = refresh_mode; + Ok(()) + }, + Err(e) => Err(e), + } } } From 4807455d01f95d999e3188356528dd60778057a4 Mon Sep 17 00:00:00 2001 From: florian Date: Fri, 12 Jun 2026 00:19:29 +0200 Subject: [PATCH 10/10] qtfb c client: handle error during client connection setup --- backends/qtfb-clients/c/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backends/qtfb-clients/c/src/lib.rs b/backends/qtfb-clients/c/src/lib.rs index d005324..a16d3e5 100644 --- a/backends/qtfb-clients/c/src/lib.rs +++ b/backends/qtfb-clients/c/src/lib.rs @@ -48,7 +48,13 @@ mod capi { shm_type: u8, custom_resolution: *const QTFB_CustomResolution, ) -> *mut dumbstupidhack::QTFB_ClientConnection<'a> { - Box::into_raw(Box::new(dumbstupidhack::QTFB_ClientConnection::new(framebuffer_id, shm_type, custom_resolution.as_ref().and_then(|t| Some((t.width, t.height)))).unwrap())) + 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 {