ctrlassist 0.4.0

Controller Assist for gaming on Linux
use super::{DemuxMode, DemuxOutput, helpers};
use evdev::InputEvent;
use gilrs::{Button, Event, EventType, GamepadId, Gilrs};

/// Build-time flag to control latching behavior on active device switch.
const DEMUX_UNICAST_LATCHING: bool = false;

/// Unicast mode: Route primary to currently active virtual gamepad
/// Cycle active with Mode button
#[derive(Default)]
pub struct UnicastMode {
    active_index: usize,
}

impl UnicastMode {
    /// Synchronize controller state to newly active virtual device
    fn sync_controller_state(primary: gilrs::Gamepad) -> Vec<InputEvent> {
        let state = primary.state();
        let mut events = Vec::new();

        // Synchronize button states
        for (code, button_data) in state.buttons() {
            let Some(gilrs::ev::AxisOrBtn::Btn(btn)) = primary.axis_or_btn_name(code) else {
                continue;
            };

            // Continue if button is Mode (exclusive binding)
            if btn == gilrs::Button::Mode {
                continue;
            }

            // Handle buttons mapped to keys
            if let Some(event) = helpers::create_button_key_event(btn, button_data.is_pressed()) {
                events.push(event);
            }

            // Handle buttons mapped to axes (triggers, D-pad)
            if let Some(abs_axis) = crate::utils::evdev::gilrs_button_to_evdev_axis(btn) {
                events.push(helpers::process_button_axis(btn, &primary, abs_axis));
            }
        }

        // Synchronize axis states
        for (code, axis_data) in state.axes() {
            let Some(gilrs::ev::AxisOrBtn::Axis(axis)) = primary.axis_or_btn_name(code) else {
                continue;
            };

            if let Some(event) = helpers::create_stick_event(axis, axis_data.value()) {
                events.push(event);
            }
        }

        events
    }

    /// Convert a gilrs event to evdev events
    fn convert_event(event: &Event, primary: gilrs::Gamepad) -> Option<Vec<InputEvent>> {
        match event.event {
            EventType::ButtonPressed(btn, _) | EventType::ButtonReleased(btn, _) => {
                let is_pressed = matches!(event.event, EventType::ButtonPressed(..));
                helpers::create_button_key_event(btn, is_pressed).map(|e| vec![e])
            }

            EventType::ButtonChanged(btn, _, _) => {
                let abs_axis = crate::utils::evdev::gilrs_button_to_evdev_axis(btn)?;
                Some(vec![helpers::process_button_axis(btn, &primary, abs_axis)])
            }

            EventType::AxisChanged(axis, raw_val, _) => {
                helpers::create_stick_event(axis, raw_val).map(|e| vec![e])
            }

            _ => None,
        }
    }
}

impl DemuxMode for UnicastMode {
    fn handle_event(
        &mut self,
        event: &Event,
        primary_id: GamepadId,
        virtuals: usize,
        gilrs: &Gilrs,
    ) -> Option<DemuxOutput> {
        if event.id != primary_id {
            return None;
        }

        // Handle mode button to cycle active device
        if matches!(event.event, EventType::ButtonPressed(Button::Mode, _)) {
            let old_active = self.active_index;
            self.active_index = (self.active_index + 1) % virtuals;

            let primary = gilrs.gamepad(primary_id);
            let sync_events = Self::sync_controller_state(primary);

            let mut events = Vec::new();

            // If latching is disabled, neutralize the previously active device
            if !DEMUX_UNICAST_LATCHING && old_active != self.active_index {
                let neutral_events = crate::utils::evdev::generate_neutral_gamepad_events();
                events.push((old_active, neutral_events));
            }

            events.push((self.active_index, sync_events));

            // Return events AND request to update active virtuals
            return Some(DemuxOutput {
                events,
                set_active_virtuals: Some(vec![self.active_index]),
            });
        }

        // Forward event to active device
        let primary = gilrs.gamepad(primary_id);
        Self::convert_event(event, primary)
            .map(|e| DemuxOutput::events(vec![(self.active_index, e)]))
    }

    fn initial_active_virtuals(&self, _virtuals: usize) -> Vec<usize> {
        // Unicast always starts at 0
        vec![0]
    }
}