pub mod actions;
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::mpsc;
pub struct BluetoothBlePresenter;
impl BluetoothBlePresenter {
#[cfg(all(unix, feature = "bluetooth"))]
pub async fn spawn_with_llm_actions(
device_name: String,
llm: crate::llm::ollama_client::OllamaClient,
state: Arc<crate::state::app_state::AppState>,
tx: mpsc::UnboundedSender<String>,
id: crate::state::ServerId,
inst: String,
) -> Result<std::net::SocketAddr> {
let inst = inst.trim();
let inst = if inst.is_empty() {
String::new()
} else if inst.ends_with(['.', '!', '?']) {
format!("{inst} ")
} else {
format!("{inst}. ")
};
let presenter_instruction = format!(
"Configure as a BLE HID presentation clicker with HID Service (UUID: 0x1812). {}\
Add HID Information, HID Report Map, HID Report (input, notify) and HID Control \
Point characteristics. The input report is a {}-byte boot-keyboard report \
(modifier byte, reserved byte, then a six-slot key array); a slide advance is \
Keyboard PageDown (0x{:02X}) in the first key slot, followed by an all-zero \
report to release it.",
inst,
HID_PRESENTER_INPUT_REPORT_LEN,
presenter_keys::NEXT_SLIDE,
);
crate::server::bluetooth_ble::BluetoothBle::spawn_with_llm_actions(
device_name,
llm,
state,
tx,
id,
presenter_instruction,
)
.await
}
}
#[cfg(any(all(unix, not(feature = "bluetooth")), not(unix)))]
impl BluetoothBlePresenter {
pub async fn spawn_with_llm_actions(
_: String,
_: crate::llm::ollama_client::OllamaClient,
_: Arc<crate::state::app_state::AppState>,
_: mpsc::UnboundedSender<String>,
_: crate::state::ServerId,
_: String,
) -> Result<std::net::SocketAddr> {
anyhow::bail!("BLE presenter not enabled")
}
}
pub mod presenter_keys {
pub const NEXT_SLIDE: u8 = 0x4E;
pub const PREVIOUS_SLIDE: u8 = 0x4B;
pub const START_PRESENTATION: u8 = 0x3E;
pub const END_PRESENTATION: u8 = 0x29;
pub const BLANK_SCREEN: u8 = 0x37;
}
pub const HID_PRESENTER_INPUT_REPORT_LEN: usize = 8;
pub const HID_PRESENTER_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, ];
pub fn build_presenter_report(control: &str) -> Option<[u8; HID_PRESENTER_INPUT_REPORT_LEN]> {
let keycode = match control {
"next_slide" => presenter_keys::NEXT_SLIDE,
"previous_slide" => presenter_keys::PREVIOUS_SLIDE,
"start_presentation" => presenter_keys::START_PRESENTATION,
"end_presentation" => presenter_keys::END_PRESENTATION,
"blank_screen" => presenter_keys::BLANK_SCREEN,
_ => return None,
};
let mut report = [0u8; HID_PRESENTER_INPUT_REPORT_LEN];
report[2] = keycode;
Some(report)
}