use crate::mux::MuxRumbleTarget;
use crate::mux::modes::MuxModeType;
use crate::utils::ff::{EffectManager, PhysicalFFDev};
use crate::utils::gilrs::GamepadResource;
use evdev::uinput::VirtualDevice;
use evdev::{Device, EventType, InputEvent};
use gilrs::{GamepadId, Gilrs};
use log::{debug, error, info, warn};
use parking_lot::RwLock;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::Duration;
const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000);
pub struct RuntimeSettings {
pub mode: Arc<RwLock<MuxModeType>>,
pub rumble: Arc<RwLock<MuxRumbleTarget>>,
active_controllers: Arc<RwLock<HashSet<GamepadId>>>,
ff_generation: Arc<AtomicU64>,
}
impl RuntimeSettings {
pub fn new(
mode: MuxModeType,
rumble: MuxRumbleTarget,
primary_id: GamepadId,
assist_id: GamepadId,
) -> Self {
let mode_impl = crate::mux::modes::create_mux_mode(mode.clone());
let initial_active = mode_impl.initial_active_controllers(primary_id, assist_id);
Self {
mode: Arc::new(RwLock::new(mode)),
rumble: Arc::new(RwLock::new(rumble)),
active_controllers: Arc::new(RwLock::new(initial_active.into_iter().collect())),
ff_generation: Arc::new(AtomicU64::new(0)),
}
}
pub fn update_mode(&self, new_mode: MuxModeType, initial_active: Vec<GamepadId>) {
let mut mode = self.mode.write();
*mode = new_mode;
let mut active = self.active_controllers.write();
active.clear();
active.extend(initial_active);
self.ff_generation.fetch_add(1, Ordering::Release);
}
pub fn set_active_controllers(&self, controllers: Vec<GamepadId>) {
let mut active = self.active_controllers.write();
active.clear();
active.extend(controllers);
self.ff_generation.fetch_add(1, Ordering::Release);
}
pub fn update_rumble(&self, new_rumble: MuxRumbleTarget) {
let mut rumble = self.rumble.write();
*rumble = new_rumble;
self.ff_generation.fetch_add(1, Ordering::Release);
}
pub fn get_mode(&self) -> MuxModeType {
self.mode.read().clone()
}
pub fn get_rumble(&self) -> MuxRumbleTarget {
self.rumble.read().clone()
}
pub fn ff_generation(&self) -> u64 {
self.ff_generation.load(Ordering::Acquire)
}
pub fn get_active_controllers(&self) -> Vec<GamepadId> {
self.active_controllers.read().iter().copied().collect()
}
}
pub fn run_input_loop(
mut gilrs: Gilrs,
mut v_dev: Device,
runtime_settings: Arc<RuntimeSettings>,
p_id: GamepadId,
a_id: GamepadId,
shutdown: Arc<AtomicBool>,
) {
let mut mux_mode = crate::mux::modes::create_mux_mode(runtime_settings.get_mode());
let mut last_mode = runtime_settings.get_mode();
while !shutdown.load(Ordering::SeqCst) {
let current_mode = runtime_settings.get_mode();
if current_mode != last_mode {
info!(
"Switching mux mode from {:?} to {:?}",
last_mode, current_mode
);
mux_mode = crate::mux::modes::create_mux_mode(current_mode.clone());
let defaults = mux_mode.initial_active_controllers(p_id, a_id);
runtime_settings.update_mode(current_mode.clone(), defaults);
last_mode = current_mode;
}
while let Some(event) = gilrs.next_event_blocking(Some(NEXT_EVENT_TIMEOUT)) {
if shutdown.load(Ordering::SeqCst) {
break;
}
if event.id != p_id && event.id != a_id {
continue;
}
if let Some(output) = mux_mode.handle_event(&event, p_id, a_id, &gilrs) {
if let Some(new_active) = output.set_active_controllers {
runtime_settings.set_active_controllers(new_active);
}
if !output.events.is_empty() {
let mut out_events = output.events;
out_events.push(InputEvent::new(EventType::SYNCHRONIZATION.0, 0, 0));
if let Err(e) = v_dev.send_events(&out_events) {
error!("Failed to write input events: {}", e);
}
}
}
}
}
}
pub fn run_ff_loop(
v_uinput: &mut VirtualDevice,
all_resources: HashMap<GamepadId, GamepadResource>,
runtime_settings: Arc<RuntimeSettings>,
p_id: GamepadId,
a_id: GamepadId,
shutdown: Arc<AtomicBool>,
) {
let mut effect_manager = EffectManager::new();
let mut phys_devs = build_ff_targets(&all_resources, &runtime_settings, p_id, a_id);
let mut last_ff_generation = runtime_settings.ff_generation();
info!("FF Thread started.");
while !shutdown.load(Ordering::SeqCst) {
let current_generation = runtime_settings.ff_generation();
if current_generation != last_ff_generation {
info!(
"FF targets changed, rebuilding (generation {} -> {})",
last_ff_generation, current_generation
);
let mut new_phys_devs = build_ff_targets(&all_resources, &runtime_settings, p_id, a_id);
for dev in &mut new_phys_devs {
let errors = dev.sync_effects(&effect_manager);
for (virt_id, error) in errors {
error!(
"Failed to sync effect {} to {}: {}",
virt_id,
dev.resource.path.display(),
error
);
}
}
for dev in &mut phys_devs {
for virt_id in effect_manager.get_playing() {
let _ = dev.control_effect(virt_id, false);
}
}
phys_devs = new_phys_devs;
last_ff_generation = current_generation;
}
let events: Vec<_> = match v_uinput.fetch_events() {
Ok(iter) => iter.collect(),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => vec![],
Err(e) => {
error!("Error fetching FF events: {}", e);
vec![]
}
};
for event in events {
match event.destructure() {
evdev::EventSummary::UInput(ev, evdev::UInputCode::UI_FF_UPLOAD, ..) => {
if let Ok(upload_ev) = v_uinput.process_ff_upload(ev) {
let virt_id = upload_ev.effect_id();
let effect_data = upload_ev.effect();
effect_manager.upload(virt_id, effect_data);
for dev in &mut phys_devs {
if let Err(e) = dev.upload_effect(virt_id, effect_data) {
error!(
"Failed to upload effect {} to {}: {}",
virt_id,
dev.resource.path.display(),
e
);
}
}
}
}
evdev::EventSummary::UInput(ev, evdev::UInputCode::UI_FF_ERASE, ..) => {
if let Ok(erase_ev) = v_uinput.process_ff_erase(ev) {
let virt_id = erase_ev.effect_id() as i16;
for dev in &mut phys_devs {
if let Err(e) = dev.erase_effect(virt_id) {
error!(
"Failed to erase effect {} from {}: {}",
virt_id,
dev.resource.path.display(),
e
);
}
}
effect_manager.erase(virt_id);
}
}
evdev::EventSummary::ForceFeedback(_, effect_id, status) => {
let virt_id = effect_id.0 as i16;
let is_playing = status == evdev::FFStatusCode::FF_STATUS_PLAYING.0 as i32;
effect_manager.set_playing(virt_id, is_playing);
for dev in &mut phys_devs {
match dev.control_effect(virt_id, is_playing) {
Ok(()) => {
}
Err(e) if e.raw_os_error() == Some(libc::ENODEV) => {
warn!(
"Device {} disconnected, attempting recovery",
dev.resource.path.display()
);
match dev.recover(&effect_manager) {
Ok(()) => {
info!(
"Successfully recovered device {}",
dev.resource.path.display()
);
if let Err(retry_err) =
dev.control_effect(virt_id, is_playing)
{
error!(
"Failed to control effect {} after recovery on {}: {}",
virt_id,
dev.resource.path.display(),
retry_err
);
}
}
Err(recover_err) => {
error!(
"Failed to recover device {}: {}",
dev.resource.path.display(),
recover_err
);
}
}
}
Err(e) => {
error!(
"Failed to control effect {} on {}: {}",
virt_id,
dev.resource.path.display(),
e
);
}
}
}
}
_ => {
debug!("Unhandled FF event: {:?}", event);
}
}
}
}
}
fn build_ff_targets(
all_resources: &HashMap<GamepadId, GamepadResource>,
runtime_settings: &Arc<RuntimeSettings>,
p_id: GamepadId,
a_id: GamepadId,
) -> Vec<PhysicalFFDev> {
let rumble = runtime_settings.get_rumble();
let rumble_ids = match rumble {
MuxRumbleTarget::Active => runtime_settings.get_active_controllers(),
MuxRumbleTarget::Assist => vec![a_id],
MuxRumbleTarget::Both => vec![p_id, a_id],
MuxRumbleTarget::None => vec![],
MuxRumbleTarget::Primary => vec![p_id],
};
rumble_ids
.into_iter()
.filter_map(|id| {
all_resources.get(&id).and_then(|res| {
if res.device.supported_ff().is_some() {
Some(PhysicalFFDev::new(res.clone()))
} else {
warn!(
"Device {} ({}) does not support force feedback (rumble setting: {:?})",
res.name,
res.path.display(),
rumble
);
None
}
})
})
.collect()
}