pub mod actions;
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::info;
use crate::console_error;
use crate::llm::ollama_client::OllamaClient;
use crate::server::bluetooth_ble::BluetoothBle;
use crate::state::app_state::AppState;
pub struct BluetoothBleKeyboard;
impl BluetoothBleKeyboard {
#[cfg(all(unix, feature = "bluetooth"))]
pub async fn spawn_with_llm_actions(
device_name: String,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
server_id: crate::state::ServerId,
instruction: String,
) -> Result<std::net::SocketAddr> {
info!("Starting BLE HID Keyboard server: {}", device_name);
let keyboard_instruction = format!(
"Configure as a BLE HID keyboard with HID Service (UUID: 0x1812). {} {}",
instruction,
"Add HID Report Map, HID Report Input, HID Information, and HID Control Point characteristics."
);
let log_tx = status_tx.clone();
let log_name = device_name.clone();
match BluetoothBle::spawn_with_llm_actions(
device_name,
llm_client,
app_state,
status_tx,
server_id,
keyboard_instruction,
)
.await
{
Ok(addr) => Ok(addr),
Err(e) => {
console_error!(log_tx, "{}", radio_start_failure(&log_name, &e));
Err(e)
}
}
}
}
pub fn radio_start_failure(device_name: &str, err: &anyhow::Error) -> String {
format!(
"BLE HID keyboard '{device_name}' could not start \
(decision=refused_adapter_unavailable): {err}. No HID service was published and \
nothing is advertising."
)
}
#[cfg(any(all(unix, not(feature = "bluetooth")), not(unix)))]
impl BluetoothBleKeyboard {
pub async fn spawn_with_llm_actions(
_device_name: String,
_llm_client: OllamaClient,
_app_state: Arc<AppState>,
_status_tx: mpsc::UnboundedSender<String>,
_server_id: crate::state::ServerId,
_instruction: String,
) -> Result<std::net::SocketAddr> {
anyhow::bail!(
"BLE keyboard support not enabled - compile with --features bluetooth-ble-keyboard"
)
}
}
pub mod hid_keycodes {
pub const KEY_A: u8 = 0x04;
pub const KEY_B: u8 = 0x05;
pub const KEY_C: u8 = 0x06;
pub const KEY_ENTER: u8 = 0x28;
pub const KEY_SPACE: u8 = 0x2C;
pub const MOD_LEFT_CTRL: u8 = 0x01;
pub const MOD_LEFT_SHIFT: u8 = 0x02;
pub const MOD_LEFT_ALT: u8 = 0x04;
pub const MOD_LEFT_GUI: u8 = 0x08;
pub fn char_to_keycode(c: char) -> Option<(u8, u8)> {
match c {
'a'..='z' => Some((0, KEY_A + (c as u8 - b'a'))),
'A'..='Z' => Some((MOD_LEFT_SHIFT, KEY_A + (c as u8 - b'A'))),
' ' => Some((0, KEY_SPACE)),
'\n' => Some((0, KEY_ENTER)),
_ => None,
}
}
}
pub const HID_KEYBOARD_INPUT_REPORT_LEN: usize = 8;
pub const HID_KEYBOARD_REPORT_DESCRIPTOR: &[u8] = &[
0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 0x00, 0xC0, ];