use crate::demux::DemuxRumbleTarget;
use crate::demux::modes::DemuxModeType;
use crate::demux::runtime::DemuxRuntimeSettings;
use crate::utils::evdev::VirtualGamepadInfo;
use crate::utils::hide::ScopedDeviceHider;
use crate::{HideType, SpoofTarget};
use evdev::Device;
use gilrs::{GamepadId, Gilrs};
use log::info;
use std::error::Error;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::thread;
pub struct DemuxConfig {
pub primary_id: GamepadId,
pub virtuals: usize,
pub mode: DemuxModeType,
pub hide: HideType,
pub spoof: SpoofTarget,
pub rumble: DemuxRumbleTarget,
}
pub struct VirtualDeviceInfo {
pub path: PathBuf,
}
pub struct DemuxHandle {
pub input_handle: thread::JoinHandle<()>,
pub ff_handles: Vec<thread::JoinHandle<()>>,
pub shutdown: Arc<AtomicBool>,
pub virtual_device_paths: Vec<PathBuf>,
#[allow(dead_code)]
pub hider: ScopedDeviceHider,
}
impl DemuxHandle {
pub fn shutdown(self) {
use std::sync::atomic::Ordering;
self.shutdown.store(true, Ordering::SeqCst);
for path in &self.virtual_device_paths {
if let Ok(mut vdev) = Device::open(path) {
use evdev::{EventType, InputEvent};
let _ = vdev.send_events(&[
InputEvent::new(EventType::FORCEFEEDBACK.0, 0, 0),
InputEvent::new(EventType::SYNCHRONIZATION.0, 0, 0),
]);
}
}
let _ = self.input_handle.join();
for handle in self.ff_handles {
let _ = handle.join();
}
}
}
pub fn start_demux(
gilrs: Gilrs,
config: DemuxConfig,
) -> Result<(DemuxHandle, Arc<DemuxRuntimeSettings>), Box<dyn Error>> {
let resources = crate::utils::gilrs::discover_gamepad_resources(&gilrs);
if config.virtuals == 0 {
return Err("DemuxConfig.virtuals must be greater than 0".into());
}
let mut hider = ScopedDeviceHider::new(config.hide)?;
if let Some(primary_res) = resources.get(&config.primary_id) {
hider.hide_gamepad_devices(primary_res)?;
}
let virtual_info = match config.spoof {
SpoofTarget::Primary => VirtualGamepadInfo::from(&gilrs.gamepad(config.primary_id)),
SpoofTarget::None | SpoofTarget::Assist => VirtualGamepadInfo {
name: "CtrlAssist Virtual Gamepad".into(),
vendor_id: None,
product_id: None,
},
};
let mut v_uinputs = Vec::new();
let mut virtual_devices = Vec::new();
for i in 0..config.virtuals {
let tag_string = format!("[{}]", i);
let tag_str = Some(tag_string.as_str());
let mut v_uinput = crate::utils::evdev::create_virtual_gamepad(&virtual_info, tag_str)?;
let v_resource = crate::utils::gilrs::wait_for_virtual_device(&mut v_uinput)?;
let virtual_msg = format!(
"Virtual: (#) {} @ {}",
v_resource.name,
v_resource.path.display()
);
info!("{}", virtual_msg);
println!("{}", virtual_msg);
virtual_devices.push(VirtualDeviceInfo {
path: v_resource.path.clone(),
});
v_uinputs.push((v_uinput, v_resource));
}
let runtime_settings = Arc::new(DemuxRuntimeSettings::new(
config.mode,
config.rumble,
config.virtuals,
));
let shutdown = Arc::new(AtomicBool::new(false));
let virtual_device_paths: Vec<PathBuf> =
virtual_devices.iter().map(|v| v.path.clone()).collect();
let primary_resource_ff = resources
.get(&config.primary_id)
.ok_or("Primary controller not found")?
.clone();
let v_devices: Vec<_> = virtual_devices
.iter()
.map(|v| Device::open(&v.path))
.collect::<Result<Vec<_>, _>>()?;
let shutdown_input = Arc::clone(&shutdown);
let runtime_settings_input = Arc::clone(&runtime_settings);
let input_handle = thread::spawn(move || {
crate::demux::runtime::run_input_loop(
gilrs,
v_devices,
runtime_settings_input,
config.primary_id,
shutdown_input,
);
});
let mut ff_handles = Vec::new();
for (virtual_index, (mut v_uinput, _)) in v_uinputs.into_iter().enumerate() {
let shutdown_ff = Arc::clone(&shutdown);
let runtime_settings_ff = Arc::clone(&runtime_settings);
let primary_resource = primary_resource_ff.clone();
let handle = thread::spawn(move || {
crate::demux::runtime::run_ff_loop(
&mut v_uinput,
primary_resource,
runtime_settings_ff,
virtual_index,
shutdown_ff,
);
});
ff_handles.push(handle);
}
Ok((
DemuxHandle {
input_handle,
ff_handles,
shutdown,
virtual_device_paths,
hider,
},
runtime_settings,
))
}