Buttplug sex toy control library
at dev 212 lines 6.4 kB view raw
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}