Buttplug sex toy control library
at master 10 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 8//! Representation and management of devices connected to the server. 9 10use crate::device::{ClientDeviceCommandValue, ClientDeviceOutputCommand}; 11 12use crate::{ 13 ButtplugClientMessageSender, 14 ButtplugClientResultFuture, 15 create_boxed_future_client_error, 16 device::ClientDeviceFeature, 17}; 18use buttplug_core::message::InputType; 19use buttplug_core::{ 20 errors::ButtplugDeviceError, 21 message::{ 22 ButtplugServerMessageV4, 23 DeviceFeature, 24 DeviceMessageInfoV4, 25 OutputType, 26 StopDeviceCmdV0, 27 }, 28 util::stream::convert_broadcast_receiver_to_stream, 29}; 30use futures::{FutureExt, Stream, future}; 31use getset::{CopyGetters, Getters}; 32use log::*; 33use std::collections::BTreeMap; 34use std::{ 35 fmt, 36 sync::{ 37 Arc, 38 atomic::{AtomicBool, Ordering}, 39 }, 40}; 41use tokio::sync::broadcast; 42 43/// Enum for messages going to a [ButtplugClientDevice] instance. 44#[derive(Clone, Debug)] 45// The message enum is what we'll fly with this most of the time. DeviceRemoved/ClientDisconnect 46// will happen at most once, so we don't care that those contentless traits still take up > 200 47// bytes. 48#[allow(clippy::large_enum_variant)] 49pub enum ButtplugClientDeviceEvent { 50 /// Device has disconnected from server. 51 DeviceRemoved, 52 /// Client has disconnected from server. 53 ClientDisconnect, 54 /// Message was received from server for that specific device. 55 Message(ButtplugServerMessageV4), 56} 57 58#[derive(Getters, CopyGetters, Clone)] 59/// Client-usable representation of device connected to the corresponding 60/// [ButtplugServer][crate::server::ButtplugServer] 61/// 62/// [ButtplugClientDevice] instances are obtained from the 63/// [ButtplugClient][super::ButtplugClient], and allow the user to send commands 64/// to a device connected to the server. 65pub struct ButtplugClientDevice { 66 /// Name of the device 67 #[getset(get = "pub")] 68 name: String, 69 /// Display name of the device 70 #[getset(get = "pub")] 71 display_name: Option<String>, 72 /// Index of the device, matching the index in the 73 /// [ButtplugServer][crate::server::ButtplugServer]'s 74 /// [DeviceManager][crate::server::device_manager::DeviceManager]. 75 #[getset(get_copy = "pub")] 76 index: u32, 77 /// Actuators and sensors available on the device. 78 #[getset(get = "pub")] 79 device_features: BTreeMap<u32, ClientDeviceFeature>, 80 /// Sends commands from the [ButtplugClientDevice] instance to the 81 /// [ButtplugClient][super::ButtplugClient]'s event loop, which will then send 82 /// the message on to the [ButtplugServer][crate::server::ButtplugServer] 83 /// through the connector. 84 event_loop_sender: ButtplugClientMessageSender, 85 internal_event_sender: broadcast::Sender<ButtplugClientDeviceEvent>, 86 /// True if this [ButtplugClientDevice] is currently connected to the 87 /// [ButtplugServer][crate::server::ButtplugServer]. 88 device_connected: Arc<AtomicBool>, 89 /// True if the [ButtplugClient][super::ButtplugClient] that generated this 90 /// [ButtplugClientDevice] instance is still connected to the 91 /// [ButtplugServer][crate::server::ButtplugServer]. 92 client_connected: Arc<AtomicBool>, 93} 94 95impl ButtplugClientDevice { 96 /// Creates a new [ButtplugClientDevice] instance 97 /// 98 /// Fills out the struct members for [ButtplugClientDevice]. 99 /// `device_connected` and `client_connected` are automatically set to true 100 /// because we assume we're only created connected devices. 101 /// 102 /// # Why is this pub(super)? 103 /// 104 /// There's really no reason for anyone but a 105 /// [ButtplugClient][super::ButtplugClient] to create a 106 /// [ButtplugClientDevice]. A [ButtplugClientDevice] is mostly a shim around 107 /// the [ButtplugClient] that generated it, with some added convenience 108 /// functions for forming device control messages. 109 pub(super) fn new( 110 name: &str, 111 display_name: &Option<String>, 112 index: u32, 113 device_features: &BTreeMap<u32, DeviceFeature>, 114 message_sender: &ButtplugClientMessageSender, 115 ) -> Self { 116 info!( 117 "Creating client device {} with index {} and messages {:?}.", 118 name, index, device_features 119 ); 120 let (event_sender, _) = broadcast::channel(256); 121 let device_connected = Arc::new(AtomicBool::new(true)); 122 let client_connected = Arc::new(AtomicBool::new(true)); 123 124 Self { 125 name: name.to_owned(), 126 display_name: display_name.clone(), 127 index, 128 device_features: device_features 129 .iter() 130 .map(|(i, x)| (*i, ClientDeviceFeature::new(index, *i, x, message_sender))) 131 .collect(), 132 event_loop_sender: message_sender.clone(), 133 internal_event_sender: event_sender, 134 device_connected, 135 client_connected, 136 } 137 } 138 139 pub(crate) fn new_from_device_info( 140 info: &DeviceMessageInfoV4, 141 sender: &ButtplugClientMessageSender, 142 ) -> Self { 143 ButtplugClientDevice::new( 144 info.device_name(), 145 info.device_display_name(), 146 info.device_index(), 147 info.device_features(), 148 sender, 149 ) 150 } 151 152 pub fn connected(&self) -> bool { 153 self.device_connected.load(Ordering::Relaxed) 154 } 155 156 pub fn event_stream(&self) -> Box<dyn Stream<Item = ButtplugClientDeviceEvent> + Send + Unpin> { 157 Box::new(Box::pin(convert_broadcast_receiver_to_stream( 158 self.internal_event_sender.subscribe(), 159 ))) 160 } 161 162 fn filter_device_actuators(&self, actuator_type: OutputType) -> Vec<ClientDeviceFeature> { 163 self 164 .device_features 165 .iter() 166 .filter(|x| { 167 if let Some(output) = x.1.feature().output() { 168 output.contains(actuator_type) 169 } else { 170 false 171 } 172 }) 173 .map(|(_, x)| x) 174 .cloned() 175 .collect() 176 } 177 178 fn set_client_value( 179 &self, 180 client_device_command: &ClientDeviceOutputCommand, 181 ) -> ButtplugClientResultFuture { 182 let features = self.filter_device_actuators(client_device_command.into()); 183 if features.is_empty() { 184 // TODO err 185 } 186 let mut fut_vec: Vec<ButtplugClientResultFuture> = vec![]; 187 for x in features { 188 let val = x.convert_client_cmd_to_output_cmd(client_device_command); 189 match val { 190 Ok(v) => fut_vec.push(self.event_loop_sender.send_message_expect_ok(v.into())), 191 Err(e) => return future::ready(Err(e)).boxed(), 192 } 193 } 194 async move { 195 futures::future::try_join_all(fut_vec).await?; 196 Ok(()) 197 } 198 .boxed() 199 } 200 201 pub fn send_command( 202 &self, 203 client_device_command: &ClientDeviceOutputCommand, 204 ) -> ButtplugClientResultFuture { 205 self.set_client_value(client_device_command) 206 } 207 208 pub fn vibrate_features(&self) -> Vec<ClientDeviceFeature> { 209 self.filter_device_actuators(OutputType::Vibrate) 210 } 211 212 /// Commands device to vibrate, assuming it has the features to do so. 213 pub fn vibrate(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 214 let val = level.into(); 215 self.set_client_value(&match val { 216 ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Vibrate(v as u32), 217 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::VibrateFloat(f), 218 }) 219 } 220 221 pub fn has_battery_level(&self) -> bool { 222 self.device_features.iter().any(|x| { 223 x.1 224 .feature() 225 .input() 226 .as_ref() 227 .is_some_and(|x| x.contains(InputType::Battery)) 228 }) 229 } 230 231 pub fn battery_level(&self) -> ButtplugClientResultFuture<u32> { 232 if let Some(battery) = self.device_features.iter().find(|x| { 233 x.1 234 .feature() 235 .input() 236 .as_ref() 237 .is_some_and(|x| x.contains(InputType::Battery)) 238 }) { 239 battery.1.battery_level() 240 } else { 241 create_boxed_future_client_error( 242 ButtplugDeviceError::DeviceFeatureMismatch( 243 "Device does not have battery feature available".to_owned(), 244 ) 245 .into(), 246 ) 247 } 248 } 249 250 pub fn has_rssi_level(&self) -> bool { 251 self.device_features.iter().any(|x| { 252 x.1 253 .feature() 254 .input() 255 .as_ref() 256 .is_some_and(|x| x.contains(InputType::Rssi)) 257 }) 258 } 259 260 pub fn rssi_level(&self) -> ButtplugClientResultFuture<i8> { 261 if let Some(rssi) = self.device_features.iter().find(|x| { 262 x.1 263 .feature() 264 .input() 265 .as_ref() 266 .is_some_and(|x| x.contains(InputType::Rssi)) 267 }) { 268 rssi.1.rssi_level() 269 } else { 270 create_boxed_future_client_error( 271 ButtplugDeviceError::DeviceFeatureMismatch( 272 "Device does not have RSSI feature available".to_owned(), 273 ) 274 .into(), 275 ) 276 } 277 } 278 279 /// Commands device to stop all movement. 280 pub fn stop(&self) -> ButtplugClientResultFuture { 281 // All devices accept StopDeviceCmd 282 self 283 .event_loop_sender 284 .send_message_expect_ok(StopDeviceCmdV0::new(self.index).into()) 285 } 286 287 pub(crate) fn set_device_connected(&self, connected: bool) { 288 self.device_connected.store(connected, Ordering::Relaxed); 289 } 290 291 pub(crate) fn set_client_connected(&self, connected: bool) { 292 self.client_connected.store(connected, Ordering::Relaxed); 293 } 294 295 pub(crate) fn queue_event(&self, event: ButtplugClientDeviceEvent) { 296 if self.internal_event_sender.receiver_count() == 0 { 297 // We can drop devices before we've hooked up listeners or after the device manager drops, 298 // which is common, so only show this when in debug. 299 debug!("No handlers for device event, dropping event: {:?}", event); 300 return; 301 } 302 self 303 .internal_event_sender 304 .send(event) 305 .expect("Checked for receivers already."); 306 } 307} 308 309impl Eq for ButtplugClientDevice { 310} 311 312impl PartialEq for ButtplugClientDevice { 313 fn eq(&self, other: &Self) -> bool { 314 self.index == other.index 315 } 316} 317 318impl fmt::Debug for ButtplugClientDevice { 319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 320 f.debug_struct("ButtplugClientDevice") 321 .field("name", &self.name) 322 .field("index", &self.index) 323 .finish() 324 } 325}