Buttplug sex toy control library
at dev 19 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 crate::ButtplugDeviceConfigError; 9 10use buttplug_core::message::{ 11 DeviceFeature, 12 DeviceFeatureInput, 13 DeviceFeatureInputBuilder, 14 DeviceFeatureInputProperties, 15 DeviceFeatureOutput, 16 DeviceFeatureOutputBuilder, 17 DeviceFeatureOutputPositionWithDurationProperties, 18 DeviceFeatureOutputValueProperties, 19 InputCommandType, 20 InputType, 21 OutputType, 22}; 23use getset::{CopyGetters, Getters, Setters}; 24use std::{collections::HashSet, ops::RangeInclusive}; 25use uuid::Uuid; 26 27/// Holds a combination of ranges. Base range is defined in the base device config, user range is 28/// defined by the user later to be a sub-range of the base range. User range only stores in u32, 29/// ranges with negatives (i.e. rotate with direction) are considered to be symettric around 0, we 30/// let the system handle that conversion. 31#[derive(Debug, Clone, Getters)] 32#[getset(get = "pub")] 33pub struct RangeWithLimit { 34 base: RangeInclusive<i32>, 35 internal_base: RangeInclusive<u32>, 36 user: Option<RangeInclusive<u32>>, 37} 38 39impl From<RangeInclusive<i32>> for RangeWithLimit { 40 fn from(value: RangeInclusive<i32>) -> Self { 41 Self::new(&value) 42 } 43} 44 45impl RangeWithLimit { 46 pub fn new(base: &RangeInclusive<i32>) -> Self { 47 Self { 48 base: base.clone(), 49 internal_base: RangeInclusive::new(0, *base.end() as u32), 50 user: None, 51 } 52 } 53 54 pub fn new_with_user(base: &RangeInclusive<i32>, user: &Option<RangeInclusive<u32>>) -> Self { 55 Self { 56 base: base.clone(), 57 internal_base: RangeInclusive::new(0, *base.end() as u32), 58 user: user.clone(), 59 } 60 } 61 62 pub fn step_limit(&self) -> RangeInclusive<i32> { 63 if *self.base.start() < 0 { 64 RangeInclusive::new(-(self.step_count() as i32), self.step_count() as i32) 65 } else { 66 RangeInclusive::new(0, self.step_count() as i32) 67 } 68 } 69 70 pub fn step_count(&self) -> u32 { 71 if let Some(user) = &self.user { 72 *user.end() - *user.start() 73 } else { 74 *self.base.end() as u32 75 } 76 } 77 78 pub fn try_new( 79 base: &RangeInclusive<i32>, 80 user: &Option<RangeInclusive<u32>>, 81 ) -> Result<Self, ButtplugDeviceConfigError> { 82 let truncated_base = RangeInclusive::new(0, *base.end() as u32); 83 if let Some(user) = user { 84 if user.is_empty() { 85 Err(ButtplugDeviceConfigError::InvalidUserRange) 86 } else if *user.start() < *truncated_base.start() 87 || *user.end() > *truncated_base.end() 88 || *user.start() > *truncated_base.end() 89 || *user.end() < *truncated_base.start() 90 { 91 Err(ButtplugDeviceConfigError::InvalidUserRange) 92 } else { 93 Ok(Self { 94 base: (*base).clone(), 95 internal_base: truncated_base, 96 user: Some((*user).clone()), 97 }) 98 } 99 } else if base.is_empty() { 100 Err(ButtplugDeviceConfigError::BaseRangeRequired) 101 } else { 102 Ok(Self { 103 base: (*base).clone(), 104 internal_base: truncated_base, 105 user: None, 106 }) 107 } 108 } 109} 110 111#[derive(Debug, Clone, Getters, CopyGetters)] 112pub struct ServerDeviceFeatureOutputValueProperties { 113 #[getset(get = "pub")] 114 value: RangeWithLimit, 115 #[getset(get_copy = "pub")] 116 disabled: bool, 117} 118 119impl ServerDeviceFeatureOutputValueProperties { 120 pub fn new(value: &RangeWithLimit, disabled: bool) -> Self { 121 Self { 122 value: value.clone(), 123 disabled, 124 } 125 } 126 127 pub fn calculate_scaled_float(&self, value: f64) -> Result<i32, ButtplugDeviceConfigError> { 128 if !(0.0..=1.0).contains(&value) { 129 Err(ButtplugDeviceConfigError::InvalidFloatConversion(value)) 130 } else { 131 let value = if value < 0.000001 { 0f64 } else { value }; 132 self.calculate_scaled_value((self.value.step_count() as f64 * value).ceil() as i32) 133 } 134 } 135 136 // We'll get a number from 0-x here. We'll need to calculate it with in the range we have. We'll 137 // consider negative ranges symmetric. 138 pub fn calculate_scaled_value(&self, value: i32) -> Result<i32, ButtplugDeviceConfigError> { 139 let range = if let Some(user_range) = self.value.user() { 140 user_range 141 } else { 142 self.value.internal_base() 143 }; 144 let current_value = value.unsigned_abs(); 145 let mult = if value < 0 { -1 } else { 1 }; 146 if value != 0 && range.contains(&(range.start() + current_value)) { 147 Ok((range.start() + current_value) as i32 * mult) 148 } else if value == 0 { 149 Ok(0) 150 } else { 151 Err(ButtplugDeviceConfigError::InvalidOutputValue( 152 value, 153 format!("{:?}", range), 154 )) 155 } 156 } 157} 158 159impl From<&ServerDeviceFeatureOutputValueProperties> for DeviceFeatureOutputValueProperties { 160 fn from(val: &ServerDeviceFeatureOutputValueProperties) -> Self { 161 DeviceFeatureOutputValueProperties::new(&val.value().step_limit()) 162 } 163} 164 165#[derive(Debug, Clone, Getters, CopyGetters)] 166pub struct ServerDeviceFeatureOutputPositionProperties { 167 #[getset(get = "pub")] 168 position: RangeWithLimit, 169 #[getset(get_copy = "pub")] 170 disabled: bool, 171 #[getset(get_copy = "pub")] 172 reverse_position: bool, 173} 174 175impl ServerDeviceFeatureOutputPositionProperties { 176 pub fn new(position: &RangeWithLimit, disabled: bool, reverse_position: bool) -> Self { 177 Self { 178 position: position.clone(), 179 disabled, 180 reverse_position, 181 } 182 } 183 184 pub fn calculate_scaled_float(&self, value: f64) -> Result<i32, ButtplugDeviceConfigError> { 185 if !(0.0..=1.0).contains(&value) { 186 Err(ButtplugDeviceConfigError::InvalidFloatConversion(value)) 187 } else { 188 self 189 .calculate_scaled_value((self.position.step_count() as f64 * value).ceil() as u32) 190 .map(|x| x as i32) 191 } 192 } 193 194 // We'll get a number from 0-x here. We'll need to calculate it with in the range we have. 195 pub fn calculate_scaled_value(&self, value: u32) -> Result<u32, ButtplugDeviceConfigError> { 196 let range = if let Some(user_range) = self.position.user() { 197 user_range 198 } else { 199 self.position.internal_base() 200 }; 201 if range.contains(&(range.start() + value)) { 202 if self.reverse_position { 203 Ok(range.end() - value) 204 } else { 205 Ok(range.start() + value) 206 } 207 } else { 208 Err(ButtplugDeviceConfigError::InvalidOutputValue( 209 value as i32, 210 format!("{:?}", range), 211 )) 212 } 213 } 214} 215 216impl From<&ServerDeviceFeatureOutputPositionProperties> for DeviceFeatureOutputValueProperties { 217 fn from(val: &ServerDeviceFeatureOutputPositionProperties) -> Self { 218 DeviceFeatureOutputValueProperties::new(&val.position().step_limit()) 219 } 220} 221 222#[derive(Debug, Clone, Getters, CopyGetters)] 223pub struct ServerDeviceFeatureOutputPositionWithDurationProperties { 224 #[getset(get = "pub")] 225 position: RangeWithLimit, 226 #[getset(get = "pub")] 227 duration: RangeWithLimit, 228 #[getset(get_copy = "pub")] 229 disabled: bool, 230 #[getset(get_copy = "pub")] 231 reverse_position: bool, 232} 233 234impl ServerDeviceFeatureOutputPositionWithDurationProperties { 235 pub fn new( 236 position: &RangeWithLimit, 237 duration: &RangeWithLimit, 238 disabled: bool, 239 reverse_position: bool, 240 ) -> Self { 241 Self { 242 position: position.clone(), 243 duration: duration.clone(), 244 disabled, 245 reverse_position, 246 } 247 } 248 249 pub fn calculate_scaled_float(&self, value: f64) -> Result<u32, ButtplugDeviceConfigError> { 250 self.calculate_scaled_value((self.position.step_count() as f64 * value) as u32) 251 } 252 253 // We'll get a number from 0-x here. We'll need to calculate it with in the range we have. 254 pub fn calculate_scaled_value(&self, value: u32) -> Result<u32, ButtplugDeviceConfigError> { 255 let range = if let Some(user_range) = self.position.user() { 256 user_range 257 } else { 258 self.position.internal_base() 259 }; 260 if value > 0 && range.contains(&(range.start() + value)) { 261 if self.reverse_position { 262 Ok(range.end() - value) 263 } else { 264 Ok(range.start() + value) 265 } 266 } else if value == 0 { 267 Ok(0) 268 } else { 269 Err(ButtplugDeviceConfigError::InvalidOutputValue( 270 value as i32, 271 format!("{:?}", range), 272 )) 273 } 274 } 275} 276 277impl From<&ServerDeviceFeatureOutputPositionWithDurationProperties> 278 for DeviceFeatureOutputPositionWithDurationProperties 279{ 280 fn from(val: &ServerDeviceFeatureOutputPositionWithDurationProperties) -> Self { 281 DeviceFeatureOutputPositionWithDurationProperties::new( 282 &val.position().step_limit(), 283 &val.duration().step_limit(), 284 ) 285 } 286} 287 288#[derive(Clone, Debug, Getters, Setters, Default)] 289#[getset(get = "pub", set = "pub")] 290pub struct ServerDeviceFeatureOutput { 291 vibrate: Option<ServerDeviceFeatureOutputValueProperties>, 292 rotate: Option<ServerDeviceFeatureOutputValueProperties>, 293 oscillate: Option<ServerDeviceFeatureOutputValueProperties>, 294 constrict: Option<ServerDeviceFeatureOutputValueProperties>, 295 temperature: Option<ServerDeviceFeatureOutputValueProperties>, 296 led: Option<ServerDeviceFeatureOutputValueProperties>, 297 position: Option<ServerDeviceFeatureOutputPositionProperties>, 298 position_with_duration: Option<ServerDeviceFeatureOutputPositionWithDurationProperties>, 299 spray: Option<ServerDeviceFeatureOutputValueProperties>, 300} 301 302impl ServerDeviceFeatureOutput { 303 pub fn contains(&self, output_type: OutputType) -> bool { 304 match output_type { 305 OutputType::Constrict => self.constrict.is_some(), 306 OutputType::Temperature => self.temperature.is_some(), 307 OutputType::Led => self.led.is_some(), 308 OutputType::Oscillate => self.oscillate.is_some(), 309 OutputType::Position => self.position.is_some(), 310 OutputType::PositionWithDuration => self.position_with_duration.is_some(), 311 OutputType::Rotate => self.rotate.is_some(), 312 OutputType::Spray => self.spray.is_some(), 313 OutputType::Unknown => false, 314 OutputType::Vibrate => self.vibrate.is_some(), 315 } 316 } 317 318 pub fn output_types(&self) -> Vec<OutputType> { 319 let mut types = vec![]; 320 self 321 .constrict 322 .is_some() 323 .then(|| types.push(OutputType::Constrict)); 324 self 325 .temperature 326 .is_some() 327 .then(|| types.push(OutputType::Temperature)); 328 self.led.is_some().then(|| types.push(OutputType::Led)); 329 self 330 .oscillate 331 .is_some() 332 .then(|| types.push(OutputType::Oscillate)); 333 self 334 .position 335 .is_some() 336 .then(|| types.push(OutputType::Position)); 337 self 338 .position_with_duration 339 .is_some() 340 .then(|| types.push(OutputType::PositionWithDuration)); 341 self 342 .rotate 343 .is_some() 344 .then(|| types.push(OutputType::Rotate)); 345 self.spray.is_some().then(|| types.push(OutputType::Spray)); 346 self 347 .vibrate 348 .is_some() 349 .then(|| types.push(OutputType::Vibrate)); 350 types 351 } 352 353 pub fn calculate_from_value( 354 &self, 355 output_type: OutputType, 356 value: i32, 357 ) -> Result<i32, ButtplugDeviceConfigError> { 358 // TODO just fucking do some trait implementations for calculation methods and clean this up for fuck sake. :c 359 match output_type { 360 OutputType::Constrict => self.constrict.as_ref().map_or( 361 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 362 |x| x.calculate_scaled_value(value), 363 ), 364 OutputType::Temperature => self.temperature.as_ref().map_or( 365 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 366 |x| x.calculate_scaled_value(value), 367 ), 368 OutputType::Led => self.led.as_ref().map_or( 369 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 370 |x| x.calculate_scaled_value(value), 371 ), 372 OutputType::Oscillate => self.oscillate.as_ref().map_or( 373 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 374 |x| x.calculate_scaled_value(value), 375 ), 376 OutputType::Position => self.position.as_ref().map_or( 377 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 378 |x| x.calculate_scaled_value(value as u32).map(|x| x as i32), 379 ), 380 OutputType::PositionWithDuration => self.position_with_duration.as_ref().map_or( 381 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 382 |x| x.calculate_scaled_value(value as u32).map(|x| x as i32), 383 ), 384 OutputType::Rotate => self.rotate.as_ref().map_or( 385 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 386 |x| x.calculate_scaled_value(value), 387 ), 388 OutputType::Spray => self.spray.as_ref().map_or( 389 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 390 |x| x.calculate_scaled_value(value), 391 ), 392 OutputType::Unknown => Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 393 OutputType::Vibrate => self.vibrate.as_ref().map_or( 394 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 395 |x| x.calculate_scaled_value(value), 396 ), 397 } 398 } 399 400 pub fn calculate_from_float( 401 &self, 402 output_type: OutputType, 403 value: f64, 404 ) -> Result<i32, ButtplugDeviceConfigError> { 405 match output_type { 406 OutputType::Constrict => self.constrict.as_ref().map_or( 407 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 408 |x| x.calculate_scaled_float(value), 409 ), 410 OutputType::Temperature => self.temperature.as_ref().map_or( 411 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 412 |x| x.calculate_scaled_float(value), 413 ), 414 OutputType::Led => self.led.as_ref().map_or( 415 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 416 |x| x.calculate_scaled_float(value), 417 ), 418 OutputType::Oscillate => self.oscillate.as_ref().map_or( 419 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 420 |x| x.calculate_scaled_float(value), 421 ), 422 OutputType::Position => self.position.as_ref().map_or( 423 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 424 |x| x.calculate_scaled_float(value), 425 ), 426 OutputType::PositionWithDuration => self.position_with_duration.as_ref().map_or( 427 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 428 |x| x.calculate_scaled_float(value).map(|x| x as i32), 429 ), 430 OutputType::Rotate => self.rotate.as_ref().map_or( 431 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 432 |x| x.calculate_scaled_float(value), 433 ), 434 OutputType::Spray => self.spray.as_ref().map_or( 435 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 436 |x| x.calculate_scaled_float(value), 437 ), 438 OutputType::Unknown => Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 439 OutputType::Vibrate => self.vibrate.as_ref().map_or( 440 Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 441 |x| x.calculate_scaled_float(value), 442 ), 443 } 444 } 445} 446 447impl From<ServerDeviceFeatureOutput> for DeviceFeatureOutput { 448 fn from(val: ServerDeviceFeatureOutput) -> Self { 449 let mut builder = DeviceFeatureOutputBuilder::default(); 450 val.vibrate.as_ref().map(|x| builder.vibrate(x.into())); 451 val.rotate.as_ref().map(|x| builder.rotate(x.into())); 452 val.oscillate.as_ref().map(|x| builder.oscillate(x.into())); 453 val.constrict.as_ref().map(|x| builder.constrict(x.into())); 454 val.temperature.as_ref().map(|x| builder.temperature(x.into())); 455 val.led.as_ref().map(|x| builder.led(x.into())); 456 val.position.as_ref().map(|x| builder.position(x.into())); 457 val 458 .position_with_duration 459 .as_ref() 460 .map(|x| builder.position_with_duration(x.into())); 461 val.spray.as_ref().map(|x| builder.spray(x.into())); 462 builder.build().expect("Infallible") 463 } 464} 465 466#[derive(Clone, Debug, Getters)] 467#[getset(get = "pub")] 468pub struct ServerDeviceFeatureInputProperties { 469 value_range: Vec<RangeInclusive<i32>>, 470 input_commands: HashSet<InputCommandType>, 471} 472 473impl ServerDeviceFeatureInputProperties { 474 pub fn new( 475 value_range: &Vec<RangeInclusive<i32>>, 476 sensor_commands: &HashSet<InputCommandType>, 477 ) -> Self { 478 Self { 479 value_range: value_range.clone(), 480 input_commands: sensor_commands.clone(), 481 } 482 } 483} 484 485impl From<&ServerDeviceFeatureInputProperties> for DeviceFeatureInputProperties { 486 fn from(val: &ServerDeviceFeatureInputProperties) -> Self { 487 DeviceFeatureInputProperties::new(&val.value_range, &val.input_commands) 488 } 489} 490 491#[derive(Clone, Debug, Getters, Setters, Default)] 492#[getset(get = "pub", set = "pub(crate)")] 493pub struct ServerDeviceFeatureInput { 494 battery: Option<ServerDeviceFeatureInputProperties>, 495 rssi: Option<ServerDeviceFeatureInputProperties>, 496 pressure: Option<ServerDeviceFeatureInputProperties>, 497 button: Option<ServerDeviceFeatureInputProperties>, 498} 499 500impl ServerDeviceFeatureInput { 501 pub fn contains(&self, input_type: InputType) -> bool { 502 match input_type { 503 InputType::Battery => self.battery.is_some(), 504 InputType::Rssi => self.rssi.is_some(), 505 InputType::Pressure => self.pressure.is_some(), 506 InputType::Button => self.button.is_some(), 507 InputType::Unknown => false, 508 } 509 } 510} 511 512impl From<ServerDeviceFeatureInput> for DeviceFeatureInput { 513 fn from(val: ServerDeviceFeatureInput) -> Self { 514 let mut builder = DeviceFeatureInputBuilder::default(); 515 val.battery.as_ref().map(|x| builder.battery(x.into())); 516 val.rssi.as_ref().map(|x| builder.rssi(x.into())); 517 val.pressure.as_ref().map(|x| builder.pressure(x.into())); 518 val.button.as_ref().map(|x| builder.button(x.into())); 519 builder.build().expect("Infallible") 520 } 521} 522 523#[derive(Clone, Debug, Getters, CopyGetters, Setters)] 524pub struct ServerDeviceFeature { 525 #[getset(get = "pub")] 526 description: String, 527 #[getset(get_copy = "pub")] 528 id: Uuid, 529 #[getset(get_copy = "pub")] 530 base_id: Option<Uuid>, 531 #[getset(get_copy = "pub")] 532 alt_protocol_index: Option<u32>, 533 #[getset(get = "pub", set = "pub")] 534 output: Option<ServerDeviceFeatureOutput>, 535 #[getset(get = "pub")] 536 input: Option<ServerDeviceFeatureInput>, 537} 538 539impl PartialEq for ServerDeviceFeature { 540 fn eq(&self, other: &Self) -> bool { 541 self.id() == other.id() 542 } 543} 544 545impl Eq for ServerDeviceFeature { 546} 547 548impl ServerDeviceFeature { 549 pub fn new( 550 description: &str, 551 id: Uuid, 552 base_id: Option<Uuid>, 553 alt_protocol_index: Option<u32>, 554 output: &Option<ServerDeviceFeatureOutput>, 555 input: &Option<ServerDeviceFeatureInput>, 556 ) -> Self { 557 Self { 558 description: description.to_owned(), 559 id, 560 base_id, 561 alt_protocol_index, 562 output: output.clone(), 563 input: input.clone(), 564 } 565 } 566 567 pub fn as_new_user_feature(&self) -> Self { 568 let mut new_feature = self.clone(); 569 new_feature.base_id = Some(self.id); 570 new_feature.id = Uuid::new_v4(); 571 new_feature 572 } 573 574 pub fn as_device_feature(&self, index: u32) -> Result<DeviceFeature, ButtplugDeviceConfigError> { 575 Ok(DeviceFeature::new( 576 index, 577 self.description(), 578 &self.output.as_ref().map(|x| x.clone().into()), 579 &self.input.as_ref().map(|x| x.clone().into()), 580 )) 581 } 582}