Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved.
4//
5// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
6// for full license information.
7
8use buttplug_server_device_config::Endpoint;
9use byteorder::LittleEndian;
10
11use crate::device::{
12 hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd},
13 protocol::{ProtocolHandler, generic_protocol_setup},
14};
15use buttplug_core::{
16 errors::ButtplugDeviceError,
17 message::{self, InputData, InputReadingV4, InputTypeData},
18};
19use byteorder::WriteBytesExt;
20use futures::future::{BoxFuture, FutureExt};
21use std::sync::{
22 Arc,
23 atomic::{AtomicU16, Ordering},
24};
25
26generic_protocol_setup!(XInput, "xinput");
27
28#[derive(Default)]
29pub struct XInput {
30 speeds: [AtomicU16; 2],
31}
32
33impl ProtocolHandler for XInput {
34 fn handle_output_vibrate_cmd(
35 &self,
36 feature_index: u32,
37 feature_id: uuid::Uuid,
38 speed: u32,
39 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
40 self.speeds[feature_index as usize].store(speed as u16, Ordering::Relaxed);
41 // XInput is fast enough that we can ignore the commands handed
42 // back by the manager and just form our own packet. This means
43 // we'll just use the manager's return for command validity
44 // checking.
45 let mut cmd = vec![];
46 if cmd
47 .write_u16::<LittleEndian>(self.speeds[1].load(Ordering::Relaxed))
48 .is_err()
49 || cmd
50 .write_u16::<LittleEndian>(self.speeds[0].load(Ordering::Relaxed))
51 .is_err()
52 {
53 return Err(ButtplugDeviceError::ProtocolSpecificError(
54 "XInput".to_owned(),
55 "Cannot convert XInput value for processing".to_owned(),
56 ));
57 }
58 Ok(vec![
59 HardwareWriteCmd::new(&[feature_id], Endpoint::Tx, cmd, false).into(),
60 ])
61 }
62
63 fn handle_input_read_cmd(
64 &self,
65 device_index: u32,
66 device: Arc<Hardware>,
67 feature_index: u32,
68 feature_id: uuid::Uuid,
69 _sensor_type: message::InputType,
70 ) -> BoxFuture<'_, Result<InputReadingV4, ButtplugDeviceError>> {
71 async move {
72 let reading = device
73 .read_value(&HardwareReadCmd::new(feature_id, Endpoint::Rx, 0, 0))
74 .await?;
75 let battery = match reading.data()[0] {
76 0 => 0u8,
77 1 => 33,
78 2 => 66,
79 3 => 100,
80 _ => {
81 return Err(ButtplugDeviceError::DeviceCommunicationError(
82 "something went wrong".to_string(),
83 ));
84 }
85 };
86 Ok(message::InputReadingV4::new(
87 device_index,
88 feature_index,
89 InputTypeData::Battery(InputData::new(battery)),
90 ))
91 }
92 .boxed()
93 }
94}