I'm trying to read out a HID device (barcode scanner) using the hidapi
crate in a Tauri App.
I have this scenario in mind:
device-disconnected
device-data
with the payload when a scan happensTauri command
from the UI to rescan for the deviceNow I have this code
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
extern crate hidapi;
use hidapi::HidApi;
use tauri::Manager;
#[derive(Clone, serde::Serialize)]
struct Payload {
message: String,
}
#[tauri::command]
fn detect_barcode_scanner(app: &mut tauri::App) -> () {
let vid = 0x1234;
let pid = 0x5678;
match HidApi::new() {
Ok(api) => {
let mut device_found = false;
for device_info in api.device_list() {
// Check if the scanner is in the list
if device_info.vendor_id() == vid && device_info.product_id() == pid {
device_found = true;
if let Ok(device) = device_info.open_device(&api) {
println!("Barcode scanner found & opened successfully.");
// Allocate mem buffer for data
let mut mem_buf = Vec::<u8>::with_capacity(256);
// Start reading data
loop {
match device.read(&mut mem_buf) {
Ok(count) => {
let data = &mem_buf[..count];
println!("Scanned data: {:?}", data);
app.emit_all("scanner-data", Payload { message: "Tauri is awesome!".into() }).unwrap();
}
Err(e) => {
eprintln!("Error reading from HID device: {:?}", e);
break;
}
}
}
}
}
}
if !device_found {
println!("Barcode scanner not found.");
}
}
Err(e) => {
eprintln!("Error initializing HID API: {}", e);
}
}
}
fn main() {
tauri::Builder::default()
.setup(|app|{
detect_barcode_scanner(app);
Ok(())
})
.invoke_handler(tauri::generate_handler![detect_barcode_scanner])
.run(tauri::generate_context!())
.expect("failed to run app");
}
I now have the problem that the emit_all
function is only available on the App Handle, so I call the function detect_barcode_scanner from the setup function.
But this gives that the events send by emit_all
function get lost because the UI is not ready.
This also prevents me from keeping the connection open to the device to continue to receive messages after the setup.
Any guidelines / tips on how to solve this ?
You'd want to manage the state differently, and in async
way.
Rust's paradigm of managing the state without sharing memory differs from other languages.
I'd recommend to do a slightly simpler version:
Here's a quick code (not tested):
fn main() {
let (in_tx, in_rx) = mpsc::channel(1);
let (out_tx, mut out_rx) = mpsc::channel(1);
tauri::Builder::default()
.manage(AsyncProcInputTx {
inner: Mutex::new(async_proc_input_tx),
})
.invoke_handler(tauri::generate_handler![your_function_here])
.setup(|app| {
tauri::async_runtime::spawn(async move {
async_process_model(
input_rx,
out_tx,
).await
});
let app_handle = app.handle();
tauri::async_runtime::spawn(async move {
loop {
if let Some(hid_device) = out_rx.recv().await {
// your_function_here(hid_device, &app_handle);
}
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}