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 super::xinput_device_comm_manager::XInputControllerIndex;
9use async_trait::async_trait;
10use buttplug_core::{errors::ButtplugDeviceError, util::async_manager};
11use buttplug_server::device::hardware::{
12 GenericHardwareSpecializer,
13 Hardware,
14 HardwareConnector,
15 HardwareEvent,
16 HardwareInternal,
17 HardwareReadCmd,
18 HardwareReading,
19 HardwareSpecializer,
20 HardwareSubscribeCmd,
21 HardwareUnsubscribeCmd,
22 HardwareWriteCmd,
23 communication::HardwareSpecificError,
24};
25use buttplug_server_device_config::{Endpoint, ProtocolCommunicationSpecifier, XInputSpecifier};
26use byteorder::{LittleEndian, ReadBytesExt};
27use futures::future::{self, BoxFuture, FutureExt};
28use rusty_xinput::{XInputHandle, XInputUsageError};
29use std::{
30 fmt::{self, Debug},
31 io::Cursor,
32 time::Duration,
33};
34use tokio::sync::broadcast;
35use tokio_util::sync::CancellationToken;
36
37pub(super) fn create_address(index: XInputControllerIndex) -> String {
38 index.to_string()
39}
40
41async fn check_gamepad_connectivity(
42 index: XInputControllerIndex,
43 sender: broadcast::Sender<HardwareEvent>,
44 cancellation_token: CancellationToken,
45) {
46 let handle = rusty_xinput::XInputHandle::load_default()
47 .expect("Always loads in windows, this shouldn't run elsewhere.");
48 loop {
49 // If we can't get state, assume we have disconnected.
50 if handle.get_state(index as u32).is_err() {
51 info!("XInput gamepad {} has disconnected.", index);
52 // If this fails, we don't care because we're exiting anyways.
53 let _ = sender.send(HardwareEvent::Disconnected(create_address(index)));
54 return;
55 }
56 tokio::select! {
57 _ = cancellation_token.cancelled() => return,
58 _ = tokio::time::sleep(Duration::from_millis(500)) => continue
59 }
60 }
61}
62
63pub struct XInputHardwareConnector {
64 index: XInputControllerIndex,
65}
66
67impl XInputHardwareConnector {
68 pub fn new(index: XInputControllerIndex) -> Self {
69 Self { index }
70 }
71}
72
73impl Debug for XInputHardwareConnector {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.debug_struct("XInputHardwareConnector")
76 .field("index", &self.index)
77 .finish()
78 }
79}
80
81#[async_trait]
82impl HardwareConnector for XInputHardwareConnector {
83 fn specifier(&self) -> ProtocolCommunicationSpecifier {
84 ProtocolCommunicationSpecifier::XInput(XInputSpecifier::default())
85 }
86
87 async fn connect(&mut self) -> Result<Box<dyn HardwareSpecializer>, ButtplugDeviceError> {
88 debug!("Emitting a new xbox device impl.");
89 let hardware_internal = XInputHardware::new(self.index);
90 let hardware = Hardware::new(
91 &self.index.to_string(),
92 &create_address(self.index),
93 &[Endpoint::Tx, Endpoint::Rx],
94 &None,
95 false,
96 Box::new(hardware_internal),
97 );
98 Ok(Box::new(GenericHardwareSpecializer::new(hardware)))
99 }
100}
101
102#[derive(Clone, Debug)]
103pub struct XInputHardware {
104 handle: XInputHandle,
105 index: XInputControllerIndex,
106 event_sender: broadcast::Sender<HardwareEvent>,
107 cancellation_token: CancellationToken,
108}
109
110impl XInputHardware {
111 pub fn new(index: XInputControllerIndex) -> Self {
112 let (device_event_sender, _) = broadcast::channel(256);
113 let token = CancellationToken::new();
114 let child = token.child_token();
115 let sender = device_event_sender.clone();
116 async_manager::spawn(async move {
117 check_gamepad_connectivity(index, sender, child).await;
118 });
119 Self {
120 handle: rusty_xinput::XInputHandle::load_default().expect("The DLL should load as long as we're on windows, and we don't get here if we're not on windows."),
121 index,
122 event_sender: device_event_sender,
123 cancellation_token: token,
124 }
125 }
126}
127
128impl HardwareInternal for XInputHardware {
129 fn event_stream(&self) -> broadcast::Receiver<HardwareEvent> {
130 self.event_sender.subscribe()
131 }
132
133 fn disconnect(&self) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
134 future::ready(Ok(())).boxed()
135 }
136
137 fn read_value(
138 &self,
139 _msg: &HardwareReadCmd,
140 ) -> BoxFuture<'static, Result<HardwareReading, ButtplugDeviceError>> {
141 let handle = self.handle.clone();
142 let index = self.index;
143 async move {
144 let battery = handle
145 .get_gamepad_battery_information(index as u32)
146 .map_err(|e| {
147 ButtplugDeviceError::from(ButtplugDeviceError::DeviceSpecificError(
148 HardwareSpecificError::HardwareSpecificError("Xinput".to_string(), format!("{e:?}"))
149 .to_string(),
150 ))
151 })?;
152 Ok(HardwareReading::new(
153 Endpoint::Rx,
154 &[battery.battery_level.0],
155 ))
156 }
157 .boxed()
158 }
159
160 fn write_value(
161 &self,
162 msg: &HardwareWriteCmd,
163 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
164 let handle = self.handle.clone();
165 let index = self.index;
166 let data = msg.data().clone();
167 async move {
168 let mut cursor = Cursor::new(data);
169 let left_motor_speed = cursor
170 .read_u16::<LittleEndian>()
171 .expect("Packed in protocol, infallible");
172 let right_motor_speed = cursor
173 .read_u16::<LittleEndian>()
174 .expect("Packed in protocol, infallible");
175 handle
176 .set_state(index as u32, left_motor_speed, right_motor_speed)
177 .map_err(|e: XInputUsageError| {
178 ButtplugDeviceError::from(ButtplugDeviceError::DeviceSpecificError(
179 HardwareSpecificError::HardwareSpecificError("Xinput".to_string(), format!("{e:?}"))
180 .to_string(),
181 ))
182 })
183 }
184 .boxed()
185 }
186
187 fn subscribe(
188 &self,
189 _msg: &HardwareSubscribeCmd,
190 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
191 future::ready(Err(ButtplugDeviceError::UnhandledCommand(
192 "XInput hardware does not support subscribe".to_owned(),
193 )))
194 .boxed()
195 }
196
197 fn unsubscribe(
198 &self,
199 _msg: &HardwareUnsubscribeCmd,
200 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
201 future::ready(Err(ButtplugDeviceError::UnhandledCommand(
202 "XInput hardware does not support unsubscribe".to_owned(),
203 )))
204 .boxed()
205 }
206}
207
208impl Drop for XInputHardware {
209 fn drop(&mut self) {
210 self.cancellation_token.cancel();
211 }
212}