From 4c7cdac2fa9f90127f5376e0dbdcb95159b04028 Mon Sep 17 00:00:00 2001 From: Vyr Cossont Date: Thu, 8 Dec 2022 17:05:42 -0800 Subject: [PATCH 1/4] WIP: works but can't quit example 4 on Ctrl-C --- Cargo.toml | 4 + buttplug/Cargo.toml | 4 +- .../buttplug-device-config-schema.json | 8 + .../buttplug-device-config.json | 38 ++ .../buttplug-device-config.yml | 20 +- .../server/device/configuration/specifier.rs | 32 +- .../device/hardware/communication/mod.rs | 62 ++- .../device/hardware/communication/sdl2/mod.rs | 14 + .../sdl2/sdl2_device_comm_manager.rs | 388 ++++++++++++++++++ .../communication/sdl2/sdl2_hardware.rs | 197 +++++++++ buttplug/src/server/device/protocol/mod.rs | 2 + buttplug/src/server/device/protocol/sdl2.rs | 107 +++++ buttplug/src/util/device_configuration.rs | 6 + buttplug/src/util/mod.rs | 35 +- 14 files changed, 902 insertions(+), 15 deletions(-) create mode 100644 buttplug/src/server/device/hardware/communication/sdl2/mod.rs create mode 100644 buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs create mode 100644 buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs create mode 100644 buttplug/src/server/device/protocol/sdl2.rs diff --git a/Cargo.toml b/Cargo.toml index dd375c6c6..2e56450ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,7 @@ members = [ [profile.release] lto = true codegen-units = 1 + +# Need development sdl2 version with additional rumble-related methods +[patch.crates-io] +sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2", branch = "master" } diff --git a/buttplug/Cargo.toml b/buttplug/Cargo.toml index 61fcd10da..f1cf16d6b 100644 --- a/buttplug/Cargo.toml +++ b/buttplug/Cargo.toml @@ -26,13 +26,14 @@ features = ["default", "unstable"] [features] # Basic features -default=["tokio-runtime", "client", "server", "serialize-json", "websockets", "btleplug-manager", "xinput-manager", "serial-manager", "lovense-dongle-manager", "lovense-connect-service-manager", "websocket-server-manager"] +default=["tokio-runtime", "client", "server", "serialize-json", "websockets", "btleplug-manager", "sdl2-manager", "serial-manager", "lovense-dongle-manager", "lovense-connect-service-manager", "websocket-server-manager"] client=[] server=[] serialize-json=[] # Connectors websockets=["serialize-json", "async-tungstenite", "native-tls"] # Device Communication Managers +sdl2-manager=["server", "sdl2"] xinput-manager=["server"] btleplug-manager=["server", "btleplug"] serial-manager=["server", "serialport"] @@ -88,6 +89,7 @@ os_info = "3.5.1" jsonschema = { version = "0.16.1", default-features = false, features = ["resolve-file"] } derivative = "2.2.0" tokio-stream = "0.1.11" +sdl2 = { version = "0.35.2", optional = true, features = ["bundled", "static-link", "hidapi"] } [dev-dependencies] serde_yaml = "0.9.14" diff --git a/buttplug/buttplug-device-config/buttplug-device-config-schema.json b/buttplug/buttplug-device-config/buttplug-device-config-schema.json index 06fbb5d5b..1a1e2c858 100644 --- a/buttplug/buttplug-device-config/buttplug-device-config-schema.json +++ b/buttplug/buttplug-device-config/buttplug-device-config-schema.json @@ -131,6 +131,14 @@ } } }, + "sdl2-definition": { + "type": "object", + "properties": { + "exists": { + "type": "boolean" + } + } + }, "lovense-connect-service-definition": { "type": "object", "properties": { diff --git a/buttplug/buttplug-device-config/buttplug-device-config.json b/buttplug/buttplug-device-config/buttplug-device-config.json index 2329db2fc..ecfa5bc26 100644 --- a/buttplug/buttplug-device-config/buttplug-device-config.json +++ b/buttplug/buttplug-device-config/buttplug-device-config.json @@ -636,6 +636,44 @@ } } }, + "sdl2": { + "sdl2": { + "exists": true + }, + "defaults": { + "name": "SDL2 Compatible Gamepad", + "messages": { + "ScalarCmd": [ + { + "StepRange": [ + 0, + 65535 + ], + "ActuatorType": "Vibrate" + }, + { + "StepRange": [ + 0, + 65535 + ], + "ActuatorType": "Vibrate" + } + ], + "SensorReadCmd": [ + { + "FeatureDescriptor": "Battery Level", + "SensorType": "Battery", + "SensorRange": [ + [ + 0, + 100 + ] + ] + } + ] + } + } + }, "kiiroo-v2": { "btle": { "names": [ diff --git a/buttplug/buttplug-device-config/buttplug-device-config.yml b/buttplug/buttplug-device-config/buttplug-device-config.yml index 0bcbaf051..18d36f967 100644 --- a/buttplug/buttplug-device-config/buttplug-device-config.yml +++ b/buttplug/buttplug-device-config/buttplug-device-config.yml @@ -404,9 +404,6 @@ protocols: # is its own connector type, so we don't have any special # connection info here. # - # TODO Maybe just start calling this "gamepad"? Maybe add "VR - # Controller" too? - # # The specifier needs to be an object and have some content, but it # doesn't matter what. xinput: @@ -419,6 +416,23 @@ protocols: ActuatorType: Vibrate - StepRange: [0, 65535] ActuatorType: Vibrate + sdl2: + # Similarly to XInput, this represents any gamepad supported by SDL2 + # (including HID and XInput gamepads) and is its own connector type. + sdl2: + exists: true + defaults: + name: SDL2 Compatible Gamepad + messages: + ScalarCmd: + - StepRange: [0, 65535] + ActuatorType: Vibrate + - StepRange: [0, 65535] + ActuatorType: Vibrate + SensorReadCmd: + - FeatureDescriptor: Battery Level + SensorType: Battery + SensorRange: [[0, 100]] kiiroo-v2: btle: names: diff --git a/buttplug/src/server/device/configuration/specifier.rs b/buttplug/src/server/device/configuration/specifier.rs index 625dc9f96..d42355aa3 100644 --- a/buttplug/src/server/device/configuration/specifier.rs +++ b/buttplug/src/server/device/configuration/specifier.rs @@ -234,9 +234,9 @@ impl PartialEq for LovenseConnectServiceSpecifier { /// Specifier for [XInput](crate::server::device::communication_manager::xinput) devices /// -/// Network based services, has no attributes because the -/// [XInput](crate::server::device::communication_manager::xinput) device communication manager handles all device -/// discovery and identification itself. +/// Has no attributes because the +/// [XInput](crate::server::device::communication_manager::xinput) +/// device communication manager handles all device discovery and identification itself. #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct XInputSpecifier { // Needed for deserialziation but unused. @@ -256,6 +256,30 @@ impl PartialEq for XInputSpecifier { } } +/// Specifier for [SDL2](crate::server::device::communication_manager::sdl2) devices +/// +/// Has no attributes because the +/// [SDL2](crate::server::device::communication_manager::sdl2) +/// device communication manager handles all device discovery and identification itself. +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct SDL2Specifier { + // Needed for deserialziation but unused. + #[allow(dead_code)] + exists: bool, +} + +impl Default for SDL2Specifier { + fn default() -> Self { + Self { exists: true } + } +} + +impl PartialEq for SDL2Specifier { + fn eq(&self, _other: &Self) -> bool { + true + } +} + /// Specifier for HID (USB, Bluetooth) devices /// /// Handles devices managed by the operating system's HID manager. @@ -360,6 +384,7 @@ pub enum ProtocolCommunicationSpecifier { USB(USBSpecifier), Serial(SerialSpecifier), XInput(XInputSpecifier), + SDL2(SDL2Specifier), LovenseConnectService(LovenseConnectServiceSpecifier), Websocket(WebsocketSpecifier), } @@ -373,6 +398,7 @@ impl PartialEq for ProtocolCommunicationSpecifier { (BluetoothLE(self_spec), BluetoothLE(other_spec)) => self_spec == other_spec, (HID(self_spec), HID(other_spec)) => self_spec == other_spec, (XInput(self_spec), XInput(other_spec)) => self_spec == other_spec, + (SDL2(self_spec), SDL2(other_spec)) => self_spec == other_spec, (Websocket(self_spec), Websocket(other_spec)) => self_spec == other_spec, (LovenseConnectService(self_spec), LovenseConnectService(other_spec)) => { self_spec == other_spec diff --git a/buttplug/src/server/device/hardware/communication/mod.rs b/buttplug/src/server/device/hardware/communication/mod.rs index a9c99d74f..1914bc189 100644 --- a/buttplug/src/server/device/hardware/communication/mod.rs +++ b/buttplug/src/server/device/hardware/communication/mod.rs @@ -12,19 +12,47 @@ pub mod lovense_connect_service; pub mod websocket_server; // BTLEPlug works on anything not WASM -#[cfg(all(feature = "btleplug-manager", any(target_os = "windows", target_os = "macos", target_os = "linux", target_os="ios", target_os="android")))] +#[cfg(all( + feature = "btleplug-manager", + any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "ios", + target_os = "android" + ) +))] pub mod btleplug; // Lovense Dongles and Serial Ports work on all desktop platforms -#[cfg(all(feature = "lovense-dongle-manager", any(target_os = "windows", target_os = "macos", target_os = "linux")))] +#[cfg(all( + feature = "lovense-dongle-manager", + any(target_os = "windows", target_os = "macos", target_os = "linux") +))] pub mod lovense_dongle; -#[cfg(all(feature = "serial-manager", any(target_os = "windows", target_os = "macos", target_os = "linux")))] +#[cfg(all( + feature = "serial-manager", + any(target_os = "windows", target_os = "macos", target_os = "linux") +))] pub mod serialport; // XInput is windows only #[cfg(all(feature = "xinput-manager", target_os = "windows"))] pub mod xinput; +// SDL2 works on anything not WASM +#[cfg(all( + feature = "sdl2-manager", + any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "ios", + target_os = "android" + ) +))] +pub mod sdl2; + use crate::{ core::{errors::ButtplugDeviceError, ButtplugResultFuture}, server::device::hardware::HardwareConnector, @@ -74,11 +102,35 @@ pub enum HardwareSpecificError { #[cfg(all(feature = "xinput-manager", target_os = "windows"))] #[error("XInput usage error: {0}")] XInputError(String), + #[cfg(all( + feature = "sdl2-manager", + any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "ios", + target_os = "android" + ) + ))] + #[error("SDL2 error: {0}")] + SDL2Error(String), // Btleplug library uses Failure, not Error, on its error enum. :( - #[cfg(all(feature = "btleplug-manager", any(target_os = "windows", target_os = "macos", target_os = "linux", target_os="ios", target_os="android")))] + #[cfg(all( + feature = "btleplug-manager", + any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "ios", + target_os = "android" + ) + ))] #[error("Btleplug error: {0}")] BtleplugError(String), - #[cfg(all(feature = "serial-manager", any(target_os = "windows", target_os = "macos", target_os = "linux")))] + #[cfg(all( + feature = "serial-manager", + any(target_os = "windows", target_os = "macos", target_os = "linux") + ))] #[error("Serial error: {0}")] SerialError(String), } diff --git a/buttplug/src/server/device/hardware/communication/sdl2/mod.rs b/buttplug/src/server/device/hardware/communication/sdl2/mod.rs new file mode 100644 index 000000000..af0819db0 --- /dev/null +++ b/buttplug/src/server/device/hardware/communication/sdl2/mod.rs @@ -0,0 +1,14 @@ +// Buttplug Rust Source Code File - See https://buttplug.io for more info. +// +// Copyright 2016-2022 Nonpolynomial Labs LLC. All rights reserved. +// +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root +// for full license information. + +mod sdl2_device_comm_manager; +mod sdl2_hardware; + +pub use sdl2_device_comm_manager::{ + SDL2DeviceCommunicationManager, + SDL2DeviceCommunicationManagerBuilder, +}; diff --git a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs new file mode 100644 index 000000000..9d629ff3c --- /dev/null +++ b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs @@ -0,0 +1,388 @@ +// Buttplug Rust Source Code File - See https://buttplug.io for more info. +// +// Copyright 2016-2022 Nonpolynomial Labs LLC. All rights reserved. +// +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root +// for full license information. + +use crate::{ + core::ButtplugResultFuture, + server::device::hardware::{ + communication::{ + sdl2::sdl2_hardware::SDL2HardwareConnector, + HardwareCommunicationManager, + HardwareCommunicationManagerBuilder, + HardwareCommunicationManagerEvent, + }, + HardwareEvent, + }, +}; +use async_trait::async_trait; +use futures_util::FutureExt; +use sdl2::{ + self, + event::Event, + joystick::{Joystick, PowerLevel}, + EventPump, + IntegerOrSdlError, + JoystickSubsystem, +}; +use std::{ + collections::HashMap, + fmt::{Debug, Formatter}, + future, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, +}; +use tokio::{ + sync::{broadcast, mpsc, oneshot}, + task::{self, LocalSet}, +}; + +#[derive(Default, Clone)] +pub struct SDL2DeviceCommunicationManagerBuilder {} + +impl HardwareCommunicationManagerBuilder for SDL2DeviceCommunicationManagerBuilder { + fn finish( + &mut self, + sender: mpsc::Sender, + ) -> Box { + Box::new(SDL2DeviceCommunicationManager::new(sender)) + } +} + +pub struct SDL2DeviceCommunicationManager { + scanning_status: Arc, +} + +impl SDL2DeviceCommunicationManager { + fn new(sender: mpsc::Sender) -> Self { + let scanning_status = Arc::new(AtomicBool::new(false)); + + { + let scanning_status = scanning_status.clone(); + thread::Builder::new() + .name("sdl-event-loop-thread".to_owned()) + .spawn(move || { + if let Err(e) = sdl2_event_loop_thread(sender, scanning_status) { + error!("SDL2 comm manager: {e}"); + } + }) + .expect("Couldn't spawn SDL event loop thread!") + }; + + Self { scanning_status } + } +} + +// We're always watching for SDL controller connection/disconnection events. +// The scan status controls whether we report them as comm manager events. +#[async_trait] +impl HardwareCommunicationManager for SDL2DeviceCommunicationManager { + fn name(&self) -> &'static str { + "SDL2DeviceCommunicationManager" + } + + fn start_scanning(&mut self) -> ButtplugResultFuture { + trace!("SDL2 manager starting scan"); + self.scanning_status.store(true, Ordering::SeqCst); + future::ready(Ok(())).boxed() + } + + fn stop_scanning(&mut self) -> ButtplugResultFuture { + trace!("SDL2 manager stopping scan"); + self.scanning_status.store(true, Ordering::SeqCst); + future::ready(Ok(())).boxed() + } + + fn scanning_status(&self) -> bool { + self.scanning_status.load(Ordering::SeqCst) + } + + fn can_scan(&self) -> bool { + true + } +} + +trait SdlResultExt { + fn map_sdl_error(self) -> Result; +} + +impl SdlResultExt for Result { + fn map_sdl_error(self) -> Result { + self.map_err(|e| format!("{e}")) + } +} + +/// Lives on the SDL2 event loop thread and responds to messages. +struct SDL2JoystickActor { + joystick: Joystick, + message_receiver: mpsc::Receiver, +} + +struct JoystickDebug<'a>(&'a Joystick); + +impl Debug for JoystickDebug<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Joystick") + .field("instance_id", &self.0.instance_id()) + .field("name", &self.0.name()) + .finish_non_exhaustive() + } +} + +impl Debug for SDL2JoystickActor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SDL2JoystickActor") + .field("joystick", &JoystickDebug(&self.joystick)) + .field("message_receiver", &self.message_receiver) + .finish() + } +} + +impl SDL2JoystickActor { + fn handle_message(&mut self, message: SDL2JoystickMessage) { + match message { + SDL2JoystickMessage::Rumble { + low_frequency_rumble, + high_frequency_rumble, + duration_ms, + oneshot_sender, + } => { + // If the receiver's gone, we don't care if the send fails. + let _ = oneshot_sender.send( + self + .joystick + .set_rumble(low_frequency_rumble, high_frequency_rumble, duration_ms) + .map_sdl_error(), + ); + } + SDL2JoystickMessage::PowerLevel { oneshot_sender } => { + let _ = oneshot_sender.send(self.joystick.power_level().map_sdl_error()); + } + } + } + + async fn run(&mut self) { + while let Some(msg) = self.message_receiver.recv().await { + self.handle_message(msg); + } + } +} + +/// Lives inside `SDL2Hardware` on any thread. +/// Sends and receives messages to its actor. +/// Sends disconnect events to the `SDL2Hardware`. +#[derive(Clone, Debug)] +pub struct SDL2JoystickActorHandle { + message_sender: mpsc::Sender, +} + +impl SDL2JoystickActorHandle { + async fn send_message_and_wait( + &self, + message: SDL2JoystickMessage, + oneshot_receiver: oneshot::Receiver, + ) -> Result { + self + .message_sender + .send(message) + .await + .map_err(|e| format!("SDL2 joystick actor proxy couldn't send message: {e}"))?; + // TODO(Vyr): add a timeout here + oneshot_receiver + .await + .map_err(|e| format!("SDL2 joystick actor proxy couldn't receive result: {e}")) + } + + pub async fn rumble( + &self, + low_frequency_rumble: u16, + high_frequency_rumble: u16, + duration_ms: u32, + ) -> Result<(), String> { + let (oneshot_sender, oneshot_receiver) = oneshot::channel(); + self + .send_message_and_wait( + SDL2JoystickMessage::Rumble { + low_frequency_rumble, + high_frequency_rumble, + duration_ms, + oneshot_sender, + }, + oneshot_receiver, + ) + .await? + } + + pub async fn power_level(&self) -> Result { + let (oneshot_sender, oneshot_receiver) = oneshot::channel(); + self + .send_message_and_wait( + SDL2JoystickMessage::PowerLevel { oneshot_sender }, + oneshot_receiver, + ) + .await? + } +} + +#[derive(Debug)] +enum SDL2JoystickMessage { + Rumble { + low_frequency_rumble: u16, + high_frequency_rumble: u16, + duration_ms: u32, + oneshot_sender: oneshot::Sender>, + }, + PowerLevel { + oneshot_sender: oneshot::Sender>, + }, +} + +/// Only one thread is allowed to talk to the SDL event loop, +/// and it has to be the one that initialized SDL. +/// The joystick subsystem and joystick handles cannot be moved across threads either. +/// This thread is thus responsible for pumping events, +/// forwarding all controller added/removed events to the comm manager, +/// and handling battery read and vibration write tasks. +fn sdl2_event_loop_thread( + comm_sender: mpsc::Sender, + scanning_status: Arc, +) -> Result<(), String> { + trace!("SDL2 event loop thread started"); + + // Enable DS4 rumble. + // SDL hint comments say that this turn on extended reports and thus mess up use of the DS4 for + // apps that don't use SDL, until the DS4 is power-cycled. + // TODO(Vyr): make this a config variable so that games that use the gamepad for input still work? + // How do we do that kind of prefs in Buttplug? + sdl2::hint::set("SDL_JOYSTICK_HIDAPI_PS4_RUMBLE", "1"); + + let sdl_context = sdl2::init()?; + let joystick_subsystem = sdl_context.joystick()?; + let mut event_pump = sdl_context.event_pump()?; + + // Map of joystick ID to hardware event sender for that joystick. + let mut event_senders = HashMap::>::new(); + + let rt = tokio::runtime::Builder::new_current_thread() + .thread_name("sdl-event-loop-thread-rt") + .enable_all() + .build() + .map_err(|e| { + format!("SDL2 event loop thread couldn't create Tokio current thread runtime: {e}") + })?; + + let local_set = LocalSet::new(); + + rt.block_on(async { + while !comm_sender.is_closed() { + local_set + .run_until(sdl2_poll_event( + &comm_sender, + &mut event_pump, + &joystick_subsystem, + &mut event_senders, + scanning_status.clone(), + )) + .await; + } + }); + + trace!("SDL2 event loop thread finished"); + Ok(()) +} + +/// Handle at most one possibly relevant SDL event. +/// Drives the event pump. +async fn sdl2_poll_event( + comm_sender: &mpsc::Sender, + event_pump: &mut EventPump, + joystick_subsystem: &JoystickSubsystem, + event_senders: &mut HashMap>, + scanning_status: Arc, +) { + // Yield at least once so we have time to drive futures in the local set. + task::yield_now().await; + + if let Some(event) = event_pump.poll_event() { + match event { + Event::JoyDeviceAdded { which: index, .. } => { + trace!("SDL2 comm manager found a new joystick at index {index}"); + if !scanning_status.load(Ordering::SeqCst) { + trace!("SDL2 comm manager is not scanning, skipping new joystick at index {index}"); + return; + } + let joystick = match joystick_subsystem.open(index) { + Ok(joystick) => joystick, + Err(e) => { + trace!("Couldn't open new joystick at index {index}: {e}"); + return; + } + }; + if !joystick.has_rumble() { + trace!("New joystick at index {index} does not support rumble, skipping it"); + return; + } + let name = joystick.name(); + let id = joystick.instance_id(); + trace!("Opened new joystick at index {index} with ID {id}: {name}"); + + let address = format!("{id}"); + let (message_sender, message_receiver) = mpsc::channel(256); + let (event_sender, _) = broadcast::channel(256); + + event_senders.insert(joystick.instance_id(), event_sender.clone()); + + task::spawn_local( + async move { + SDL2JoystickActor { + joystick, + message_receiver, + } + .run() + .await + } + .boxed_local(), + ); + + let joystick_actor_handle = SDL2JoystickActorHandle { message_sender }; + if let Err(e) = comm_sender + .send(HardwareCommunicationManagerEvent::DeviceFound { + name: name.clone(), + address: address.clone(), + creator: Box::new(SDL2HardwareConnector::new( + name, + address, + joystick_actor_handle, + event_sender, + )), + }) + .await + { + error!("SDL2 event loop thread couldn't send connection event: {e}"); + } + } + + Event::JoyDeviceRemoved { which: id, .. } => { + debug!("SDL2 comm manager lost a joystick with ID {id}"); + if let Some(event_sender) = event_senders.remove(&id) { + let address = format!("{id}"); + if let Err(e) = event_sender.send(HardwareEvent::Disconnected(address)) { + error!("SDL2 event loop thread couldn't send disconnection event: {e}"); + } + } + } + + Event::Quit { .. } => { + // TODO(Vyr): this should exit the thread + println!("SDL says byyyeeeeeeee!"); + } + + _ => {} + } + } +} diff --git a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs new file mode 100644 index 000000000..b7344a252 --- /dev/null +++ b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs @@ -0,0 +1,197 @@ +use crate::server::device::hardware::communication::HardwareSpecificError; +use crate::{ + core::{errors::ButtplugDeviceError, message::Endpoint}, + server::device::{ + configuration::{ProtocolCommunicationSpecifier, SDL2Specifier}, + hardware::{ + communication::sdl2::sdl2_device_comm_manager::SDL2JoystickActorHandle, + GenericHardwareSpecializer, + Hardware, + HardwareConnector, + HardwareEvent, + HardwareInternal, + HardwareReadCmd, + HardwareReading, + HardwareSpecializer, + HardwareSubscribeCmd, + HardwareUnsubscribeCmd, + HardwareWriteCmd, + }, + }, +}; +use async_trait::async_trait; +use byteorder::{LittleEndian, ReadBytesExt}; +use futures::future::{self, BoxFuture, FutureExt}; +use sdl2::joystick::PowerLevel; +use std::io::Cursor; +use tokio::sync::broadcast; + +#[derive(Debug)] +struct SDL2HardwareConnectArgs { + name: String, + address: String, + joystick: SDL2JoystickActorHandle, + event_sender: broadcast::Sender, +} + +#[derive(Debug)] +pub struct SDL2HardwareConnector { + args: Option, +} + +impl SDL2HardwareConnector { + pub fn new( + name: String, + address: String, + joystick: SDL2JoystickActorHandle, + event_sender: broadcast::Sender, + ) -> Self { + Self { + args: Some(SDL2HardwareConnectArgs { + name, + address, + joystick, + event_sender, + }), + } + } +} + +#[async_trait] +impl HardwareConnector for SDL2HardwareConnector { + fn specifier(&self) -> ProtocolCommunicationSpecifier { + ProtocolCommunicationSpecifier::SDL2(SDL2Specifier::default()) + } + + async fn connect(&mut self) -> Result, ButtplugDeviceError> { + if let Some(args) = self.args.take() { + debug!( + "SDL2 connector emitting a new SDL2 device impl: {name}, {address}", + name = args.name, + address = args.address + ); + let hardware_internal = SDL2Hardware::new(args.joystick, args.event_sender); + let hardware = Hardware::new( + &args.name, + &args.address, + &[Endpoint::TxVibrate, Endpoint::RxBLEBattery], + Box::new(hardware_internal), + ); + Ok(Box::new(GenericHardwareSpecializer::new(hardware))) + } else { + Err(ButtplugDeviceError::DeviceSpecificError( + HardwareSpecificError::SDL2Error( + "SDL2 hardware connectors shouldn't be reused!".to_owned(), + ), + )) + } + } +} + +pub struct SDL2Hardware { + joystick: SDL2JoystickActorHandle, + event_sender: broadcast::Sender, +} + +impl SDL2Hardware { + fn new( + joystick: SDL2JoystickActorHandle, + event_sender: broadcast::Sender, + ) -> Self { + Self { + joystick, + event_sender, + } + } +} + +impl HardwareInternal for SDL2Hardware { + /// We shouldn't have to do anything, assuming the `SDL2Hardware` gets dropped sometime after this, + /// which should close the `Joystick` and its underlying SDL2 object. + fn disconnect(&self) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> { + future::ready(Ok(())).boxed() + } + + fn event_stream(&self) -> broadcast::Receiver { + self.event_sender.subscribe() + } + + fn read_value( + &self, + msg: &HardwareReadCmd, + ) -> BoxFuture<'static, Result> { + if msg.endpoint != Endpoint::RxBLEBattery { + return future::ready(Err(ButtplugDeviceError::InvalidEndpoint(msg.endpoint))).boxed(); + } + let joystick = self.joystick.clone(); + async move { + match joystick.power_level().await { + Ok(r) => match r { + PowerLevel::Unknown => Err(ButtplugDeviceError::DeviceSpecificError( + HardwareSpecificError::SDL2Error( + "SDL2 couldn't read joystick battery level".to_owned(), + ), + )), + PowerLevel::Empty => Ok(0), + PowerLevel::Low => Ok(33), + PowerLevel::Medium => Ok(66), + PowerLevel::Full => Ok(100), + PowerLevel::Wired => Ok(100), + }, + Err(e) => Err(ButtplugDeviceError::DeviceSpecificError( + HardwareSpecificError::SDL2Error(e), + )), + } + .map(|r| HardwareReading::new(Endpoint::Rx, &vec![r])) + } + .boxed() + } + + fn write_value( + &self, + msg: &HardwareWriteCmd, + ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> { + if msg.endpoint != Endpoint::TxVibrate { + return future::ready(Err(ButtplugDeviceError::InvalidEndpoint(msg.endpoint))).boxed(); + } + let mut cursor = Cursor::new(msg.data.clone()); + let low_frequency_rumble = cursor + .read_u16::() + .expect("Packed in protocol, infallible"); + let high_frequency_rumble = cursor + .read_u16::() + .expect("Packed in protocol, infallible"); + let joystick = self.joystick.clone(); + async move { + joystick + .rumble( + low_frequency_rumble, + high_frequency_rumble, + 0, // indefinitely + ) + .await + .map_err(|e| ButtplugDeviceError::DeviceSpecificError(HardwareSpecificError::SDL2Error(e))) + } + .boxed() + } + + fn subscribe( + &self, + _msg: &HardwareSubscribeCmd, + ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> { + future::ready(Err(ButtplugDeviceError::UnhandledCommand( + "SDL2 hardware does not support subscribe".to_owned(), + ))) + .boxed() + } + + fn unsubscribe( + &self, + _msg: &HardwareUnsubscribeCmd, + ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> { + future::ready(Err(ButtplugDeviceError::UnhandledCommand( + "SDL2 hardware does not support unsubscribe".to_owned(), + ))) + .boxed() + } +} diff --git a/buttplug/src/server/device/protocol/mod.rs b/buttplug/src/server/device/protocol/mod.rs index 102364221..3b3e048f9 100644 --- a/buttplug/src/server/device/protocol/mod.rs +++ b/buttplug/src/server/device/protocol/mod.rs @@ -58,6 +58,7 @@ pub mod raw_protocol; pub mod realov; pub mod sakuraneko; pub mod satisfyer; +pub mod sdl2; pub mod svakom; pub mod svakom_alex; pub mod svakom_iker; @@ -272,6 +273,7 @@ pub fn get_default_protocol_map() -> HashMap bool { + true + } + + fn handle_scalar_cmd( + &self, + cmds: &[Option<(ActuatorType, u32)>], + ) -> Result, ButtplugDeviceError> { + if let Some(Some((actuator_type, _))) = cmds.iter().find(|cmd| match cmd { + None => false, + Some((ActuatorType::Vibrate, _)) => false, + _ => true, + }) { + return Err(ButtplugDeviceError::ProtocolSpecificError( + "SDL2".to_owned(), + format!("{actuator_type} actuators are not supported"), + )); + }; + + let mut cmd = vec![]; + if cmd + .write_u16::( + cmds[1] + .expect("GCM uses match_all, we'll always get 2 values") + .1 as u16, + ) + .is_err() + || cmd + .write_u16::( + cmds[0] + .expect("GCM uses match_all, we'll always get 2 values") + .1 as u16, + ) + .is_err() + { + return Err(ButtplugDeviceError::ProtocolSpecificError( + "SDL2".to_owned(), + "Cannot convert SDL2 value for processing".to_owned(), + )); + } + + Ok(vec![ + HardwareWriteCmd::new(Endpoint::TxVibrate, cmd, false).into() + ]) + } + + fn handle_battery_level_cmd( + &self, + device: Arc, + msg: message::SensorReadCmd, + ) -> BoxFuture> { + async move { + let reading = device + .read_value(&HardwareReadCmd::new(Endpoint::RxBLEBattery, 0, 0)) + .await?; + + let data_len = reading.data().len(); + if data_len != 1 { + return Err(ButtplugDeviceError::ProtocolSpecificError( + "SDL2".to_owned(), + format!("Expected 1 byte of battery data, got {data_len}"), + )); + } + + let battery = reading.data()[0] as i32; + Ok( + message::SensorReading::new( + msg.device_index(), + *msg.sensor_index(), + *msg.sensor_type(), + vec![battery], + ) + .into(), + ) + } + .boxed() + } +} diff --git a/buttplug/src/util/device_configuration.rs b/buttplug/src/util/device_configuration.rs index e88f0935b..462aee208 100644 --- a/buttplug/src/util/device_configuration.rs +++ b/buttplug/src/util/device_configuration.rs @@ -19,6 +19,7 @@ use crate::{ ProtocolAttributesType, ProtocolCommunicationSpecifier, ProtocolDeviceAttributes, + SDL2Specifier, SerialSpecifier, ServerDeviceMessageAttributes, USBSpecifier, @@ -124,6 +125,8 @@ struct ProtocolDefinition { #[serde(skip_serializing_if = "Option::is_none")] xinput: Option, #[serde(skip_serializing_if = "Option::is_none")] + sdl2: Option, + #[serde(skip_serializing_if = "Option::is_none")] websocket: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "lovense-connect-service")] @@ -207,6 +210,9 @@ impl From for ProtocolDeviceConfiguration { if let Some(xinput) = &protocol_def.xinput { specifiers.push(ProtocolCommunicationSpecifier::XInput(*xinput)); } + if let Some(sdl2) = &protocol_def.sdl2 { + specifiers.push(ProtocolCommunicationSpecifier::SDL2(*sdl2)); + } if let Some(websocket) = &protocol_def.websocket { specifiers.push(ProtocolCommunicationSpecifier::Websocket(websocket.clone())); } diff --git a/buttplug/src/util/mod.rs b/buttplug/src/util/mod.rs index 9e9eb6090..f666ec1f1 100644 --- a/buttplug/src/util/mod.rs +++ b/buttplug/src/util/mod.rs @@ -60,7 +60,16 @@ use crate::{ pub async fn in_process_client(client_name: &str, allow_raw_messages: bool) -> ButtplugClient { let mut server_builder = ButtplugServerBuilder::default(); - #[cfg(all(feature = "btleplug-manager", any(target_os = "windows", target_os = "macos", target_os = "linux", target_os="ios", target_os="android")))] + #[cfg(all( + feature = "btleplug-manager", + any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "ios", + target_os = "android" + ) + ))] { use crate::server::device::hardware::communication::btleplug::BtlePlugCommunicationManagerBuilder; server_builder.comm_manager(BtlePlugCommunicationManagerBuilder::default()); @@ -72,7 +81,10 @@ pub async fn in_process_client(client_name: &str, allow_raw_messages: bool) -> B WebsocketServerDeviceCommunicationManagerBuilder::default().listen_on_all_interfaces(true), ); } - #[cfg(all(feature = "serial-manager", any(target_os = "windows", target_os = "macos", target_os = "linux")))] + #[cfg(all( + feature = "serial-manager", + any(target_os = "windows", target_os = "macos", target_os = "linux") + ))] { use crate::server::device::hardware::communication::serialport::SerialPortCommunicationManagerBuilder; server_builder.comm_manager(SerialPortCommunicationManagerBuilder::default()); @@ -82,7 +94,10 @@ pub async fn in_process_client(client_name: &str, allow_raw_messages: bool) -> B use crate::server::device::hardware::communication::lovense_connect_service::LovenseConnectServiceCommunicationManagerBuilder; server_builder.comm_manager(LovenseConnectServiceCommunicationManagerBuilder::default()); } - #[cfg(all(feature = "lovense-dongle-manager", any(target_os = "windows", target_os = "macos", target_os = "linux")))] + #[cfg(all( + feature = "lovense-dongle-manager", + any(target_os = "windows", target_os = "macos", target_os = "linux") + ))] { use crate::server::device::hardware::communication::lovense_dongle::{ LovenseHIDDongleCommunicationManagerBuilder, @@ -96,6 +111,20 @@ pub async fn in_process_client(client_name: &str, allow_raw_messages: bool) -> B use crate::server::device::hardware::communication::xinput::XInputDeviceCommunicationManagerBuilder; server_builder.comm_manager(XInputDeviceCommunicationManagerBuilder::default()); } + #[cfg(all( + feature = "sdl2-manager", + any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "ios", + target_os = "android" + ) + ))] + { + use crate::server::device::hardware::communication::sdl2::SDL2DeviceCommunicationManagerBuilder; + server_builder.comm_manager(SDL2DeviceCommunicationManagerBuilder::default()); + } if allow_raw_messages { server_builder.allow_raw_messages(); } From 83a13bbe7843a6d806cbf351c4ff2b4c8a83e7c4 Mon Sep 17 00:00:00 2001 From: Vyr Cossont Date: Fri, 9 Dec 2022 17:17:07 -0800 Subject: [PATCH 2/4] WIP: cleaner actors --- .../sdl2/sdl2_device_comm_manager.rs | 162 +---------------- .../communication/sdl2/sdl2_hardware.rs | 165 +++++++++++++++++- 2 files changed, 169 insertions(+), 158 deletions(-) diff --git a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs index 9d629ff3c..dd92da2f1 100644 --- a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs +++ b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs @@ -9,7 +9,7 @@ use crate::{ core::ButtplugResultFuture, server::device::hardware::{ communication::{ - sdl2::sdl2_hardware::SDL2HardwareConnector, + sdl2::sdl2_hardware::{SDL2HardwareConnector, SDL2JoystickActor}, HardwareCommunicationManager, HardwareCommunicationManagerBuilder, HardwareCommunicationManagerEvent, @@ -19,17 +19,9 @@ use crate::{ }; use async_trait::async_trait; use futures_util::FutureExt; -use sdl2::{ - self, - event::Event, - joystick::{Joystick, PowerLevel}, - EventPump, - IntegerOrSdlError, - JoystickSubsystem, -}; +use sdl2::{self, event::Event, EventPump, JoystickSubsystem}; use std::{ collections::HashMap, - fmt::{Debug, Formatter}, future, sync::{ atomic::{AtomicBool, Ordering}, @@ -38,7 +30,7 @@ use std::{ thread, }; use tokio::{ - sync::{broadcast, mpsc, oneshot}, + sync::{broadcast, mpsc}, task::{self, LocalSet}, }; @@ -107,141 +99,6 @@ impl HardwareCommunicationManager for SDL2DeviceCommunicationManager { } } -trait SdlResultExt { - fn map_sdl_error(self) -> Result; -} - -impl SdlResultExt for Result { - fn map_sdl_error(self) -> Result { - self.map_err(|e| format!("{e}")) - } -} - -/// Lives on the SDL2 event loop thread and responds to messages. -struct SDL2JoystickActor { - joystick: Joystick, - message_receiver: mpsc::Receiver, -} - -struct JoystickDebug<'a>(&'a Joystick); - -impl Debug for JoystickDebug<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Joystick") - .field("instance_id", &self.0.instance_id()) - .field("name", &self.0.name()) - .finish_non_exhaustive() - } -} - -impl Debug for SDL2JoystickActor { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SDL2JoystickActor") - .field("joystick", &JoystickDebug(&self.joystick)) - .field("message_receiver", &self.message_receiver) - .finish() - } -} - -impl SDL2JoystickActor { - fn handle_message(&mut self, message: SDL2JoystickMessage) { - match message { - SDL2JoystickMessage::Rumble { - low_frequency_rumble, - high_frequency_rumble, - duration_ms, - oneshot_sender, - } => { - // If the receiver's gone, we don't care if the send fails. - let _ = oneshot_sender.send( - self - .joystick - .set_rumble(low_frequency_rumble, high_frequency_rumble, duration_ms) - .map_sdl_error(), - ); - } - SDL2JoystickMessage::PowerLevel { oneshot_sender } => { - let _ = oneshot_sender.send(self.joystick.power_level().map_sdl_error()); - } - } - } - - async fn run(&mut self) { - while let Some(msg) = self.message_receiver.recv().await { - self.handle_message(msg); - } - } -} - -/// Lives inside `SDL2Hardware` on any thread. -/// Sends and receives messages to its actor. -/// Sends disconnect events to the `SDL2Hardware`. -#[derive(Clone, Debug)] -pub struct SDL2JoystickActorHandle { - message_sender: mpsc::Sender, -} - -impl SDL2JoystickActorHandle { - async fn send_message_and_wait( - &self, - message: SDL2JoystickMessage, - oneshot_receiver: oneshot::Receiver, - ) -> Result { - self - .message_sender - .send(message) - .await - .map_err(|e| format!("SDL2 joystick actor proxy couldn't send message: {e}"))?; - // TODO(Vyr): add a timeout here - oneshot_receiver - .await - .map_err(|e| format!("SDL2 joystick actor proxy couldn't receive result: {e}")) - } - - pub async fn rumble( - &self, - low_frequency_rumble: u16, - high_frequency_rumble: u16, - duration_ms: u32, - ) -> Result<(), String> { - let (oneshot_sender, oneshot_receiver) = oneshot::channel(); - self - .send_message_and_wait( - SDL2JoystickMessage::Rumble { - low_frequency_rumble, - high_frequency_rumble, - duration_ms, - oneshot_sender, - }, - oneshot_receiver, - ) - .await? - } - - pub async fn power_level(&self) -> Result { - let (oneshot_sender, oneshot_receiver) = oneshot::channel(); - self - .send_message_and_wait( - SDL2JoystickMessage::PowerLevel { oneshot_sender }, - oneshot_receiver, - ) - .await? - } -} - -#[derive(Debug)] -enum SDL2JoystickMessage { - Rumble { - low_frequency_rumble: u16, - high_frequency_rumble: u16, - duration_ms: u32, - oneshot_sender: oneshot::Sender>, - }, - PowerLevel { - oneshot_sender: oneshot::Sender>, - }, -} - /// Only one thread is allowed to talk to the SDL event loop, /// and it has to be the one that initialized SDL. /// The joystick subsystem and joystick handles cannot be moved across threads either. @@ -332,24 +189,21 @@ async fn sdl2_poll_event( trace!("Opened new joystick at index {index} with ID {id}: {name}"); let address = format!("{id}"); - let (message_sender, message_receiver) = mpsc::channel(256); let (event_sender, _) = broadcast::channel(256); event_senders.insert(joystick.instance_id(), event_sender.clone()); + let joystick_actor = SDL2JoystickActor::new(joystick); + let joystick_actor_handle = joystick_actor.new_handle(); + task::spawn_local( async move { - SDL2JoystickActor { - joystick, - message_receiver, - } - .run() - .await + let mut joystick_actor = joystick_actor; + joystick_actor.run().await } .boxed_local(), ); - let joystick_actor_handle = SDL2JoystickActorHandle { message_sender }; if let Err(e) = comm_sender .send(HardwareCommunicationManagerEvent::DeviceFound { name: name.clone(), diff --git a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs index b7344a252..1cc51cdd4 100644 --- a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs +++ b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs @@ -4,7 +4,6 @@ use crate::{ server::device::{ configuration::{ProtocolCommunicationSpecifier, SDL2Specifier}, hardware::{ - communication::sdl2::sdl2_device_comm_manager::SDL2JoystickActorHandle, GenericHardwareSpecializer, Hardware, HardwareConnector, @@ -22,9 +21,16 @@ use crate::{ use async_trait::async_trait; use byteorder::{LittleEndian, ReadBytesExt}; use futures::future::{self, BoxFuture, FutureExt}; -use sdl2::joystick::PowerLevel; -use std::io::Cursor; -use tokio::sync::broadcast; +use sdl2::{ + self, + joystick::{Joystick, PowerLevel}, + IntegerOrSdlError, +}; +use std::{ + fmt::{Debug, Formatter}, + io::Cursor, +}; +use tokio::sync::{broadcast, mpsc, oneshot}; #[derive(Debug)] struct SDL2HardwareConnectArgs { @@ -195,3 +201,154 @@ impl HardwareInternal for SDL2Hardware { .boxed() } } + +trait SdlResultExt { + fn map_sdl_error(self) -> Result; +} + +impl SdlResultExt for Result { + fn map_sdl_error(self) -> Result { + self.map_err(|e| format!("{e}")) + } +} + +/// Lives on the SDL2 event loop thread and responds to messages from its actor handles. +pub struct SDL2JoystickActor { + joystick: Joystick, + message_sender: mpsc::Sender, + message_receiver: mpsc::Receiver, +} + +impl SDL2JoystickActor { + pub fn new(joystick: Joystick) -> Self { + let (message_sender, message_receiver) = mpsc::channel(256); + Self { + joystick, + message_sender, + message_receiver, + } + } + + pub fn new_handle(&self) -> SDL2JoystickActorHandle { + SDL2JoystickActorHandle { + message_sender: self.message_sender.clone(), + } + } + + fn handle_message(&mut self, message: SDL2JoystickMessage) { + match message { + SDL2JoystickMessage::Rumble { + low_frequency_rumble, + high_frequency_rumble, + duration_ms, + oneshot_sender, + } => { + // If the receiver's gone, we don't care if the send fails. + let _ = oneshot_sender.send( + self + .joystick + .set_rumble(low_frequency_rumble, high_frequency_rumble, duration_ms) + .map_sdl_error(), + ); + } + SDL2JoystickMessage::PowerLevel { oneshot_sender } => { + let _ = oneshot_sender.send(self.joystick.power_level().map_sdl_error()); + } + } + } + + /// Awaited on SDL event loop thread. + pub async fn run(&mut self) { + while let Some(msg) = self.message_receiver.recv().await { + self.handle_message(msg); + } + } +} + +struct JoystickDebug<'a>(&'a Joystick); + +impl Debug for JoystickDebug<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Joystick") + .field("instance_id", &self.0.instance_id()) + .field("name", &self.0.name()) + .finish_non_exhaustive() + } +} + +impl Debug for SDL2JoystickActor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SDL2JoystickActor") + .field("joystick", &JoystickDebug(&self.joystick)) + .field("message_receiver", &self.message_receiver) + .finish() + } +} + +/// Lives inside `SDL2Hardware` on any thread. +/// Sends and receives messages to its actor. +#[derive(Clone, Debug)] +pub struct SDL2JoystickActorHandle { + message_sender: mpsc::Sender, +} + +impl SDL2JoystickActorHandle { + async fn send_message_and_wait( + &self, + message: SDL2JoystickMessage, + oneshot_receiver: oneshot::Receiver, + ) -> Result { + self + .message_sender + .send(message) + .await + .map_err(|e| format!("SDL2 joystick actor proxy couldn't send message: {e}"))?; + // TODO(Vyr): add a timeout here + oneshot_receiver + .await + .map_err(|e| format!("SDL2 joystick actor proxy couldn't receive result: {e}")) + } + + pub async fn rumble( + &self, + low_frequency_rumble: u16, + high_frequency_rumble: u16, + duration_ms: u32, + ) -> Result<(), String> { + let (oneshot_sender, oneshot_receiver) = oneshot::channel(); + self + .send_message_and_wait( + SDL2JoystickMessage::Rumble { + low_frequency_rumble, + high_frequency_rumble, + duration_ms, + oneshot_sender, + }, + oneshot_receiver, + ) + .await? + } + + pub async fn power_level(&self) -> Result { + let (oneshot_sender, oneshot_receiver) = oneshot::channel(); + self + .send_message_and_wait( + SDL2JoystickMessage::PowerLevel { oneshot_sender }, + oneshot_receiver, + ) + .await? + } +} + +#[derive(Debug)] +enum SDL2JoystickMessage { + Rumble { + low_frequency_rumble: u16, + high_frequency_rumble: u16, + duration_ms: u32, + oneshot_sender: oneshot::Sender>, + }, + PowerLevel { + oneshot_sender: oneshot::Sender>, + }, +} From 6e473e8a1b2965c3614a746891b6dc79a27924fb Mon Sep 17 00:00:00 2001 From: Vyr Cossont Date: Fri, 9 Dec 2022 17:42:28 -0800 Subject: [PATCH 3/4] WIP: cleaner errors --- .../sdl2/sdl2_device_comm_manager.rs | 43 +++++++++++++------ .../communication/sdl2/sdl2_hardware.rs | 41 +++++++++--------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs index dd92da2f1..597f67a4a 100644 --- a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs +++ b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_device_comm_manager.rs @@ -57,7 +57,7 @@ impl SDL2DeviceCommunicationManager { { let scanning_status = scanning_status.clone(); thread::Builder::new() - .name("sdl-event-loop-thread".to_owned()) + .name("sdl2-event-loop-thread".to_owned()) .spawn(move || { if let Err(e) = sdl2_event_loop_thread(sender, scanning_status) { error!("SDL2 comm manager: {e}"); @@ -126,7 +126,7 @@ fn sdl2_event_loop_thread( let mut event_senders = HashMap::>::new(); let rt = tokio::runtime::Builder::new_current_thread() - .thread_name("sdl-event-loop-thread-rt") + .thread_name("sdl2-event-loop-thread-rt") .enable_all() .build() .map_err(|e| { @@ -136,8 +136,8 @@ fn sdl2_event_loop_thread( let local_set = LocalSet::new(); rt.block_on(async { - while !comm_sender.is_closed() { - local_set + loop { + let exit = local_set .run_until(sdl2_poll_event( &comm_sender, &mut event_pump, @@ -146,6 +146,9 @@ fn sdl2_event_loop_thread( scanning_status.clone(), )) .await; + if exit { + break; + } } }); @@ -155,38 +158,50 @@ fn sdl2_event_loop_thread( /// Handle at most one possibly relevant SDL event. /// Drives the event pump. +/// Returns true if we should stop processing SDL events. async fn sdl2_poll_event( comm_sender: &mpsc::Sender, event_pump: &mut EventPump, joystick_subsystem: &JoystickSubsystem, event_senders: &mut HashMap>, scanning_status: Arc, -) { +) -> bool { // Yield at least once so we have time to drive futures in the local set. task::yield_now().await; if let Some(event) = event_pump.poll_event() { match event { Event::JoyDeviceAdded { which: index, .. } => { - trace!("SDL2 comm manager found a new joystick at index {index}"); + debug!("SDL2 comm manager found a new joystick at index {index}"); + + if comm_sender.is_closed() { + trace!( + "SDL2 comm manager event sender is closed. Skipping new joystick at index {index}" + ); + return false; + } + if !scanning_status.load(Ordering::SeqCst) { - trace!("SDL2 comm manager is not scanning, skipping new joystick at index {index}"); - return; + trace!("SDL2 comm manager is not scanning. Skipping new joystick at index {index}"); + return false; } + let joystick = match joystick_subsystem.open(index) { Ok(joystick) => joystick, Err(e) => { trace!("Couldn't open new joystick at index {index}: {e}"); - return; + return false; } }; + if !joystick.has_rumble() { trace!("New joystick at index {index} does not support rumble, skipping it"); - return; + return false; } + let name = joystick.name(); let id = joystick.instance_id(); - trace!("Opened new joystick at index {index} with ID {id}: {name}"); + debug!("Opened new joystick at index {index} with ID {id}: {name}"); let address = format!("{id}"); let (event_sender, _) = broadcast::channel(256); @@ -232,11 +247,13 @@ async fn sdl2_poll_event( } Event::Quit { .. } => { - // TODO(Vyr): this should exit the thread - println!("SDL says byyyeeeeeeee!"); + debug!("SDL is quitting"); + return true; } _ => {} } } + + return false; } diff --git a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs index 1cc51cdd4..f781f2a09 100644 --- a/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs +++ b/buttplug/src/server/device/hardware/communication/sdl2/sdl2_hardware.rs @@ -144,9 +144,7 @@ impl HardwareInternal for SDL2Hardware { PowerLevel::Full => Ok(100), PowerLevel::Wired => Ok(100), }, - Err(e) => Err(ButtplugDeviceError::DeviceSpecificError( - HardwareSpecificError::SDL2Error(e), - )), + Err(e) => Err(e), } .map(|r| HardwareReading::new(Endpoint::Rx, &vec![r])) } @@ -176,7 +174,6 @@ impl HardwareInternal for SDL2Hardware { 0, // indefinitely ) .await - .map_err(|e| ButtplugDeviceError::DeviceSpecificError(HardwareSpecificError::SDL2Error(e))) } .boxed() } @@ -203,12 +200,14 @@ impl HardwareInternal for SDL2Hardware { } trait SdlResultExt { - fn map_sdl_error(self) -> Result; + fn map_sdl_error(self) -> Result; } impl SdlResultExt for Result { - fn map_sdl_error(self) -> Result { - self.map_err(|e| format!("{e}")) + fn map_sdl_error(self) -> Result { + self.map_err(|e| { + ButtplugDeviceError::DeviceSpecificError(HardwareSpecificError::SDL2Error(format!("{e}"))) + }) } } @@ -297,16 +296,18 @@ impl SDL2JoystickActorHandle { &self, message: SDL2JoystickMessage, oneshot_receiver: oneshot::Receiver, - ) -> Result { - self - .message_sender - .send(message) - .await - .map_err(|e| format!("SDL2 joystick actor proxy couldn't send message: {e}"))?; + ) -> Result { + self.message_sender.send(message).await.map_err(|e| { + ButtplugDeviceError::DeviceSpecificError(HardwareSpecificError::SDL2Error(format!( + "SDL2 joystick actor proxy couldn't send message: {e}" + ))) + })?; // TODO(Vyr): add a timeout here - oneshot_receiver - .await - .map_err(|e| format!("SDL2 joystick actor proxy couldn't receive result: {e}")) + oneshot_receiver.await.map_err(|e| { + ButtplugDeviceError::DeviceSpecificError(HardwareSpecificError::SDL2Error(format!( + "SDL2 joystick actor proxy couldn't receive result: {e}" + ))) + }) } pub async fn rumble( @@ -314,7 +315,7 @@ impl SDL2JoystickActorHandle { low_frequency_rumble: u16, high_frequency_rumble: u16, duration_ms: u32, - ) -> Result<(), String> { + ) -> Result<(), ButtplugDeviceError> { let (oneshot_sender, oneshot_receiver) = oneshot::channel(); self .send_message_and_wait( @@ -329,7 +330,7 @@ impl SDL2JoystickActorHandle { .await? } - pub async fn power_level(&self) -> Result { + pub async fn power_level(&self) -> Result { let (oneshot_sender, oneshot_receiver) = oneshot::channel(); self .send_message_and_wait( @@ -346,9 +347,9 @@ enum SDL2JoystickMessage { low_frequency_rumble: u16, high_frequency_rumble: u16, duration_ms: u32, - oneshot_sender: oneshot::Sender>, + oneshot_sender: oneshot::Sender>, }, PowerLevel { - oneshot_sender: oneshot::Sender>, + oneshot_sender: oneshot::Sender>, }, } From 632b009e5dc542c6527fc3bdcb3677589888a529 Mon Sep 17 00:00:00 2001 From: Vyr Cossont Date: Wed, 14 Dec 2022 12:42:54 -0800 Subject: [PATCH 4/4] Pin sdl2 to a specific known-working revision --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2e56450ac..ddda00e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ codegen-units = 1 # Need development sdl2 version with additional rumble-related methods [patch.crates-io] -sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2", branch = "master" } +sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2", rev = "a147388" }