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 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}