Files
sexy/packages/buttplug/src/webbluetooth/webbluetooth_manager.rs
Sebastian Krüger 6ea4ed1933
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 7m30s
feat: upgrade buttplug package to protocol v4 and WASM v10
Upgrade the buttplug TypeScript client from class-based v3 protocol to
interface-based v4 protocol, and the Rust/WASM server from the monolithic
buttplug 9.0.9 crate to the split buttplug_core/buttplug_server/
buttplug_server_device_config 10.0.0 crates.

TypeScript changes:
- Messages are now plain interfaces with msgId()/setMsgId() helpers
- ActuatorType → OutputType, SensorType → InputType
- ScalarCmd/RotateCmd/LinearCmd → OutputCmd, SensorReadCmd → InputCmd
- Client.ts → ButtplugClient.ts, new DeviceCommand/DeviceFeature files
- Devices getter returns Map instead of array
- Removed class-transformer/reflect-metadata dependencies

Rust/WASM changes:
- Split imports across buttplug_core, buttplug_server, buttplug_server_device_config
- Removed ButtplugServerDowngradeWrapper (use ButtplugServer directly)
- Replaced ButtplugFuture/ButtplugFutureStateShared with tokio::sync::oneshot
- Updated Hardware::new for new 6-arg signature
- Uses git fork (valknarthing/buttplug) to fix missing wasm deps in buttplug_core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 14:46:47 +01:00

131 lines
4.5 KiB
Rust

use super::webbluetooth_hardware::WebBluetoothHardwareConnector;
use buttplug_core::ButtplugResultFuture;
use buttplug_server_device_config::ProtocolCommunicationSpecifier;
use buttplug_server::device::hardware::communication::{
HardwareCommunicationManager, HardwareCommunicationManagerBuilder,
HardwareCommunicationManagerEvent,
};
use futures::future;
use js_sys::Array;
use tokio::sync::mpsc::Sender;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::BluetoothDevice;
use crate::create_test_dcm;
#[derive(Default)]
pub struct WebBluetoothCommunicationManagerBuilder {
}
impl HardwareCommunicationManagerBuilder for WebBluetoothCommunicationManagerBuilder {
fn finish(&mut self, sender: Sender<HardwareCommunicationManagerEvent>) -> Box<dyn HardwareCommunicationManager> {
Box::new(WebBluetoothCommunicationManager {
sender,
})
}
}
pub struct WebBluetoothCommunicationManager {
sender: Sender<HardwareCommunicationManagerEvent>,
}
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
impl HardwareCommunicationManager for WebBluetoothCommunicationManager {
fn name(&self) -> &'static str {
"WebBluetoothCommunicationManager"
}
fn can_scan(&self) -> bool {
true
}
fn start_scanning(&mut self) -> ButtplugResultFuture {
info!("WebBluetooth manager scanning");
let sender_clone = self.sender.clone();
spawn_local(async move {
// Build the filter block
let nav = web_sys::window().unwrap().navigator();
if nav.bluetooth().is_none() {
error!("WebBluetooth is not supported on this browser");
return;
}
info!("WebBluetooth supported by browser, continuing with scan.");
// HACK: As of buttplug v5, we can't just create a HardwareCommunicationManager anymore. This is
// using a test method to create a filled out DCM, which will work for now because there's no
// way for anyone to add device configurations through FFI yet anyways.
let config_manager = create_test_dcm(false);
let options = web_sys::RequestDeviceOptions::new();
let filters = Array::new();
let optional_services = Array::new();
for vals in config_manager.base_communication_specifiers().iter() {
for config in vals.1.iter() {
if let ProtocolCommunicationSpecifier::BluetoothLE(btle) = &config {
for name in btle.names() {
let filter = web_sys::BluetoothLeScanFilterInit::new();
if name.contains("*") {
let mut name_clone = name.clone();
name_clone.pop();
filter.set_name_prefix(&name_clone);
} else {
filter.set_name(&name);
}
filters.push(&filter.into());
}
for (service, _) in btle.services() {
optional_services.push(&service.to_string().into());
}
}
}
}
options.set_filters(&filters.into());
options.set_optional_services(&optional_services.into());
let nav = web_sys::window().unwrap().navigator();
//nav.bluetooth().get_availability();
//JsFuture::from(nav.bluetooth().request_device()).await;
match JsFuture::from(nav.bluetooth().unwrap().request_device(&options)).await {
Ok(device) => {
let bt_device = BluetoothDevice::from(device);
if bt_device.name().is_none() {
return;
}
let name = bt_device.name().unwrap();
let address = bt_device.id();
let device_creator = Box::new(WebBluetoothHardwareConnector::new(bt_device));
if sender_clone
.send(HardwareCommunicationManagerEvent::DeviceFound {
name,
address,
creator: device_creator,
})
.await
.is_err()
{
error!("Device manager receiver dropped, cannot send device found message.");
} else {
info!("WebBluetooth device found.");
}
}
Err(e) => {
error!("Error while trying to start bluetooth scan: {:?}", e);
}
};
let _ = sender_clone
.send(HardwareCommunicationManagerEvent::ScanningFinished)
.await;
});
Box::pin(future::ready(Ok(())))
}
fn stop_scanning(&mut self) -> ButtplugResultFuture {
Box::pin(future::ready(Ok(())))
}
}