From 799cebb1f0a70230218fe2b225410a92dca08cb3 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 17:37:14 -0600 Subject: [PATCH 01/28] wip: stage progress on Active RumbleTarget for MuxMode --- src/main.rs | 1 + src/mux/manager.rs | 7 +++- src/mux/modes/average.rs | 10 ++--- src/mux/modes/mod.rs | 34 +++++++++++++++- src/mux/modes/priority.rs | 10 ++--- src/mux/modes/toggle.rs | 16 ++++++-- src/mux/runtime.rs | 84 +++++++++++++++++++++++++++++++++------ src/tray/app.rs | 8 +++- 8 files changed, 139 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index 068e5ac..c2d6f67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,6 +108,7 @@ pub enum RumbleTarget { Assist, #[default] Both, + Active, None, } diff --git a/src/mux/manager.rs b/src/mux/manager.rs index eba0283..afea356 100644 --- a/src/mux/manager.rs +++ b/src/mux/manager.rs @@ -100,7 +100,12 @@ pub fn start_mux( ); // Create runtime settings - let runtime_settings = Arc::new(RuntimeSettings::new(config.mode, config.rumble)); + let runtime_settings = Arc::new(RuntimeSettings::new( + config.mode, + config.rumble, + config.primary_id, + config.assist_id + )); // Setup shutdown signal let shutdown = Arc::new(AtomicBool::new(false)); diff --git a/src/mux/modes/average.rs b/src/mux/modes/average.rs index 9f387aa..23d1189 100644 --- a/src/mux/modes/average.rs +++ b/src/mux/modes/average.rs @@ -1,4 +1,4 @@ -use super::{MuxMode, helpers}; +use super::{MuxMode, MuxOutput, helpers}; use evdev::InputEvent; use gilrs::{Button, Event, EventType, GamepadId, Gilrs}; @@ -12,7 +12,7 @@ impl MuxMode for AverageMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &Gilrs, - ) -> Option> { + ) -> Option { // Filter out irrelevant devices if event.id != primary_id && event.id != assist_id { return None; @@ -42,7 +42,7 @@ impl MuxMode for AverageMode { return None; } - helpers::create_button_key_event(btn, is_pressed).map(|e| vec![e]) + helpers::create_button_key_event(btn, is_pressed).map(|e| MuxOutput::events(vec![e])) } EventType::ButtonChanged(btn, _, _) => { @@ -82,7 +82,7 @@ impl MuxMode for AverageMode { helpers::create_trigger_event(final_value, abs_axis) }; - Some(vec![event]) + Some(MuxOutput::events(vec![event])) } EventType::AxisChanged(axis, _, _) => { @@ -114,7 +114,7 @@ impl MuxMode for AverageMode { .filter_map(|(ax, val)| helpers::create_stick_event(ax, val)) .collect::>(); - (!events.is_empty()).then_some(events) + (!events.is_empty()).then_some(MuxOutput::events(events)) } _ => None, diff --git a/src/mux/modes/mod.rs b/src/mux/modes/mod.rs index 9c6bbec..b13231d 100644 --- a/src/mux/modes/mod.rs +++ b/src/mux/modes/mod.rs @@ -16,6 +16,32 @@ pub enum MuxModeType { Toggle, } +/// Output from a mux mode handling an event +pub struct MuxOutput { + /// Events to forward to the virtual device + pub events: Vec, + /// Optional request to update which controllers are considered "active" for FF + pub set_active_controllers: Option>, +} + +impl MuxOutput { + /// Create output with only events + pub fn events(events: Vec) -> Self { + Self { + events, + set_active_controllers: None, + } + } + + /// Create output with events and active controller update + pub fn with_active(events: Vec, active: Vec) -> Self { + Self { + events, + set_active_controllers: Some(active), + } + } +} + /// The trait all muxing modes must implement pub trait MuxMode { fn handle_event( @@ -24,7 +50,13 @@ pub trait MuxMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &gilrs::Gilrs, - ) -> Option>; + ) -> Option; + + /// Return the initial set of active controllers for this mode. + /// Used when the mode is first initialized or changes. + fn initial_active_controllers(&self, primary_id: GamepadId, assist_id: GamepadId) -> Vec { + vec![primary_id, assist_id] + } } /// Factory function to create the correct mux mode diff --git a/src/mux/modes/priority.rs b/src/mux/modes/priority.rs index cabac63..d403c6f 100644 --- a/src/mux/modes/priority.rs +++ b/src/mux/modes/priority.rs @@ -1,4 +1,4 @@ -use super::{MuxMode, helpers}; +use super::{MuxMode, MuxOutput, helpers}; use evdev::InputEvent; use gilrs::{Button, Event, EventType, GamepadId, Gilrs}; @@ -12,7 +12,7 @@ impl MuxMode for PriorityMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &Gilrs, - ) -> Option> { + ) -> Option { // Filter out irrelevant devices if event.id != primary_id && event.id != assist_id { return None; @@ -38,7 +38,7 @@ impl MuxMode for PriorityMode { return None; } - helpers::create_button_key_event(btn, is_pressed).map(|e| vec![e]) + helpers::create_button_key_event(btn, is_pressed).map(|e| MuxOutput::events(vec![e])) } EventType::ButtonChanged(btn, _, _) => { @@ -67,7 +67,7 @@ impl MuxMode for PriorityMode { helpers::create_trigger_event(max_val, abs_axis) }; - Some(vec![event]) + Some(MuxOutput::events(vec![event])) } EventType::AxisChanged(axis, _, _) => { @@ -92,7 +92,7 @@ impl MuxMode for PriorityMode { }) .collect::>(); - (!events.is_empty()).then_some(events) + (!events.is_empty()).then_some(MuxOutput::events(events)) } _ => None, diff --git a/src/mux/modes/toggle.rs b/src/mux/modes/toggle.rs index a46a632..0cbf766 100644 --- a/src/mux/modes/toggle.rs +++ b/src/mux/modes/toggle.rs @@ -1,4 +1,4 @@ -use super::{MuxMode, helpers}; +use super::{MuxMode, MuxOutput, helpers}; use evdev::InputEvent; use gilrs::{Event, EventType, GamepadId, Gilrs}; @@ -82,7 +82,7 @@ impl MuxMode for ToggleMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &Gilrs, - ) -> Option> { + ) -> Option { let active_id = self.active_id.get_or_insert(primary_id); // Handle toggle logic @@ -97,7 +97,10 @@ impl MuxMode for ToggleMode { }; let active = gilrs.gamepad(*active_id); - return Some(Self::sync_controller_state(active, *active_id, assist_id)); + let sync_events = Self::sync_controller_state(active, *active_id, assist_id); + + // NEW: Report the active controller change + return Some(MuxOutput::with_active(sync_events, vec![*active_id])); } // Only forward events from the active controller @@ -106,6 +109,11 @@ impl MuxMode for ToggleMode { } let active = gilrs.gamepad(*active_id); - Self::convert_event(event, active) + Self::convert_event(event, active).map(MuxOutput::events) + } + + /// NEW: Toggle always starts with primary active + fn initial_active_controllers(&self, primary_id: GamepadId, _assist_id: GamepadId) -> Vec { + vec![primary_id] } } diff --git a/src/mux/runtime.rs b/src/mux/runtime.rs index 5d9ce69..b06cebb 100644 --- a/src/mux/runtime.rs +++ b/src/mux/runtime.rs @@ -7,7 +7,7 @@ use evdev::{Device, EventType, InputEvent}; use gilrs::{GamepadId, Gilrs}; use log::{debug, error, info, warn}; use parking_lot::RwLock; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; @@ -18,19 +18,44 @@ const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); pub struct RuntimeSettings { pub mode: Arc>, pub rumble: Arc>, + /// NEW: Track currently active controllers for force feedback + pub active_controllers: Arc>>, } impl RuntimeSettings { - pub fn new(mode: MuxModeType, rumble: RumbleTarget) -> Self { + pub fn new(mode: MuxModeType, rumble: RumbleTarget, primary_id: GamepadId, assist_id: GamepadId) -> Self { + // Get initial active controllers from the mode + 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())), } } - pub fn update_mode(&self, new_mode: MuxModeType) { + pub fn update_mode(&self, new_mode: MuxModeType, primary_id: GamepadId, assist_id: GamepadId) { let mut mode = self.mode.write(); - *mode = new_mode; + *mode = new_mode.clone(); + + // Reset active controllers based on new mode's defaults + let mode_impl = crate::mux::modes::create_mux_mode(new_mode); + let defaults = mode_impl.initial_active_controllers(primary_id, assist_id); + + let mut active = self.active_controllers.write(); + active.clear(); + active.extend(defaults); + } + + pub fn set_active_controllers(&self, controllers: Vec) { + let mut active = self.active_controllers.write(); + active.clear(); + active.extend(controllers); + } + + pub fn is_controller_active(&self, controller_id: GamepadId) -> bool { + self.active_controllers.read().contains(&controller_id) } pub fn update_rumble(&self, new_rumble: RumbleTarget) { @@ -66,6 +91,7 @@ pub fn run_input_loop( "Switching mux mode from {:?} to {:?}", last_mode, current_mode ); + runtime_settings.update_mode(current_mode.clone(), p_id, a_id); mux_mode = crate::mux::modes::create_mux_mode(current_mode.clone()); last_mode = current_mode; } @@ -77,12 +103,20 @@ pub fn run_input_loop( if event.id != p_id && event.id != a_id { continue; } - if let Some(mut out_events) = mux_mode.handle_event(&event, p_id, a_id, &gilrs) - && !out_events.is_empty() - { - 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); + + if let Some(output) = mux_mode.handle_event(&event, p_id, a_id, &gilrs) { + // NEW: Update active controllers if requested by the mode + if let Some(new_active) = output.set_active_controllers { + runtime_settings.set_active_controllers(new_active); + } + + // Send events + 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); + } } } } @@ -101,7 +135,7 @@ pub fn run_ff_loop( let mut effect_manager = EffectManager::new(); // Current physical devices - let mut phys_devs = build_ff_targets(&all_resources, runtime_settings.get_rumble(), p_id, a_id); + let mut phys_devs = build_ff_targets(&all_resources, runtime_settings.get_rumble(), &runtime_settings, p_id, a_id); let mut last_rumble = runtime_settings.get_rumble(); info!("FF Thread started."); @@ -117,7 +151,7 @@ pub fn run_ff_loop( // Build new device set let mut new_phys_devs = - build_ff_targets(&all_resources, current_rumble.clone(), p_id, a_id); + build_ff_targets(&all_resources, current_rumble.clone(), &runtime_settings, p_id, a_id); // Synchronize all effects to new devices for dev in &mut new_phys_devs { @@ -140,7 +174,7 @@ pub fn run_ff_loop( } phys_devs = new_phys_devs; - last_rumble = current_rumble; + last_rumble = current_rumble.clone(); } // Process events @@ -153,6 +187,24 @@ pub fn run_ff_loop( } }; + // Always update phys_devs if RumbleTarget::Active, to track changes in active controllers + if current_rumble == RumbleTarget::Active { + let mut new_phys_devs = build_ff_targets(&all_resources, current_rumble.clone(), &runtime_settings, p_id, a_id); + // Synchronize all effects to new devices + 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 + ); + } + } + phys_devs = new_phys_devs; + } + for event in events { match event.destructure() { evdev::EventSummary::UInput(ev, evdev::UInputCode::UI_FF_UPLOAD, ..) => { @@ -267,9 +319,11 @@ pub fn run_ff_loop( } // Helper function to build FF targets based on rumble setting +// UPDATED: Now handles Active variant using runtime_settings fn build_ff_targets( all_resources: &HashMap, rumble: RumbleTarget, + runtime_settings: &Arc, p_id: GamepadId, a_id: GamepadId, ) -> Vec { @@ -277,6 +331,10 @@ fn build_ff_targets( RumbleTarget::Primary => vec![p_id], RumbleTarget::Assist => vec![a_id], RumbleTarget::Both => vec![p_id, a_id], + RumbleTarget::Active => { + // NEW: Use the tracked active controllers + runtime_settings.active_controllers.read().iter().copied().collect() + } RumbleTarget::None => vec![], }; diff --git a/src/tray/app.rs b/src/tray/app.rs index dadaad3..605c2e5 100644 --- a/src/tray/app.rs +++ b/src/tray/app.rs @@ -520,7 +520,11 @@ impl Tray for CtrlAssistTray { enabled: true, access: { mux.mode }, on_change: |_t, s, v| { - if let Some(r) = &s.mux.runtime_settings { r.update_mode(v.clone()); } + if let Some(r) = &s.mux.runtime_settings { + let primary_id = s.mux.selected_primary.unwrap(); + let assist_id = s.mux.selected_assist.unwrap(); + r.update_mode(v.clone(), primary_id, assist_id); + } } ), enum_menu!( @@ -548,7 +552,7 @@ impl Tray for CtrlAssistTray { icon: "notification-active", current: state.mux.rumble, type: RumbleTarget, - variants: [Both, Primary, Assist, None], + variants: [Both, Primary, Assist, Active, None], enabled: true, access: { mux.rumble }, on_change: |_t, s, v| { From 1f0c6e1671609b73ed4bb543049b4119a46e61d1 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 18:33:25 -0600 Subject: [PATCH 02/28] refactor: enhance RuntimeSettings to track FF generation and active controllers --- src/mux/runtime.rs | 80 +++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/mux/runtime.rs b/src/mux/runtime.rs index b06cebb..953d79c 100644 --- a/src/mux/runtime.rs +++ b/src/mux/runtime.rs @@ -9,7 +9,7 @@ use log::{debug, error, info, warn}; use parking_lot::RwLock; use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::time::Duration; const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); @@ -18,8 +18,11 @@ const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); pub struct RuntimeSettings { pub mode: Arc>, pub rumble: Arc>, - /// NEW: Track currently active controllers for force feedback - pub active_controllers: Arc>>, + /// Track currently active controllers for force feedback + active_controllers: Arc>>, + /// Generation counter incremented when FF targets need rebuilding + /// This covers both rumble target changes AND active controller changes + ff_generation: Arc, } impl RuntimeSettings { @@ -32,6 +35,7 @@ impl RuntimeSettings { 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)), } } @@ -46,21 +50,26 @@ impl RuntimeSettings { let mut active = self.active_controllers.write(); active.clear(); active.extend(defaults); + + // Increment generation to trigger FF rebuild + self.ff_generation.fetch_add(1, Ordering::Release); } pub fn set_active_controllers(&self, controllers: Vec) { let mut active = self.active_controllers.write(); active.clear(); active.extend(controllers); - } - - pub fn is_controller_active(&self, controller_id: GamepadId) -> bool { - self.active_controllers.read().contains(&controller_id) + + // Increment generation to trigger FF rebuild + self.ff_generation.fetch_add(1, Ordering::Release); } pub fn update_rumble(&self, new_rumble: RumbleTarget) { let mut rumble = self.rumble.write(); *rumble = new_rumble; + + // Increment generation to trigger FF rebuild + self.ff_generation.fetch_add(1, Ordering::Release); } pub fn get_mode(&self) -> MuxModeType { @@ -70,6 +79,16 @@ impl RuntimeSettings { pub fn get_rumble(&self) -> RumbleTarget { self.rumble.read().clone() } + + /// Get current FF generation for change detection + pub fn ff_generation(&self) -> u64 { + self.ff_generation.load(Ordering::Acquire) + } + + /// Get the current set of active controllers for FF routing + pub fn get_active_controllers(&self) -> Vec { + self.active_controllers.read().iter().copied().collect() + } } pub fn run_input_loop( @@ -135,23 +154,19 @@ pub fn run_ff_loop( let mut effect_manager = EffectManager::new(); // Current physical devices - let mut phys_devs = build_ff_targets(&all_resources, runtime_settings.get_rumble(), &runtime_settings, p_id, a_id); - let mut last_rumble = runtime_settings.get_rumble(); + 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) { - // Check for rumble target changes - let current_rumble = runtime_settings.get_rumble(); - if current_rumble != last_rumble { - info!( - "Switching rumble target from {:?} to {:?}", - last_rumble, current_rumble - ); + // Check if FF targets need rebuilding (catches rumble changes AND active controller changes) + let current_generation = runtime_settings.ff_generation(); + if current_generation != last_ff_generation { + info!("FF targets changed, rebuilding (generation {} -> {})", last_ff_generation, current_generation); // Build new device set - let mut new_phys_devs = - build_ff_targets(&all_resources, current_rumble.clone(), &runtime_settings, p_id, a_id); + let mut new_phys_devs = build_ff_targets(&all_resources, &runtime_settings, p_id, a_id); // Synchronize all effects to new devices for dev in &mut new_phys_devs { @@ -174,7 +189,7 @@ pub fn run_ff_loop( } phys_devs = new_phys_devs; - last_rumble = current_rumble.clone(); + last_ff_generation = current_generation; } // Process events @@ -187,24 +202,6 @@ pub fn run_ff_loop( } }; - // Always update phys_devs if RumbleTarget::Active, to track changes in active controllers - if current_rumble == RumbleTarget::Active { - let mut new_phys_devs = build_ff_targets(&all_resources, current_rumble.clone(), &runtime_settings, p_id, a_id); - // Synchronize all effects to new devices - 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 - ); - } - } - phys_devs = new_phys_devs; - } - for event in events { match event.destructure() { evdev::EventSummary::UInput(ev, evdev::UInputCode::UI_FF_UPLOAD, ..) => { @@ -319,22 +316,19 @@ pub fn run_ff_loop( } // Helper function to build FF targets based on rumble setting -// UPDATED: Now handles Active variant using runtime_settings fn build_ff_targets( all_resources: &HashMap, - rumble: RumbleTarget, runtime_settings: &Arc, p_id: GamepadId, a_id: GamepadId, ) -> Vec { + let rumble = runtime_settings.get_rumble(); + let rumble_ids = match rumble { RumbleTarget::Primary => vec![p_id], RumbleTarget::Assist => vec![a_id], RumbleTarget::Both => vec![p_id, a_id], - RumbleTarget::Active => { - // NEW: Use the tracked active controllers - runtime_settings.active_controllers.read().iter().copied().collect() - } + RumbleTarget::Active => runtime_settings.get_active_controllers(), RumbleTarget::None => vec![], }; From 5b3818822f41adc3badd4eccd66a8de6094cb431 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 18:34:44 -0600 Subject: [PATCH 03/28] style: format code --- src/mux/manager.rs | 4 ++-- src/mux/modes/average.rs | 4 ++-- src/mux/modes/mod.rs | 10 +++++++--- src/mux/modes/priority.rs | 4 ++-- src/mux/modes/toggle.rs | 10 +++++++--- src/mux/runtime.rs | 34 +++++++++++++++++++++------------- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/mux/manager.rs b/src/mux/manager.rs index afea356..f176f88 100644 --- a/src/mux/manager.rs +++ b/src/mux/manager.rs @@ -101,10 +101,10 @@ pub fn start_mux( // Create runtime settings let runtime_settings = Arc::new(RuntimeSettings::new( - config.mode, + config.mode, config.rumble, config.primary_id, - config.assist_id + config.assist_id, )); // Setup shutdown signal diff --git a/src/mux/modes/average.rs b/src/mux/modes/average.rs index 23d1189..d381e5a 100644 --- a/src/mux/modes/average.rs +++ b/src/mux/modes/average.rs @@ -1,5 +1,4 @@ use super::{MuxMode, MuxOutput, helpers}; -use evdev::InputEvent; use gilrs::{Button, Event, EventType, GamepadId, Gilrs}; #[derive(Default)] @@ -42,7 +41,8 @@ impl MuxMode for AverageMode { return None; } - helpers::create_button_key_event(btn, is_pressed).map(|e| MuxOutput::events(vec![e])) + helpers::create_button_key_event(btn, is_pressed) + .map(|e| MuxOutput::events(vec![e])) } EventType::ButtonChanged(btn, _, _) => { diff --git a/src/mux/modes/mod.rs b/src/mux/modes/mod.rs index b13231d..bf1f0a2 100644 --- a/src/mux/modes/mod.rs +++ b/src/mux/modes/mod.rs @@ -32,7 +32,7 @@ impl MuxOutput { set_active_controllers: None, } } - + /// Create output with events and active controller update pub fn with_active(events: Vec, active: Vec) -> Self { Self { @@ -51,10 +51,14 @@ pub trait MuxMode { assist_id: GamepadId, gilrs: &gilrs::Gilrs, ) -> Option; - + /// Return the initial set of active controllers for this mode. /// Used when the mode is first initialized or changes. - fn initial_active_controllers(&self, primary_id: GamepadId, assist_id: GamepadId) -> Vec { + fn initial_active_controllers( + &self, + primary_id: GamepadId, + assist_id: GamepadId, + ) -> Vec { vec![primary_id, assist_id] } } diff --git a/src/mux/modes/priority.rs b/src/mux/modes/priority.rs index d403c6f..c5d3568 100644 --- a/src/mux/modes/priority.rs +++ b/src/mux/modes/priority.rs @@ -1,5 +1,4 @@ use super::{MuxMode, MuxOutput, helpers}; -use evdev::InputEvent; use gilrs::{Button, Event, EventType, GamepadId, Gilrs}; #[derive(Default)] @@ -38,7 +37,8 @@ impl MuxMode for PriorityMode { return None; } - helpers::create_button_key_event(btn, is_pressed).map(|e| MuxOutput::events(vec![e])) + helpers::create_button_key_event(btn, is_pressed) + .map(|e| MuxOutput::events(vec![e])) } EventType::ButtonChanged(btn, _, _) => { diff --git a/src/mux/modes/toggle.rs b/src/mux/modes/toggle.rs index 0cbf766..e697d80 100644 --- a/src/mux/modes/toggle.rs +++ b/src/mux/modes/toggle.rs @@ -98,7 +98,7 @@ impl MuxMode for ToggleMode { let active = gilrs.gamepad(*active_id); let sync_events = Self::sync_controller_state(active, *active_id, assist_id); - + // NEW: Report the active controller change return Some(MuxOutput::with_active(sync_events, vec![*active_id])); } @@ -111,9 +111,13 @@ impl MuxMode for ToggleMode { let active = gilrs.gamepad(*active_id); Self::convert_event(event, active).map(MuxOutput::events) } - + /// NEW: Toggle always starts with primary active - fn initial_active_controllers(&self, primary_id: GamepadId, _assist_id: GamepadId) -> Vec { + fn initial_active_controllers( + &self, + primary_id: GamepadId, + _assist_id: GamepadId, + ) -> Vec { vec![primary_id] } } diff --git a/src/mux/runtime.rs b/src/mux/runtime.rs index 953d79c..ad73ccb 100644 --- a/src/mux/runtime.rs +++ b/src/mux/runtime.rs @@ -26,11 +26,16 @@ pub struct RuntimeSettings { } impl RuntimeSettings { - pub fn new(mode: MuxModeType, rumble: RumbleTarget, primary_id: GamepadId, assist_id: GamepadId) -> Self { + pub fn new( + mode: MuxModeType, + rumble: RumbleTarget, + primary_id: GamepadId, + assist_id: GamepadId, + ) -> Self { // Get initial active controllers from the mode 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)), @@ -42,15 +47,15 @@ impl RuntimeSettings { pub fn update_mode(&self, new_mode: MuxModeType, primary_id: GamepadId, assist_id: GamepadId) { let mut mode = self.mode.write(); *mode = new_mode.clone(); - + // Reset active controllers based on new mode's defaults let mode_impl = crate::mux::modes::create_mux_mode(new_mode); let defaults = mode_impl.initial_active_controllers(primary_id, assist_id); - + let mut active = self.active_controllers.write(); active.clear(); active.extend(defaults); - + // Increment generation to trigger FF rebuild self.ff_generation.fetch_add(1, Ordering::Release); } @@ -59,7 +64,7 @@ impl RuntimeSettings { let mut active = self.active_controllers.write(); active.clear(); active.extend(controllers); - + // Increment generation to trigger FF rebuild self.ff_generation.fetch_add(1, Ordering::Release); } @@ -67,7 +72,7 @@ impl RuntimeSettings { pub fn update_rumble(&self, new_rumble: RumbleTarget) { let mut rumble = self.rumble.write(); *rumble = new_rumble; - + // Increment generation to trigger FF rebuild self.ff_generation.fetch_add(1, Ordering::Release); } @@ -79,12 +84,12 @@ impl RuntimeSettings { pub fn get_rumble(&self) -> RumbleTarget { self.rumble.read().clone() } - + /// Get current FF generation for change detection pub fn ff_generation(&self) -> u64 { self.ff_generation.load(Ordering::Acquire) } - + /// Get the current set of active controllers for FF routing pub fn get_active_controllers(&self) -> Vec { self.active_controllers.read().iter().copied().collect() @@ -122,13 +127,13 @@ pub fn run_input_loop( if event.id != p_id && event.id != a_id { continue; } - + if let Some(output) = mux_mode.handle_event(&event, p_id, a_id, &gilrs) { // NEW: Update active controllers if requested by the mode if let Some(new_active) = output.set_active_controllers { runtime_settings.set_active_controllers(new_active); } - + // Send events if !output.events.is_empty() { let mut out_events = output.events; @@ -163,7 +168,10 @@ pub fn run_ff_loop( // Check if FF targets need rebuilding (catches rumble changes AND active controller changes) let current_generation = runtime_settings.ff_generation(); if current_generation != last_ff_generation { - info!("FF targets changed, rebuilding (generation {} -> {})", last_ff_generation, current_generation); + info!( + "FF targets changed, rebuilding (generation {} -> {})", + last_ff_generation, current_generation + ); // Build new device set let mut new_phys_devs = build_ff_targets(&all_resources, &runtime_settings, p_id, a_id); @@ -323,7 +331,7 @@ fn build_ff_targets( a_id: GamepadId, ) -> Vec { let rumble = runtime_settings.get_rumble(); - + let rumble_ids = match rumble { RumbleTarget::Primary => vec![p_id], RumbleTarget::Assist => vec![a_id], From 425a8b28c6c6a85b997c6f4ddccba5ea27a7f95e Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 18:51:45 -0600 Subject: [PATCH 04/28] style: rename and move RumbleTarget enums --- src/demux/manager.rs | 3 ++- src/demux/mod.rs | 10 ++++++++++ src/demux/runtime.rs | 11 +---------- src/main.rs | 17 ++++------------- src/mux/manager.rs | 4 ++-- src/mux/mod.rs | 13 +++++++++++++ src/mux/runtime.rs | 20 ++++++++++---------- src/tray/app.rs | 6 ++++-- src/tray/config.rs | 6 ++++-- src/tray/state.rs | 6 ++++-- 10 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/demux/manager.rs b/src/demux/manager.rs index 99aa1bf..cf63a75 100644 --- a/src/demux/manager.rs +++ b/src/demux/manager.rs @@ -1,8 +1,9 @@ +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::{DemuxRumbleTarget, HideType, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use evdev::Device; use gilrs::{GamepadId, Gilrs}; use log::info; diff --git a/src/demux/mod.rs b/src/demux/mod.rs index f880e97..9c29fcf 100644 --- a/src/demux/mod.rs +++ b/src/demux/mod.rs @@ -1,3 +1,13 @@ pub mod manager; pub mod modes; pub mod runtime; + +/// Rumble target for demux +#[derive( + clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, +)] +pub enum DemuxRumbleTarget { + #[default] + Active, + None, +} diff --git a/src/demux/runtime.rs b/src/demux/runtime.rs index aa4ab8b..743df3b 100644 --- a/src/demux/runtime.rs +++ b/src/demux/runtime.rs @@ -1,3 +1,4 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::modes::DemuxModeType; use crate::utils::ff::{EffectManager, PhysicalFFDev}; use crate::utils::gilrs::GamepadResource; @@ -12,16 +13,6 @@ use std::time::Duration; const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); -/// Rumble target for demux -#[derive( - clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, -)] -pub enum DemuxRumbleTarget { - #[default] - Active, - None, -} - /// Runtime-updatable demux settings pub struct DemuxRuntimeSettings { pub mode: Arc>, diff --git a/src/main.rs b/src/main.rs index c2d6f67..f16bc9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ +use crate::demux::DemuxRumbleTarget; +use crate::mux::MuxRumbleTarget; use clap::{Parser, Subcommand, ValueEnum}; -use demux::runtime::DemuxRumbleTarget; use log::info; use serde::{Deserialize, Serialize}; use std::error::Error; @@ -55,8 +56,8 @@ struct MuxArgs { mode: crate::mux::modes::MuxModeType, /// Rumble target for virtual device. - #[arg(long, value_enum, default_value_t = RumbleTarget::default())] - rumble: RumbleTarget, + #[arg(long, value_enum, default_value_t = MuxRumbleTarget::default())] + rumble: MuxRumbleTarget, } #[derive(clap::Args, Debug)] @@ -96,20 +97,10 @@ pub enum HideType { #[derive(ValueEnum, Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub enum SpoofTarget { - Primary, Assist, #[default] None, -} - -#[derive(ValueEnum, Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub enum RumbleTarget { Primary, - Assist, - #[default] - Both, - Active, - None, } #[tokio::main] diff --git a/src/mux/manager.rs b/src/mux/manager.rs index f176f88..952a698 100644 --- a/src/mux/manager.rs +++ b/src/mux/manager.rs @@ -2,7 +2,7 @@ use crate::mux::modes::MuxModeType; use crate::mux::runtime::RuntimeSettings; use crate::utils::evdev::VirtualGamepadInfo; use crate::utils::hide::ScopedDeviceHider; -use crate::{HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, MuxRumbleTarget, SpoofTarget}; use evdev::Device; use gilrs::{GamepadId, Gilrs}; use log::info; @@ -19,7 +19,7 @@ pub struct MuxConfig { pub mode: MuxModeType, pub hide: HideType, pub spoof: SpoofTarget, - pub rumble: RumbleTarget, + pub rumble: MuxRumbleTarget, } /// Handle to a running mux session diff --git a/src/mux/mod.rs b/src/mux/mod.rs index f880e97..da0590f 100644 --- a/src/mux/mod.rs +++ b/src/mux/mod.rs @@ -1,3 +1,16 @@ pub mod manager; pub mod modes; pub mod runtime; + +/// Rumble target for mux +#[derive( + clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, +)] +pub enum MuxRumbleTarget { + Active, + Assist, + #[default] + Both, + None, + Primary, +} diff --git a/src/mux/runtime.rs b/src/mux/runtime.rs index ad73ccb..259c4bd 100644 --- a/src/mux/runtime.rs +++ b/src/mux/runtime.rs @@ -1,4 +1,4 @@ -use crate::RumbleTarget; +use crate::mux::MuxRumbleTarget; use crate::mux::modes::MuxModeType; use crate::utils::ff::{EffectManager, PhysicalFFDev}; use crate::utils::gilrs::GamepadResource; @@ -17,7 +17,7 @@ const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); /// Runtime-updatable mux settings pub struct RuntimeSettings { pub mode: Arc>, - pub rumble: Arc>, + pub rumble: Arc>, /// Track currently active controllers for force feedback active_controllers: Arc>>, /// Generation counter incremented when FF targets need rebuilding @@ -28,7 +28,7 @@ pub struct RuntimeSettings { impl RuntimeSettings { pub fn new( mode: MuxModeType, - rumble: RumbleTarget, + rumble: MuxRumbleTarget, primary_id: GamepadId, assist_id: GamepadId, ) -> Self { @@ -69,7 +69,7 @@ impl RuntimeSettings { self.ff_generation.fetch_add(1, Ordering::Release); } - pub fn update_rumble(&self, new_rumble: RumbleTarget) { + pub fn update_rumble(&self, new_rumble: MuxRumbleTarget) { let mut rumble = self.rumble.write(); *rumble = new_rumble; @@ -81,7 +81,7 @@ impl RuntimeSettings { self.mode.read().clone() } - pub fn get_rumble(&self) -> RumbleTarget { + pub fn get_rumble(&self) -> MuxRumbleTarget { self.rumble.read().clone() } @@ -333,11 +333,11 @@ fn build_ff_targets( let rumble = runtime_settings.get_rumble(); let rumble_ids = match rumble { - RumbleTarget::Primary => vec![p_id], - RumbleTarget::Assist => vec![a_id], - RumbleTarget::Both => vec![p_id, a_id], - RumbleTarget::Active => runtime_settings.get_active_controllers(), - RumbleTarget::None => vec![], + 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 diff --git a/src/tray/app.rs b/src/tray/app.rs index 605c2e5..56a8cf7 100644 --- a/src/tray/app.rs +++ b/src/tray/app.rs @@ -1,8 +1,10 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::manager::{DemuxConfig, DemuxHandle}; use crate::demux::modes::DemuxModeType; +use crate::mux::MuxRumbleTarget; use crate::mux::manager::{MuxConfig, MuxHandle}; use crate::mux::modes::MuxModeType; -use crate::{DemuxRumbleTarget, HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use gilrs::GamepadId; use ksni::{Category, MenuItem, Status, ToolTip, Tray, menu}; use log::{error, info}; @@ -551,7 +553,7 @@ impl Tray for CtrlAssistTray { label: "Rumble: {:?}", icon: "notification-active", current: state.mux.rumble, - type: RumbleTarget, + type: MuxRumbleTarget, variants: [Both, Primary, Assist, Active, None], enabled: true, access: { mux.rumble }, diff --git a/src/tray/config.rs b/src/tray/config.rs index 686694b..5255492 100644 --- a/src/tray/config.rs +++ b/src/tray/config.rs @@ -1,7 +1,9 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::modes::DemuxModeType; +use crate::mux::MuxRumbleTarget; use crate::mux::modes::MuxModeType; use crate::tray::state::OperationMode; -use crate::{DemuxRumbleTarget, HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use log::{info, warn}; use serde::{Deserialize, Serialize}; use std::error::Error; @@ -35,7 +37,7 @@ pub struct MuxConfig { pub spoof: SpoofTarget, /// Last used rumble target #[serde(default)] - pub rumble: RumbleTarget, + pub rumble: MuxRumbleTarget, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] diff --git a/src/tray/state.rs b/src/tray/state.rs index 51be75d..9310923 100644 --- a/src/tray/state.rs +++ b/src/tray/state.rs @@ -1,6 +1,8 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::modes::DemuxModeType; +use crate::mux::MuxRumbleTarget; use crate::mux::modes::MuxModeType; -use crate::{DemuxRumbleTarget, HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use gilrs::{GamepadId, Gilrs}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -67,7 +69,7 @@ pub struct MuxState { /// Current spoof target pub spoof: SpoofTarget, /// Current rumble target - pub rumble: RumbleTarget, + pub rumble: MuxRumbleTarget, /// Shared runtime settings for live updates pub runtime_settings: Option>, } From 74b416e5f1426200e7289249fbc143eb80517b33 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 18:53:38 -0600 Subject: [PATCH 05/28] style: add missing words to cSpell configuration --- .vscode/settings.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index b78607e..16d6c7b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "codegen", "ctrlassist", "deadzone", + "demux", "devnode", "devnodes", "devpath", @@ -15,6 +16,7 @@ "gamepads", "gilrs", "hidraw", + "ksni", "libc", "libudev", "metainfo", From 566142433537c3ddec6a3a5b317f4cab2035316a Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 18:54:53 -0600 Subject: [PATCH 06/28] chore: update gilrs to latest release --- Cargo.lock | 6 ++++-- Cargo.toml | 5 +---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2dab5a5..718a2e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -705,7 +705,8 @@ dependencies = [ [[package]] name = "gilrs" version = "0.11.1" -source = "git+https://gitlab.com/gilrs-project/gilrs.git?branch=maint#984c4337f090431c0ab8f59287de9b4266d14ab2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa85c2e35dc565c90511917897ea4eae16b77f2773d5223536f7b602536d462" dependencies = [ "fnv", "gilrs-core", @@ -717,7 +718,8 @@ dependencies = [ [[package]] name = "gilrs-core" version = "0.6.7" -source = "git+https://gitlab.com/gilrs-project/gilrs.git?branch=maint#984c4337f090431c0ab8f59287de9b4266d14ab2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23f2cc5144060a7f8d9e02d3fce5d06705376568256a509cdbc3c24d47e4f04" dependencies = [ "inotify", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index f806491..962c06d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,10 +35,7 @@ ctrlc = "3.5.1" dirs = "6.0.0" env_logger = "0.11.8" evdev = "0.13.2" -# gilrs = "0.11.1" -# use gilrs from local path -# gilrs = { path = "../gilrs/gilrs" } -gilrs = { git = "https://gitlab.com/gilrs-project/gilrs.git", branch = "maint" } +gilrs = "0.11.1" libc = "0.2.180" log = "0.4.29" udev = "0.9.3" From d6786b12ab58c9080c56abe78a045a24e843d5a0 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 18:56:43 -0600 Subject: [PATCH 07/28] chore: update ashpd dependency to version 0.12.1 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 718a2e2..59de668 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd" +checksum = "618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce" dependencies = [ "enumflags2", "futures-channel", diff --git a/Cargo.toml b/Cargo.toml index 962c06d..c027a41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ uuid = "1.19.0" tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } # New dependencies for tray -ashpd = "0.12.0" +ashpd = "0.12.1" futures = "0.3.31" futures-util = "0.3.31" ksni = "0.3.3" From b79e41723aca1d91924c3bee211e32574021eb2b Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 18:57:53 -0600 Subject: [PATCH 08/28] chore: update flatpak cargo-sources.json --- flatpak/cargo-sources.json | 249 ++++++++----------------------------- 1 file changed, 53 insertions(+), 196 deletions(-) diff --git a/flatpak/cargo-sources.json b/flatpak/cargo-sources.json index 5bb323f..8aaea76 100644 --- a/flatpak/cargo-sources.json +++ b/flatpak/cargo-sources.json @@ -80,14 +80,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ashpd/ashpd-0.12.0.crate", - "sha256": "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd", - "dest": "cargo/vendor/ashpd-0.12.0" + "url": "https://static.crates.io/crates/ashpd/ashpd-0.12.1.crate", + "sha256": "618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce", + "dest": "cargo/vendor/ashpd-0.12.1" }, { "type": "inline", - "contents": "{\"package\": \"da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd\", \"files\": {}}", - "dest": "cargo/vendor/ashpd-0.12.0", + "contents": "{\"package\": \"618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce\", \"files\": {}}", + "dest": "cargo/vendor/ashpd-0.12.1", "dest-filename": ".cargo-checksum.json" }, { @@ -366,27 +366,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap/clap-4.5.51.crate", - "sha256": "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5", - "dest": "cargo/vendor/clap-4.5.51" + "url": "https://static.crates.io/crates/clap/clap-4.5.54.crate", + "sha256": "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394", + "dest": "cargo/vendor/clap-4.5.54" }, { "type": "inline", - "contents": "{\"package\": \"4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5\", \"files\": {}}", - "dest": "cargo/vendor/clap-4.5.51", + "contents": "{\"package\": \"c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394\", \"files\": {}}", + "dest": "cargo/vendor/clap-4.5.54", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap_builder/clap_builder-4.5.51.crate", - "sha256": "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a", - "dest": "cargo/vendor/clap_builder-4.5.51" + "url": "https://static.crates.io/crates/clap_builder/clap_builder-4.5.54.crate", + "sha256": "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00", + "dest": "cargo/vendor/clap_builder-4.5.54" }, { "type": "inline", - "contents": "{\"package\": \"75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a\", \"files\": {}}", - "dest": "cargo/vendor/clap_builder-4.5.51", + "contents": "{\"package\": \"fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00\", \"files\": {}}", + "dest": "cargo/vendor/clap_builder-4.5.54", "dest-filename": ".cargo-checksum.json" }, { @@ -441,32 +441,6 @@ "dest": "cargo/vendor/concurrent-queue-2.5.0", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/core-foundation/core-foundation-0.10.1.crate", - "sha256": "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6", - "dest": "cargo/vendor/core-foundation-0.10.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6\", \"files\": {}}", - "dest": "cargo/vendor/core-foundation-0.10.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/core-foundation-sys/core-foundation-sys-0.8.7.crate", - "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b", - "dest": "cargo/vendor/core-foundation-sys-0.8.7" - }, - { - "type": "inline", - "contents": "{\"package\": \"773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b\", \"files\": {}}", - "dest": "cargo/vendor/core-foundation-sys-0.8.7", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -912,27 +886,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/gilrs/gilrs-0.11.0.crate", - "sha256": "bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f", - "dest": "cargo/vendor/gilrs-0.11.0" + "url": "https://static.crates.io/crates/gilrs/gilrs-0.11.1.crate", + "sha256": "3fa85c2e35dc565c90511917897ea4eae16b77f2773d5223536f7b602536d462", + "dest": "cargo/vendor/gilrs-0.11.1" }, { "type": "inline", - "contents": "{\"package\": \"bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f\", \"files\": {}}", - "dest": "cargo/vendor/gilrs-0.11.0", + "contents": "{\"package\": \"3fa85c2e35dc565c90511917897ea4eae16b77f2773d5223536f7b602536d462\", \"files\": {}}", + "dest": "cargo/vendor/gilrs-0.11.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/gilrs-core/gilrs-core-0.6.6.crate", - "sha256": "be11a71ac3564f6965839e2ed275bf4fcf5ce16d80d396e1dfdb7b2d80bd587e", - "dest": "cargo/vendor/gilrs-core-0.6.6" + "url": "https://static.crates.io/crates/gilrs-core/gilrs-core-0.6.7.crate", + "sha256": "d23f2cc5144060a7f8d9e02d3fce5d06705376568256a509cdbc3c24d47e4f04", + "dest": "cargo/vendor/gilrs-core-0.6.7" }, { "type": "inline", - "contents": "{\"package\": \"be11a71ac3564f6965839e2ed275bf4fcf5ce16d80d396e1dfdb7b2d80bd587e\", \"files\": {}}", - "dest": "cargo/vendor/gilrs-core-0.6.6", + "contents": "{\"package\": \"d23f2cc5144060a7f8d9e02d3fce5d06705376568256a509cdbc3c24d47e4f04\", \"files\": {}}", + "dest": "cargo/vendor/gilrs-core-0.6.7", "dest-filename": ".cargo-checksum.json" }, { @@ -1156,19 +1130,6 @@ "dest": "cargo/vendor/inotify-sys-0.1.5", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/io-kit-sys/io-kit-sys-0.4.1.crate", - "sha256": "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b", - "dest": "cargo/vendor/io-kit-sys-0.4.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b\", \"files\": {}}", - "dest": "cargo/vendor/io-kit-sys-0.4.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -1250,14 +1211,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/libc/libc-0.2.178.crate", - "sha256": "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091", - "dest": "cargo/vendor/libc-0.2.178" + "url": "https://static.crates.io/crates/libc/libc-0.2.180.crate", + "sha256": "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc", + "dest": "cargo/vendor/libc-0.2.180" }, { "type": "inline", - "contents": "{\"package\": \"37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091\", \"files\": {}}", - "dest": "cargo/vendor/libc-0.2.178", + "contents": "{\"package\": \"bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc\", \"files\": {}}", + "dest": "cargo/vendor/libc-0.2.180", "dest-filename": ".cargo-checksum.json" }, { @@ -1328,14 +1289,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/log/log-0.4.28.crate", - "sha256": "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432", - "dest": "cargo/vendor/log-0.4.28" + "url": "https://static.crates.io/crates/log/log-0.4.29.crate", + "sha256": "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897", + "dest": "cargo/vendor/log-0.4.29" }, { "type": "inline", - "contents": "{\"package\": \"34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432\", \"files\": {}}", - "dest": "cargo/vendor/log-0.4.28", + "contents": "{\"package\": \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\", \"files\": {}}", + "dest": "cargo/vendor/log-0.4.29", "dest-filename": ".cargo-checksum.json" }, { @@ -1351,19 +1312,6 @@ "dest": "cargo/vendor/mac-notification-sys-0.6.9", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/mach2/mach2-0.4.3.crate", - "sha256": "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44", - "dest": "cargo/vendor/mach2-0.4.3" - }, - { - "type": "inline", - "contents": "{\"package\": \"d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44\", \"files\": {}}", - "dest": "cargo/vendor/mach2-0.4.3", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -1507,6 +1455,19 @@ "dest": "cargo/vendor/objc2-foundation-0.3.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/objc2-io-kit/objc2-io-kit-0.3.2.crate", + "sha256": "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15", + "dest": "cargo/vendor/objc2-io-kit-0.3.2" + }, + { + "type": "inline", + "contents": "{\"package\": \"33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15\", \"files\": {}}", + "dest": "cargo/vendor/objc2-io-kit-0.3.2", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2303,14 +2264,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/toml/toml-0.9.10+spec-1.1.0.crate", - "sha256": "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48", - "dest": "cargo/vendor/toml-0.9.10+spec-1.1.0" + "url": "https://static.crates.io/crates/toml/toml-0.9.11+spec-1.1.0.crate", + "sha256": "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46", + "dest": "cargo/vendor/toml-0.9.11+spec-1.1.0" }, { "type": "inline", - "contents": "{\"package\": \"0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48\", \"files\": {}}", - "dest": "cargo/vendor/toml-0.9.10+spec-1.1.0", + "contents": "{\"package\": \"f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46\", \"files\": {}}", + "dest": "cargo/vendor/toml-0.9.11+spec-1.1.0", "dest-filename": ".cargo-checksum.json" }, { @@ -2651,19 +2612,6 @@ "dest": "cargo/vendor/windows-0.61.3", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows/windows-0.62.2.crate", - "sha256": "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580", - "dest": "cargo/vendor/windows-0.62.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580\", \"files\": {}}", - "dest": "cargo/vendor/windows-0.62.2", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2677,19 +2625,6 @@ "dest": "cargo/vendor/windows-collections-0.2.0", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-collections/windows-collections-0.3.2.crate", - "sha256": "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610", - "dest": "cargo/vendor/windows-collections-0.3.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610\", \"files\": {}}", - "dest": "cargo/vendor/windows-collections-0.3.2", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2703,19 +2638,6 @@ "dest": "cargo/vendor/windows-core-0.61.2", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-core/windows-core-0.62.2.crate", - "sha256": "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb", - "dest": "cargo/vendor/windows-core-0.62.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb\", \"files\": {}}", - "dest": "cargo/vendor/windows-core-0.62.2", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2729,19 +2651,6 @@ "dest": "cargo/vendor/windows-future-0.2.1", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-future/windows-future-0.3.2.crate", - "sha256": "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb", - "dest": "cargo/vendor/windows-future-0.3.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb\", \"files\": {}}", - "dest": "cargo/vendor/windows-future-0.3.2", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2807,19 +2716,6 @@ "dest": "cargo/vendor/windows-numerics-0.2.0", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-numerics/windows-numerics-0.3.1.crate", - "sha256": "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26", - "dest": "cargo/vendor/windows-numerics-0.3.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26\", \"files\": {}}", - "dest": "cargo/vendor/windows-numerics-0.3.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2833,19 +2729,6 @@ "dest": "cargo/vendor/windows-result-0.3.4", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-result/windows-result-0.4.1.crate", - "sha256": "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5", - "dest": "cargo/vendor/windows-result-0.4.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5\", \"files\": {}}", - "dest": "cargo/vendor/windows-result-0.4.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2859,19 +2742,6 @@ "dest": "cargo/vendor/windows-strings-0.4.2", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-strings/windows-strings-0.5.1.crate", - "sha256": "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091", - "dest": "cargo/vendor/windows-strings-0.5.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091\", \"files\": {}}", - "dest": "cargo/vendor/windows-strings-0.5.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2950,19 +2820,6 @@ "dest": "cargo/vendor/windows-threading-0.1.0", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-threading/windows-threading-0.2.1.crate", - "sha256": "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37", - "dest": "cargo/vendor/windows-threading-0.2.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37\", \"files\": {}}", - "dest": "cargo/vendor/windows-threading-0.2.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", From 614005d067b03aa8bdc3fd4c74d36bd6dddd8386 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 19:01:01 -0600 Subject: [PATCH 09/28] feat: update default for MuxRumbleTarget to Active only toggle should be effected --- src/mux/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mux/mod.rs b/src/mux/mod.rs index da0590f..08acd72 100644 --- a/src/mux/mod.rs +++ b/src/mux/mod.rs @@ -7,9 +7,9 @@ pub mod runtime; clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, )] pub enum MuxRumbleTarget { + #[default] Active, Assist, - #[default] Both, None, Primary, From 83d7623f53ed0c6d8ba873f8d5b1b9e7cad86f34 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 19:06:42 -0600 Subject: [PATCH 10/28] docs: add example for dynamically targeting active controllers in README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4752807..2eae2e3 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,12 @@ Target force feedback to either, none, or both physical controllers: $ ctrlassist mux --rumble both ``` +Some modes also support dynamically targeting active controllers: + +```sh +$ ctrlassist mux --mode toggle --rumble active +``` + ### 🙈 Hide Physical Devices Multiple hiding strategies are available to avoid input conflicts: From aab645805e4485cff9157c058c263b02c62df9be Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 20:26:56 -0600 Subject: [PATCH 11/28] feat: log virtual device to stdout to make it simpler to find by default --- src/demux/manager.rs | 6 ++++-- src/mux/manager.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/demux/manager.rs b/src/demux/manager.rs index cf63a75..662a269 100644 --- a/src/demux/manager.rs +++ b/src/demux/manager.rs @@ -102,11 +102,13 @@ pub fn start_demux( 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)?; - info!( - "Virtual: {} @ {}", + 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(), diff --git a/src/mux/manager.rs b/src/mux/manager.rs index 952a698..dcd2bfb 100644 --- a/src/mux/manager.rs +++ b/src/mux/manager.rs @@ -93,11 +93,13 @@ pub fn start_mux( let v_resource = crate::utils::gilrs::wait_for_virtual_device(&mut v_uinput)?; let virtual_device_path = v_resource.path.clone(); - info!( - "Virtual: {} @ {}", + let virtual_msg = format!( + "Virtual: (#) {} @ {}", v_resource.name, v_resource.path.display() ); + info!("{}", virtual_msg); + println!("{}", virtual_msg); // Create runtime settings let runtime_settings = Arc::new(RuntimeSettings::new( From a1597b6ee961791a917840599af63c3f9ded2239 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 20:27:37 -0600 Subject: [PATCH 12/28] docs: add cookbook section and examples to README --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2eae2e3..5f76f64 100644 --- a/README.md +++ b/README.md @@ -413,6 +413,8 @@ CtrlAssist was first created out of personal necessity. After migrating househol Following its initial release and personal household success, as well as the broader trend of Linux adoption, CtrlAssist evolved from a simple CLI tool into a desktop-friendly utility. This category of accessibility features has significantly enhanced family gaming time, transforming passive spectators into active participants. From helping grandparents experience new immersive and interactive single player stories, to leveling age gaps across nieces and nephews in multiplayer PvPs, to rescuing friends from critical damage and finally overcoming a challenging boss, assistive players may expect as much enjoyment as primary players. +
More QA + ### **What games are compatible?** CtrlAssist works with most Linux games that support standard gamepad input. Some games or launchers may require restarting after changing controller visibility or virtual device settings. Note that many games have no explicit setting for controller selection, thus the motivation for various hiding strategies to avoid input conflicts between physical and virtual devices. For best compatibility, use the appropriate hiding strategy as described above. @@ -437,12 +439,68 @@ Yes! For scenarios where multiple primary players would like assistance, such as Additionally, each instance can use different hiding strategies, spoofing options, and rumble targets to suit the needs of each player. Just be mindful that selected hiding strategies do not conflict between instances, causing one virtual device to be hidden by another instance. -### **How else can CtrlAssist be used?** +
+ +# 🧑‍🍳 Cookbook + +Other basic examples of how else CtrlAssist can be used include: -Examples include: - Dual wielding one for each hand, like split Nintendo Switch Joy-Cons - Combining a standard gamepad with an accessible Xbox Adaptive Controller -- Assist multiple Primary players using demux outputs as mux Assist inputs + +However, because running multiple instances possible, more complex setups can be achieved by chaining mux and demux commands together. + +## Couch Co-Op Swap + +Two players can take turns assisting each other using toggle mode: + +```sh +$ ctrlassist list +(0) PS4 Controller +(1) PS5 Controller +``` + +```bash +#!/usr/bin/env bash +trap 'kill 0' SIGINT +ctrlassist mux --primary 0 --assist 1 --mode toggle --hide steam & +ctrlassist mux --primary 1 --assist 0 --mode toggle --hide steam & +wait +``` + +```mermaid +flowchart LR + A[Assist 1
Controller] --> C[Mux
Toggle] + B[Assist 2
Controller] --> C + A --> D[Mux
Toggle] + B --> D + C --> E[Virtual 1
Gamepad] + D --> F[Virtual 2
Gamepad] +``` + +Or to specify a single assist controller, toggle once before starting the second matching mux. + +## Double Agent Tag Team + +Assist multiple Primary players using demux outputs as mux Assist inputs: + +```sh +$ ctrlassist list +(0) PS4 Controller +(1) PS5 Controller +(2) Xbox One Controller +``` + +```bash +#!/usr/bin/env bash +trap 'kill 0' SIGINT +ctrlassist demux --primary 2 --virtuals 2 --mode unicast \ + --hide steam --spoof primary & # spoof to not also hide virtual 1 & 2 +sleep 1 # wait to ensure virtual devices are discoverable +ctrlassist mux --primary 0 --assist 3 --mode priority --hide steam & +ctrlassist mux --primary 1 --assist 4 --mode priority --hide steam & +wait +``` ```mermaid flowchart LR @@ -457,6 +515,8 @@ flowchart LR F --> J[Virtual 2
Gamepad] ``` +Simply scale with number of Primary players by adjusting the `--virtuals` count. + # 📚 Background - [Controller Assist on Xbox and Windows](https://support.xbox.com/en-US/help/account-profile/accessibility/copilot) From 94c623d758ce7d260a8920f4cbc8b90aaa694102 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 21:33:03 -0600 Subject: [PATCH 13/28] feat: add fs2 dependency and implement file locking in modify_steam_config --- Cargo.lock | 11 +++++++++++ Cargo.toml | 4 +++- src/utils/hide.rs | 27 ++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59de668..daecad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,7 @@ dependencies = [ "dirs", "env_logger", "evdev", + "fs2", "futures", "futures-util", "gilrs", @@ -571,6 +572,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index c027a41..8be7c56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,12 +35,14 @@ ctrlc = "3.5.1" dirs = "6.0.0" env_logger = "0.11.8" evdev = "0.13.2" +fs2 = "0.4.3" gilrs = "0.11.1" libc = "0.2.180" log = "0.4.29" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } udev = "0.9.3" uuid = "1.19.0" -tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } + # New dependencies for tray ashpd = "0.12.1" diff --git a/src/utils/hide.rs b/src/utils/hide.rs index d8f7eca..ac16690 100644 --- a/src/utils/hide.rs +++ b/src/utils/hide.rs @@ -1,5 +1,6 @@ use crate::HideType; use crate::utils::gilrs::GamepadResource; +use fs2::FileExt; use std::collections::HashSet; use std::error::Error; use std::fs; @@ -126,7 +127,19 @@ fn modify_steam_config(path: &Path, modifier: F) -> Result<(), Box where F: FnOnce(&mut HashSet), { - let content = fs::read_to_string(path)?; + // Lock the config file for exclusive access + let mut file = fs::OpenOptions::new().read(true).write(true).open(path)?; + file.lock_exclusive()?; + + // Read the file contents after locking + let mut content = String::new(); + use std::io::Seek; + use std::io::SeekFrom; + { + use std::io::Read; + file.seek(SeekFrom::Start(0))?; + file.read_to_string(&mut content)?; + } let mut lines: Vec = content.lines().map(String::from).collect(); let (line_idx, current_val) = match lines @@ -197,11 +210,15 @@ where lines.insert(line_idx, new_line); } + // Write back atomically using a temp file, then rename, while holding the lock let tmp_path = path.with_extension("tmp"); - let mut file = fs::File::create(&tmp_path)?; - file.write_all(lines.join("\n").as_bytes())?; - file.sync_all()?; - fs::rename(tmp_path, path)?; + { + let mut tmp_file = fs::File::create(&tmp_path)?; + tmp_file.write_all(lines.join("\n").as_bytes())?; + tmp_file.sync_all()?; + } + fs::rename(&tmp_path, path)?; + file.unlock()?; Ok(()) } From f4f3ca768152a63da4b23974828e455868a4df4b Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 21:47:18 -0600 Subject: [PATCH 14/28] chore: add signal-hook dependency for improved signal handling --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + 2 files changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index daecad9..38104ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,6 +376,7 @@ dependencies = [ "notify-rust", "parking_lot", "serde", + "signal-hook", "tokio", "toml", "udev", @@ -1517,6 +1518,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.7" diff --git a/Cargo.toml b/Cargo.toml index 8be7c56..5f9aa42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ fs2 = "0.4.3" gilrs = "0.11.1" libc = "0.2.180" log = "0.4.29" +signal-hook = "0.4.1" tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } udev = "0.9.3" uuid = "1.19.0" From 796929d2fc059547334a358dc7c6f6a077abc076 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 21:57:42 -0600 Subject: [PATCH 15/28] feat: replace ctrlc with signal-hook for improved signal handling --- Cargo.lock | 14 -------------- Cargo.toml | 1 - src/main.rs | 26 ++++++++++++++++++-------- src/tray/mod.rs | 29 ++++++++++++++++++----------- 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38104ce..6a7d7d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,7 +362,6 @@ version = "0.3.0" dependencies = [ "ashpd", "clap", - "ctrlc", "dirs", "env_logger", "evdev", @@ -383,17 +382,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "ctrlc" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" -dependencies = [ - "dispatch2", - "nix 0.30.1", - "windows-sys 0.61.2", -] - [[package]] name = "deranged" version = "0.5.5" @@ -431,8 +419,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags", - "block2", - "libc", "objc2", ] diff --git a/Cargo.toml b/Cargo.toml index 5f9aa42..bd516fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ github = { repository = "ruffsl/CtrlAssist" } [dependencies] clap = { version = "4.5.54", features = ["derive"] } -ctrlc = "3.5.1" dirs = "6.0.0" env_logger = "0.11.8" evdev = "0.13.2" diff --git a/src/main.rs b/src/main.rs index f16bc9c..1b8217e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use crate::mux::MuxRumbleTarget; use clap::{Parser, Subcommand, ValueEnum}; use log::info; use serde::{Deserialize, Serialize}; +use signal_hook::consts::signal::{SIGINT, SIGTERM}; +use signal_hook::iterator::Signals; use std::error::Error; mod demux; @@ -194,10 +196,14 @@ fn run_mux(args: MuxArgs) -> Result<(), Box> { mux_handle.0.shutdown(); }); - ctrlc::set_handler(move || { - println!("\nShutting down..."); - let _ = shutdown_tx.send(()); - })?; + // Handle both SIGINT and SIGTERM + let mut signals = Signals::new([SIGINT, SIGTERM])?; + std::thread::spawn(move || { + if let Some(_sig) = signals.forever().next() { + println!("\nShutting down..."); + let _ = shutdown_tx.send(()); + } + }); info!("Mux Active. Press Ctrl+C to exit."); println!("Mux Active. Press Ctrl+C to exit."); @@ -247,10 +253,14 @@ fn run_demux(args: DemuxArgs) -> Result<(), Box> { demux_handle.0.shutdown(); }); - ctrlc::set_handler(move || { - println!("\nShutting down..."); - let _ = shutdown_tx.send(()); - })?; + // Handle both SIGINT and SIGTERM + let mut signals = Signals::new([SIGINT, SIGTERM])?; + std::thread::spawn(move || { + if let Some(_sig) = signals.forever().next() { + println!("\nShutting down..."); + let _ = shutdown_tx.send(()); + } + }); info!("Demux Active. Press Ctrl+C to exit."); println!("Demux Active. Press Ctrl+C to exit."); diff --git a/src/tray/mod.rs b/src/tray/mod.rs index f72b1fd..e806935 100644 --- a/src/tray/mod.rs +++ b/src/tray/mod.rs @@ -1,4 +1,9 @@ +use ashpd::is_sandboxed; use futures_util::TryFutureExt; +use ksni::TrayMethods; +use std::error::Error; +use tokio::signal::unix::{SignalKind, signal}; +use tokio::sync::watch; pub mod app; pub mod config; @@ -6,11 +11,6 @@ pub mod state; pub use app::CtrlAssistTray; -use ashpd::is_sandboxed; -use ksni::TrayMethods; -use std::error::Error; -use tokio::sync::watch; - pub async fn run_tray() -> Result<(), Box> { let (tray, mut shutdown_rx) = CtrlAssistTray::new()?; @@ -27,13 +27,20 @@ pub async fn run_tray() -> Result<(), Box> { .await? }; - // Create a separate shutdown channel for Ctrl+C + // Create a separate shutdown channel for Ctrl+C or SIGTERM let (ctrlc_tx, mut ctrlc_rx) = watch::channel(false); tokio::spawn(async move { - // Wait for Ctrl+C signal - if tokio::signal::ctrl_c().await.is_ok() { - let _ = ctrlc_tx.send(true); + // Wait for either SIGINT or SIGTERM + let mut sigint = signal(SignalKind::interrupt()).unwrap(); + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + tokio::select! { + _ = sigint.recv() => { + let _ = ctrlc_tx.send(true); + } + _ = sigterm.recv() => { + let _ = ctrlc_tx.send(true); + } } }); @@ -41,13 +48,13 @@ pub async fn run_tray() -> Result<(), Box> { println!("Configure and control the mux from your system tray"); println!("Press Ctrl+C to exit"); - // Wait for either shutdown signal or Ctrl+C + // Wait for either shutdown signal or Ctrl+C/SIGTERM tokio::select! { _ = shutdown_rx.changed() => { // Exit button clicked, tray handled shutdown } _ = ctrlc_rx.changed() => { - // Ctrl+C pressed, handle shutdown here + // Ctrl+C or SIGTERM, handle shutdown here handle.update(|tray: &mut CtrlAssistTray| { tray.shutdown(); }).await; From f6d0c22ce92e10e95507d97c53a5e612a64c8f4e Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 22:20:05 -0600 Subject: [PATCH 16/28] docs: remove redundant SIGINT trap from example scripts in README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5f76f64..a9ef3a4 100644 --- a/README.md +++ b/README.md @@ -462,7 +462,6 @@ $ ctrlassist list ```bash #!/usr/bin/env bash -trap 'kill 0' SIGINT ctrlassist mux --primary 0 --assist 1 --mode toggle --hide steam & ctrlassist mux --primary 1 --assist 0 --mode toggle --hide steam & wait @@ -493,7 +492,6 @@ $ ctrlassist list ```bash #!/usr/bin/env bash -trap 'kill 0' SIGINT ctrlassist demux --primary 2 --virtuals 2 --mode unicast \ --hide steam --spoof primary & # spoof to not also hide virtual 1 & 2 sleep 1 # wait to ensure virtual devices are discoverable From e3dbba9a0b15fb1cabef60224b0cf2137f4f867e Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 22:28:15 -0600 Subject: [PATCH 17/28] docs: add sleep command to ensure virtual devices are discoverable in mux/demux examples and avoid race conditions in discovering resources --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a9ef3a4..4f6f63e 100644 --- a/README.md +++ b/README.md @@ -463,6 +463,7 @@ $ ctrlassist list ```bash #!/usr/bin/env bash ctrlassist mux --primary 0 --assist 1 --mode toggle --hide steam & +sleep 1 # wait to ensure virtual devices are discoverable ctrlassist mux --primary 1 --assist 0 --mode toggle --hide steam & wait ``` @@ -496,6 +497,7 @@ ctrlassist demux --primary 2 --virtuals 2 --mode unicast \ --hide steam --spoof primary & # spoof to not also hide virtual 1 & 2 sleep 1 # wait to ensure virtual devices are discoverable ctrlassist mux --primary 0 --assist 3 --mode priority --hide steam & +sleep 1 # wait to ensure virtual devices are discoverable ctrlassist mux --primary 1 --assist 4 --mode priority --hide steam & wait ``` From 8be3581d2b2ec0bc7365d316ddff1e749b793161 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 22:29:01 -0600 Subject: [PATCH 18/28] chore: remove ctrlc dependency and add fs2 and signal-hook dependencies --- flatpak/cargo-sources.json | 39 +++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/flatpak/cargo-sources.json b/flatpak/cargo-sources.json index 8aaea76..4725604 100644 --- a/flatpak/cargo-sources.json +++ b/flatpak/cargo-sources.json @@ -454,19 +454,6 @@ "dest": "cargo/vendor/crossbeam-utils-0.8.21", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ctrlc/ctrlc-3.5.1.crate", - "sha256": "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790", - "dest": "cargo/vendor/ctrlc-3.5.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790\", \"files\": {}}", - "dest": "cargo/vendor/ctrlc-3.5.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -714,6 +701,19 @@ "dest": "cargo/vendor/form_urlencoded-1.2.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/fs2/fs2-0.4.3.crate", + "sha256": "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213", + "dest": "cargo/vendor/fs2-0.4.3" + }, + { + "type": "inline", + "contents": "{\"package\": \"9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213\", \"files\": {}}", + "dest": "cargo/vendor/fs2-0.4.3", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2014,6 +2014,19 @@ "dest": "cargo/vendor/shlex-1.3.0", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/signal-hook/signal-hook-0.4.1.crate", + "sha256": "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952", + "dest": "cargo/vendor/signal-hook-0.4.1" + }, + { + "type": "inline", + "contents": "{\"package\": \"2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952\", \"files\": {}}", + "dest": "cargo/vendor/signal-hook-0.4.1", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", From 177b38bc8c3abec62afa2684dd1d51b8322ab638 Mon Sep 17 00:00:00 2001 From: Ruffin Date: Mon, 12 Jan 2026 22:51:43 -0600 Subject: [PATCH 19/28] Update src/tray/app.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/tray/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tray/app.rs b/src/tray/app.rs index 56a8cf7..ee3e733 100644 --- a/src/tray/app.rs +++ b/src/tray/app.rs @@ -554,7 +554,7 @@ impl Tray for CtrlAssistTray { icon: "notification-active", current: state.mux.rumble, type: MuxRumbleTarget, - variants: [Both, Primary, Assist, Active, None], + variants: [Active, Both, Primary, Assist, None], enabled: true, access: { mux.rumble }, on_change: |_t, s, v| { From 8ab1f50a654993091c548c4635b5918bc9412798 Mon Sep 17 00:00:00 2001 From: Ruffin Date: Mon, 12 Jan 2026 22:51:52 -0600 Subject: [PATCH 20/28] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f6f63e..97ccada 100644 --- a/README.md +++ b/README.md @@ -448,7 +448,7 @@ Other basic examples of how else CtrlAssist can be used include: - Dual wielding one for each hand, like split Nintendo Switch Joy-Cons - Combining a standard gamepad with an accessible Xbox Adaptive Controller -However, because running multiple instances possible, more complex setups can be achieved by chaining mux and demux commands together. +However, because running multiple instances is possible, more complex setups can be achieved by chaining mux and demux commands together. ## Couch Co-Op Swap From 3e1b670cb9fad4b0b86b2b411f3fde2a204ee142 Mon Sep 17 00:00:00 2001 From: Ruffin Date: Mon, 12 Jan 2026 23:08:30 -0600 Subject: [PATCH 21/28] Update src/tray/mod.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/tray/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tray/mod.rs b/src/tray/mod.rs index e806935..cf40df9 100644 --- a/src/tray/mod.rs +++ b/src/tray/mod.rs @@ -32,8 +32,10 @@ pub async fn run_tray() -> Result<(), Box> { tokio::spawn(async move { // Wait for either SIGINT or SIGTERM - let mut sigint = signal(SignalKind::interrupt()).unwrap(); - let mut sigterm = signal(SignalKind::terminate()).unwrap(); + let mut sigint = signal(SignalKind::interrupt()) + .expect("Failed to register SIGINT handler"); + let mut sigterm = signal(SignalKind::terminate()) + .expect("Failed to register SIGTERM handler"); tokio::select! { _ = sigint.recv() => { let _ = ctrlc_tx.send(true); From 382425e895fa2d18571a09717a93f9429c589e12 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 23:24:12 -0600 Subject: [PATCH 22/28] style: format signal handler registration for readability --- src/tray/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tray/mod.rs b/src/tray/mod.rs index cf40df9..214fd1d 100644 --- a/src/tray/mod.rs +++ b/src/tray/mod.rs @@ -32,10 +32,10 @@ pub async fn run_tray() -> Result<(), Box> { tokio::spawn(async move { // Wait for either SIGINT or SIGTERM - let mut sigint = signal(SignalKind::interrupt()) - .expect("Failed to register SIGINT handler"); - let mut sigterm = signal(SignalKind::terminate()) - .expect("Failed to register SIGTERM handler"); + let mut sigint = + signal(SignalKind::interrupt()).expect("Failed to register SIGINT handler"); + let mut sigterm = + signal(SignalKind::terminate()).expect("Failed to register SIGTERM handler"); tokio::select! { _ = sigint.recv() => { let _ = ctrlc_tx.send(true); From b674321daf1935089a0992a31dbdf5b01c5f01da Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 23:36:12 -0600 Subject: [PATCH 23/28] refactor: avoid redundant mux mode instantiation in run_input_loop Refactored run_input_loop and RuntimeSettings::update_mode to eliminate double creation of mux mode instances when switching modes. Now, the mode instance is created once, used to obtain default active controllers, and passed to update_mode, improving efficiency and clarity. --- src/mux/runtime.rs | 17 +++++++++-------- src/tray/app.rs | 4 +++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/mux/runtime.rs b/src/mux/runtime.rs index 259c4bd..2bfc164 100644 --- a/src/mux/runtime.rs +++ b/src/mux/runtime.rs @@ -44,17 +44,14 @@ impl RuntimeSettings { } } - pub fn update_mode(&self, new_mode: MuxModeType, primary_id: GamepadId, assist_id: GamepadId) { + pub fn update_mode(&self, new_mode: MuxModeType, initial_active: Vec) { let mut mode = self.mode.write(); - *mode = new_mode.clone(); - - // Reset active controllers based on new mode's defaults - let mode_impl = crate::mux::modes::create_mux_mode(new_mode); - let defaults = mode_impl.initial_active_controllers(primary_id, assist_id); + *mode = new_mode; + // Update active controllers directly with the provided list let mut active = self.active_controllers.write(); active.clear(); - active.extend(defaults); + active.extend(initial_active); // Increment generation to trigger FF rebuild self.ff_generation.fetch_add(1, Ordering::Release); @@ -110,13 +107,17 @@ pub fn run_input_loop( while !shutdown.load(Ordering::SeqCst) { // Check for mode changes let current_mode = runtime_settings.get_mode(); + if current_mode != last_mode { info!( "Switching mux mode from {:?} to {:?}", last_mode, current_mode ); - runtime_settings.update_mode(current_mode.clone(), p_id, a_id); + 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; } diff --git a/src/tray/app.rs b/src/tray/app.rs index ee3e733..f380795 100644 --- a/src/tray/app.rs +++ b/src/tray/app.rs @@ -525,7 +525,9 @@ impl Tray for CtrlAssistTray { if let Some(r) = &s.mux.runtime_settings { let primary_id = s.mux.selected_primary.unwrap(); let assist_id = s.mux.selected_assist.unwrap(); - r.update_mode(v.clone(), primary_id, assist_id); + let mode_impl = crate::mux::modes::create_mux_mode(v.clone()); + let defaults = mode_impl.initial_active_controllers(primary_id, assist_id); + r.update_mode(v.clone(), defaults); } } ), From 18815bde0ebde2cce7c9da19d5bfc0cab046b51e Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 23:45:03 -0600 Subject: [PATCH 24/28] fix: handle missing primary or assist controller selection in mux mode update --- src/tray/app.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/tray/app.rs b/src/tray/app.rs index f380795..654b462 100644 --- a/src/tray/app.rs +++ b/src/tray/app.rs @@ -523,11 +523,18 @@ impl Tray for CtrlAssistTray { access: { mux.mode }, on_change: |_t, s, v| { if let Some(r) = &s.mux.runtime_settings { - let primary_id = s.mux.selected_primary.unwrap(); - let assist_id = s.mux.selected_assist.unwrap(); - let mode_impl = crate::mux::modes::create_mux_mode(v.clone()); - let defaults = mode_impl.initial_active_controllers(primary_id, assist_id); - r.update_mode(v.clone(), defaults); + if let (Some(primary_id), Some(assist_id)) = ( + s.mux.selected_primary.as_ref(), + s.mux.selected_assist.as_ref(), + ) { + let mode_impl = crate::mux::modes::create_mux_mode(v.clone()); + let defaults = mode_impl.initial_active_controllers(*primary_id, *assist_id); + r.update_mode(v.clone(), defaults); + } else { + error!( + "Cannot update mux mode: primary or assist controller not selected" + ); + } } } ), From cd65013520b64016018ce7aff68917c79a96ffa3 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Mon, 12 Jan 2026 23:54:30 -0600 Subject: [PATCH 25/28] docs: update README to clarify command usage and add demux option --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 97ccada..ef30747 100644 --- a/README.md +++ b/README.md @@ -129,15 +129,16 @@ Use the `--help` flag for information on each CLI subcommand: ```sh $ ctrlassist --help -Multiplex multiple controllers into virtual gamepad +Controller Assist for gaming on Linux Usage: ctrlassist Commands: - list List all detected controllers and respective IDs - mux Multiplex connected controllers into virtual gamepad - tray Launch system tray app for graphical control - help Print this message or the help of the given subcommand(s) + list List all detected controllers and respective IDs + mux Multiplex connected controllers into virtual gamepad + demux Demultiplex one controller to multiple virtual gamepads + tray Launch system tray app for graphical control + help Print this message or the help of the given subcommand(s) Options: -h, --help Print help From 50a32ed973e11ee5a102846e2d5583d1ae1db452 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Tue, 13 Jan 2026 00:18:06 -0600 Subject: [PATCH 26/28] chore: relax dependency versions for consistency and compatibility --- Cargo.lock | 221 ++++++++++++++-------- Cargo.toml | 38 ++-- flatpak/cargo-sources.json | 367 +++++++++++++++++++++++-------------- 3 files changed, 397 insertions(+), 229 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a7d7d8..3c55a18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" @@ -275,9 +275,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "shlex", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -540,9 +540,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "fnv" @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -731,7 +731,7 @@ dependencies = [ "vec_map", "wasm-bindgen", "web-sys", - "windows", + "windows 0.62.2", ] [[package]] @@ -868,9 +868,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -915,9 +915,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -928,9 +928,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", @@ -968,9 +968,9 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", @@ -1073,7 +1073,6 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", - "memoffset", ] [[package]] @@ -1260,9 +1259,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -1308,9 +1307,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -1326,9 +1325,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1367,9 +1366,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" dependencies = [ "getrandom 0.3.4", ] @@ -1389,7 +1388,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror", ] @@ -1516,10 +1515,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1551,12 +1551,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -1565,9 +1559,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1599,7 +1593,7 @@ checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ "quick-xml", "thiserror", - "windows", + "windows 0.61.3", "windows-version", ] @@ -1667,9 +1661,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -1806,14 +1800,15 @@ checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -1943,11 +1938,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -1956,7 +1963,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -1968,8 +1984,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -1978,9 +2007,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -2023,10 +2063,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -2036,6 +2086,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -2045,6 +2104,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2113,6 +2181,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-version" version = "0.1.7" @@ -2267,9 +2344,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b" dependencies = [ "async-broadcast", "async-executor", @@ -2285,8 +2362,9 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "libc", "ordered-stream", + "rustix", "serde", "serde_repr", "tokio", @@ -2302,9 +2380,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2317,30 +2395,29 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", "winnow", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -2403,9 +2480,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +checksum = "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543" dependencies = [ "endi", "enumflags2", @@ -2418,9 +2495,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +checksum = "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2431,9 +2508,9 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index bd516fa..435631d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,29 +30,29 @@ exclude = [ github = { repository = "ruffsl/CtrlAssist" } [dependencies] -clap = { version = "4.5.54", features = ["derive"] } -dirs = "6.0.0" -env_logger = "0.11.8" -evdev = "0.13.2" -fs2 = "0.4.3" -gilrs = "0.11.1" -libc = "0.2.180" -log = "0.4.29" -signal-hook = "0.4.1" +clap = { version = "^4.5", features = ["derive"] } +dirs = "6" +env_logger = "0.11" +evdev = "0.13" +fs2 = "0.4" +gilrs = "^0.11.1" +libc = "0.2" +log = "0.4" +signal-hook = "0.4" tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } -udev = "0.9.3" -uuid = "1.19.0" +udev = "0.9" +uuid = "1" # New dependencies for tray -ashpd = "0.12.1" -futures = "0.3.31" -futures-util = "0.3.31" -ksni = "0.3.3" -notify-rust = "4.11.7" -parking_lot = "0.12.5" -serde = "1.0.228" -toml = "0.9.11" +ashpd = "0.12" +futures = "0.3" +futures-util = "0.3" +ksni = "^0.3.3" +notify-rust = "4.11" +parking_lot = "0.12" +serde = "1" +toml = "0.9" [package.metadata.deb] depends = "libudev1" diff --git a/flatpak/cargo-sources.json b/flatpak/cargo-sources.json index 4725604..d3a22b7 100644 --- a/flatpak/cargo-sources.json +++ b/flatpak/cargo-sources.json @@ -301,14 +301,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/bumpalo/bumpalo-3.19.0.crate", - "sha256": "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43", - "dest": "cargo/vendor/bumpalo-3.19.0" + "url": "https://static.crates.io/crates/bumpalo/bumpalo-3.19.1.crate", + "sha256": "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510", + "dest": "cargo/vendor/bumpalo-3.19.1" }, { "type": "inline", - "contents": "{\"package\": \"46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43\", \"files\": {}}", - "dest": "cargo/vendor/bumpalo-3.19.0", + "contents": "{\"package\": \"5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510\", \"files\": {}}", + "dest": "cargo/vendor/bumpalo-3.19.1", "dest-filename": ".cargo-checksum.json" }, { @@ -327,14 +327,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/cc/cc-1.2.50.crate", - "sha256": "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c", - "dest": "cargo/vendor/cc-1.2.50" + "url": "https://static.crates.io/crates/cc/cc-1.2.52.crate", + "sha256": "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3", + "dest": "cargo/vendor/cc-1.2.52" }, { "type": "inline", - "contents": "{\"package\": \"9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c\", \"files\": {}}", - "dest": "cargo/vendor/cc-1.2.50", + "contents": "{\"package\": \"cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3\", \"files\": {}}", + "dest": "cargo/vendor/cc-1.2.52", "dest-filename": ".cargo-checksum.json" }, { @@ -405,14 +405,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap_lex/clap_lex-0.7.6.crate", - "sha256": "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d", - "dest": "cargo/vendor/clap_lex-0.7.6" + "url": "https://static.crates.io/crates/clap_lex/clap_lex-0.7.7.crate", + "sha256": "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32", + "dest": "cargo/vendor/clap_lex-0.7.7" }, { "type": "inline", - "contents": "{\"package\": \"a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d\", \"files\": {}}", - "dest": "cargo/vendor/clap_lex-0.7.6", + "contents": "{\"package\": \"c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32\", \"files\": {}}", + "dest": "cargo/vendor/clap_lex-0.7.7", "dest-filename": ".cargo-checksum.json" }, { @@ -665,14 +665,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.5.crate", - "sha256": "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844", - "dest": "cargo/vendor/find-msvc-tools-0.1.5" + "url": "https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.7.crate", + "sha256": "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41", + "dest": "cargo/vendor/find-msvc-tools-0.1.7" }, { "type": "inline", - "contents": "{\"package\": \"3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844\", \"files\": {}}", - "dest": "cargo/vendor/find-msvc-tools-0.1.5", + "contents": "{\"package\": \"f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41\", \"files\": {}}", + "dest": "cargo/vendor/find-msvc-tools-0.1.7", "dest-filename": ".cargo-checksum.json" }, { @@ -860,14 +860,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/getrandom/getrandom-0.2.16.crate", - "sha256": "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592", - "dest": "cargo/vendor/getrandom-0.2.16" + "url": "https://static.crates.io/crates/getrandom/getrandom-0.2.17.crate", + "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0", + "dest": "cargo/vendor/getrandom-0.2.17" }, { "type": "inline", - "contents": "{\"package\": \"335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592\", \"files\": {}}", - "dest": "cargo/vendor/getrandom-0.2.16", + "contents": "{\"package\": \"ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0\", \"files\": {}}", + "dest": "cargo/vendor/getrandom-0.2.17", "dest-filename": ".cargo-checksum.json" }, { @@ -1094,14 +1094,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/indexmap/indexmap-2.12.1.crate", - "sha256": "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2", - "dest": "cargo/vendor/indexmap-2.12.1" + "url": "https://static.crates.io/crates/indexmap/indexmap-2.13.0.crate", + "sha256": "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017", + "dest": "cargo/vendor/indexmap-2.13.0" }, { "type": "inline", - "contents": "{\"package\": \"0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2\", \"files\": {}}", - "dest": "cargo/vendor/indexmap-2.12.1", + "contents": "{\"package\": \"7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017\", \"files\": {}}", + "dest": "cargo/vendor/indexmap-2.13.0", "dest-filename": ".cargo-checksum.json" }, { @@ -1159,27 +1159,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/jiff/jiff-0.2.16.crate", - "sha256": "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35", - "dest": "cargo/vendor/jiff-0.2.16" + "url": "https://static.crates.io/crates/jiff/jiff-0.2.18.crate", + "sha256": "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50", + "dest": "cargo/vendor/jiff-0.2.18" }, { "type": "inline", - "contents": "{\"package\": \"49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35\", \"files\": {}}", - "dest": "cargo/vendor/jiff-0.2.16", + "contents": "{\"package\": \"e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50\", \"files\": {}}", + "dest": "cargo/vendor/jiff-0.2.18", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/jiff-static/jiff-static-0.2.16.crate", - "sha256": "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69", - "dest": "cargo/vendor/jiff-static-0.2.16" + "url": "https://static.crates.io/crates/jiff-static/jiff-static-0.2.18.crate", + "sha256": "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78", + "dest": "cargo/vendor/jiff-static-0.2.18" }, { "type": "inline", - "contents": "{\"package\": \"980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69\", \"files\": {}}", - "dest": "cargo/vendor/jiff-static-0.2.16", + "contents": "{\"package\": \"e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78\", \"files\": {}}", + "dest": "cargo/vendor/jiff-static-0.2.18", "dest-filename": ".cargo-checksum.json" }, { @@ -1224,14 +1224,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/libredox/libredox-0.1.11.crate", - "sha256": "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50", - "dest": "cargo/vendor/libredox-0.1.11" + "url": "https://static.crates.io/crates/libredox/libredox-0.1.12.crate", + "sha256": "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616", + "dest": "cargo/vendor/libredox-0.1.12" }, { "type": "inline", - "contents": "{\"package\": \"df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50\", \"files\": {}}", - "dest": "cargo/vendor/libredox-0.1.11", + "contents": "{\"package\": \"3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616\", \"files\": {}}", + "dest": "cargo/vendor/libredox-0.1.12", "dest-filename": ".cargo-checksum.json" }, { @@ -1653,14 +1653,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/portable-atomic/portable-atomic-1.11.1.crate", - "sha256": "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483", - "dest": "cargo/vendor/portable-atomic-1.11.1" + "url": "https://static.crates.io/crates/portable-atomic/portable-atomic-1.13.0.crate", + "sha256": "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950", + "dest": "cargo/vendor/portable-atomic-1.13.0" }, { "type": "inline", - "contents": "{\"package\": \"f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483\", \"files\": {}}", - "dest": "cargo/vendor/portable-atomic-1.11.1", + "contents": "{\"package\": \"f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950\", \"files\": {}}", + "dest": "cargo/vendor/portable-atomic-1.13.0", "dest-filename": ".cargo-checksum.json" }, { @@ -1731,14 +1731,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.103.crate", - "sha256": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8", - "dest": "cargo/vendor/proc-macro2-1.0.103" + "url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.105.crate", + "sha256": "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7", + "dest": "cargo/vendor/proc-macro2-1.0.105" }, { "type": "inline", - "contents": "{\"package\": \"5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8\", \"files\": {}}", - "dest": "cargo/vendor/proc-macro2-1.0.103", + "contents": "{\"package\": \"535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7\", \"files\": {}}", + "dest": "cargo/vendor/proc-macro2-1.0.105", "dest-filename": ".cargo-checksum.json" }, { @@ -1757,14 +1757,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/quote/quote-1.0.42.crate", - "sha256": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f", - "dest": "cargo/vendor/quote-1.0.42" + "url": "https://static.crates.io/crates/quote/quote-1.0.43.crate", + "sha256": "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a", + "dest": "cargo/vendor/quote-1.0.43" }, { "type": "inline", - "contents": "{\"package\": \"a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f\", \"files\": {}}", - "dest": "cargo/vendor/quote-1.0.42", + "contents": "{\"package\": \"dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a\", \"files\": {}}", + "dest": "cargo/vendor/quote-1.0.43", "dest-filename": ".cargo-checksum.json" }, { @@ -1822,14 +1822,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/rand_core/rand_core-0.9.3.crate", - "sha256": "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38", - "dest": "cargo/vendor/rand_core-0.9.3" + "url": "https://static.crates.io/crates/rand_core/rand_core-0.9.4.crate", + "sha256": "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa", + "dest": "cargo/vendor/rand_core-0.9.4" }, { "type": "inline", - "contents": "{\"package\": \"99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38\", \"files\": {}}", - "dest": "cargo/vendor/rand_core-0.9.3", + "contents": "{\"package\": \"4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa\", \"files\": {}}", + "dest": "cargo/vendor/rand_core-0.9.4", "dest-filename": ".cargo-checksum.json" }, { @@ -2030,14 +2030,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/signal-hook-registry/signal-hook-registry-1.4.7.crate", - "sha256": "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad", - "dest": "cargo/vendor/signal-hook-registry-1.4.7" + "url": "https://static.crates.io/crates/signal-hook-registry/signal-hook-registry-1.4.8.crate", + "sha256": "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b", + "dest": "cargo/vendor/signal-hook-registry-1.4.8" }, { "type": "inline", - "contents": "{\"package\": \"7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad\", \"files\": {}}", - "dest": "cargo/vendor/signal-hook-registry-1.4.7", + "contents": "{\"package\": \"c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b\", \"files\": {}}", + "dest": "cargo/vendor/signal-hook-registry-1.4.8", "dest-filename": ".cargo-checksum.json" }, { @@ -2092,19 +2092,6 @@ "dest": "cargo/vendor/stable_deref_trait-1.2.1", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/static_assertions/static_assertions-1.1.0.crate", - "sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f", - "dest": "cargo/vendor/static_assertions-1.1.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f\", \"files\": {}}", - "dest": "cargo/vendor/static_assertions-1.1.0", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2121,14 +2108,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/syn/syn-2.0.110.crate", - "sha256": "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea", - "dest": "cargo/vendor/syn-2.0.110" + "url": "https://static.crates.io/crates/syn/syn-2.0.114.crate", + "sha256": "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a", + "dest": "cargo/vendor/syn-2.0.114" }, { "type": "inline", - "contents": "{\"package\": \"a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea\", \"files\": {}}", - "dest": "cargo/vendor/syn-2.0.110", + "contents": "{\"package\": \"d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a\", \"files\": {}}", + "dest": "cargo/vendor/syn-2.0.114", "dest-filename": ".cargo-checksum.json" }, { @@ -2251,14 +2238,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/tokio/tokio-1.48.0.crate", - "sha256": "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408", - "dest": "cargo/vendor/tokio-1.48.0" + "url": "https://static.crates.io/crates/tokio/tokio-1.49.0.crate", + "sha256": "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86", + "dest": "cargo/vendor/tokio-1.49.0" }, { "type": "inline", - "contents": "{\"package\": \"ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408\", \"files\": {}}", - "dest": "cargo/vendor/tokio-1.48.0", + "contents": "{\"package\": \"72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86\", \"files\": {}}", + "dest": "cargo/vendor/tokio-1.49.0", "dest-filename": ".cargo-checksum.json" }, { @@ -2420,14 +2407,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/url/url-2.5.7.crate", - "sha256": "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b", - "dest": "cargo/vendor/url-2.5.7" + "url": "https://static.crates.io/crates/url/url-2.5.8.crate", + "sha256": "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed", + "dest": "cargo/vendor/url-2.5.8" }, { "type": "inline", - "contents": "{\"package\": \"08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b\", \"files\": {}}", - "dest": "cargo/vendor/url-2.5.7", + "contents": "{\"package\": \"ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed\", \"files\": {}}", + "dest": "cargo/vendor/url-2.5.8", "dest-filename": ".cargo-checksum.json" }, { @@ -2625,6 +2612,19 @@ "dest": "cargo/vendor/windows-0.61.3", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows/windows-0.62.2.crate", + "sha256": "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580", + "dest": "cargo/vendor/windows-0.62.2" + }, + { + "type": "inline", + "contents": "{\"package\": \"527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580\", \"files\": {}}", + "dest": "cargo/vendor/windows-0.62.2", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2638,6 +2638,19 @@ "dest": "cargo/vendor/windows-collections-0.2.0", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows-collections/windows-collections-0.3.2.crate", + "sha256": "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610", + "dest": "cargo/vendor/windows-collections-0.3.2" + }, + { + "type": "inline", + "contents": "{\"package\": \"23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610\", \"files\": {}}", + "dest": "cargo/vendor/windows-collections-0.3.2", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2651,6 +2664,19 @@ "dest": "cargo/vendor/windows-core-0.61.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows-core/windows-core-0.62.2.crate", + "sha256": "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb", + "dest": "cargo/vendor/windows-core-0.62.2" + }, + { + "type": "inline", + "contents": "{\"package\": \"b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb\", \"files\": {}}", + "dest": "cargo/vendor/windows-core-0.62.2", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2664,6 +2690,19 @@ "dest": "cargo/vendor/windows-future-0.2.1", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows-future/windows-future-0.3.2.crate", + "sha256": "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb", + "dest": "cargo/vendor/windows-future-0.3.2" + }, + { + "type": "inline", + "contents": "{\"package\": \"e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb\", \"files\": {}}", + "dest": "cargo/vendor/windows-future-0.3.2", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2729,6 +2768,19 @@ "dest": "cargo/vendor/windows-numerics-0.2.0", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows-numerics/windows-numerics-0.3.1.crate", + "sha256": "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26", + "dest": "cargo/vendor/windows-numerics-0.3.1" + }, + { + "type": "inline", + "contents": "{\"package\": \"6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26\", \"files\": {}}", + "dest": "cargo/vendor/windows-numerics-0.3.1", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2742,6 +2794,19 @@ "dest": "cargo/vendor/windows-result-0.3.4", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows-result/windows-result-0.4.1.crate", + "sha256": "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5", + "dest": "cargo/vendor/windows-result-0.4.1" + }, + { + "type": "inline", + "contents": "{\"package\": \"7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5\", \"files\": {}}", + "dest": "cargo/vendor/windows-result-0.4.1", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2755,6 +2820,19 @@ "dest": "cargo/vendor/windows-strings-0.4.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows-strings/windows-strings-0.5.1.crate", + "sha256": "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091", + "dest": "cargo/vendor/windows-strings-0.5.1" + }, + { + "type": "inline", + "contents": "{\"package\": \"7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091\", \"files\": {}}", + "dest": "cargo/vendor/windows-strings-0.5.1", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -2833,6 +2911,19 @@ "dest": "cargo/vendor/windows-threading-0.1.0", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/windows-threading/windows-threading-0.2.1.crate", + "sha256": "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37", + "dest": "cargo/vendor/windows-threading-0.2.1" + }, + { + "type": "inline", + "contents": "{\"package\": \"3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37\", \"files\": {}}", + "dest": "cargo/vendor/windows-threading-0.2.1", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -3122,66 +3213,66 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zbus/zbus-5.12.0.crate", - "sha256": "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91", - "dest": "cargo/vendor/zbus-5.12.0" + "url": "https://static.crates.io/crates/zbus/zbus-5.13.1.crate", + "sha256": "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b", + "dest": "cargo/vendor/zbus-5.13.1" }, { "type": "inline", - "contents": "{\"package\": \"b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91\", \"files\": {}}", - "dest": "cargo/vendor/zbus-5.12.0", + "contents": "{\"package\": \"17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b\", \"files\": {}}", + "dest": "cargo/vendor/zbus-5.13.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zbus_macros/zbus_macros-5.12.0.crate", - "sha256": "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314", - "dest": "cargo/vendor/zbus_macros-5.12.0" + "url": "https://static.crates.io/crates/zbus_macros/zbus_macros-5.13.1.crate", + "sha256": "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37", + "dest": "cargo/vendor/zbus_macros-5.13.1" }, { "type": "inline", - "contents": "{\"package\": \"1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314\", \"files\": {}}", - "dest": "cargo/vendor/zbus_macros-5.12.0", + "contents": "{\"package\": \"aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37\", \"files\": {}}", + "dest": "cargo/vendor/zbus_macros-5.13.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zbus_names/zbus_names-4.2.0.crate", - "sha256": "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97", - "dest": "cargo/vendor/zbus_names-4.2.0" + "url": "https://static.crates.io/crates/zbus_names/zbus_names-4.3.1.crate", + "sha256": "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f", + "dest": "cargo/vendor/zbus_names-4.3.1" }, { "type": "inline", - "contents": "{\"package\": \"7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97\", \"files\": {}}", - "dest": "cargo/vendor/zbus_names-4.2.0", + "contents": "{\"package\": \"ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f\", \"files\": {}}", + "dest": "cargo/vendor/zbus_names-4.3.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.31.crate", - "sha256": "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3", - "dest": "cargo/vendor/zerocopy-0.8.31" + "url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.33.crate", + "sha256": "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd", + "dest": "cargo/vendor/zerocopy-0.8.33" }, { "type": "inline", - "contents": "{\"package\": \"fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3\", \"files\": {}}", - "dest": "cargo/vendor/zerocopy-0.8.31", + "contents": "{\"package\": \"668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd\", \"files\": {}}", + "dest": "cargo/vendor/zerocopy-0.8.33", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.31.crate", - "sha256": "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a", - "dest": "cargo/vendor/zerocopy-derive-0.8.31" + "url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.33.crate", + "sha256": "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1", + "dest": "cargo/vendor/zerocopy-derive-0.8.33" }, { "type": "inline", - "contents": "{\"package\": \"d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a\", \"files\": {}}", - "dest": "cargo/vendor/zerocopy-derive-0.8.31", + "contents": "{\"package\": \"2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1\", \"files\": {}}", + "dest": "cargo/vendor/zerocopy-derive-0.8.33", "dest-filename": ".cargo-checksum.json" }, { @@ -3252,40 +3343,40 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zvariant/zvariant-5.8.0.crate", - "sha256": "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c", - "dest": "cargo/vendor/zvariant-5.8.0" + "url": "https://static.crates.io/crates/zvariant/zvariant-5.9.1.crate", + "sha256": "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543", + "dest": "cargo/vendor/zvariant-5.9.1" }, { "type": "inline", - "contents": "{\"package\": \"2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c\", \"files\": {}}", - "dest": "cargo/vendor/zvariant-5.8.0", + "contents": "{\"package\": \"326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543\", \"files\": {}}", + "dest": "cargo/vendor/zvariant-5.9.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zvariant_derive/zvariant_derive-5.8.0.crate", - "sha256": "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006", - "dest": "cargo/vendor/zvariant_derive-5.8.0" + "url": "https://static.crates.io/crates/zvariant_derive/zvariant_derive-5.9.1.crate", + "sha256": "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7", + "dest": "cargo/vendor/zvariant_derive-5.9.1" }, { "type": "inline", - "contents": "{\"package\": \"da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006\", \"files\": {}}", - "dest": "cargo/vendor/zvariant_derive-5.8.0", + "contents": "{\"package\": \"ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7\", \"files\": {}}", + "dest": "cargo/vendor/zvariant_derive-5.9.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zvariant_utils/zvariant_utils-3.2.1.crate", - "sha256": "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599", - "dest": "cargo/vendor/zvariant_utils-3.2.1" + "url": "https://static.crates.io/crates/zvariant_utils/zvariant_utils-3.3.0.crate", + "sha256": "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9", + "dest": "cargo/vendor/zvariant_utils-3.3.0" }, { "type": "inline", - "contents": "{\"package\": \"c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599\", \"files\": {}}", - "dest": "cargo/vendor/zvariant_utils-3.2.1", + "contents": "{\"package\": \"f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9\", \"files\": {}}", + "dest": "cargo/vendor/zvariant_utils-3.3.0", "dest-filename": ".cargo-checksum.json" }, { From 3718aa8d5fe7a38dfabf8eaf2fc9bc7fd665486a Mon Sep 17 00:00:00 2001 From: ruffsl Date: Tue, 13 Jan 2026 16:03:04 -0600 Subject: [PATCH 27/28] docs: remove redundant comments for clarity in demux and mux modules --- src/demux/mod.rs | 1 - src/mux/mod.rs | 1 - src/mux/modes/toggle.rs | 4 ++-- src/mux/runtime.rs | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/demux/mod.rs b/src/demux/mod.rs index 9c29fcf..a13b963 100644 --- a/src/demux/mod.rs +++ b/src/demux/mod.rs @@ -2,7 +2,6 @@ pub mod manager; pub mod modes; pub mod runtime; -/// Rumble target for demux #[derive( clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, )] diff --git a/src/mux/mod.rs b/src/mux/mod.rs index 08acd72..fe4c95f 100644 --- a/src/mux/mod.rs +++ b/src/mux/mod.rs @@ -2,7 +2,6 @@ pub mod manager; pub mod modes; pub mod runtime; -/// Rumble target for mux #[derive( clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, )] diff --git a/src/mux/modes/toggle.rs b/src/mux/modes/toggle.rs index e697d80..902418b 100644 --- a/src/mux/modes/toggle.rs +++ b/src/mux/modes/toggle.rs @@ -99,7 +99,7 @@ impl MuxMode for ToggleMode { let active = gilrs.gamepad(*active_id); let sync_events = Self::sync_controller_state(active, *active_id, assist_id); - // NEW: Report the active controller change + // Report the active controller change return Some(MuxOutput::with_active(sync_events, vec![*active_id])); } @@ -112,7 +112,7 @@ impl MuxMode for ToggleMode { Self::convert_event(event, active).map(MuxOutput::events) } - /// NEW: Toggle always starts with primary active + /// Toggle always starts with primary active fn initial_active_controllers( &self, primary_id: GamepadId, diff --git a/src/mux/runtime.rs b/src/mux/runtime.rs index 2bfc164..7b28d39 100644 --- a/src/mux/runtime.rs +++ b/src/mux/runtime.rs @@ -130,7 +130,7 @@ pub fn run_input_loop( } if let Some(output) = mux_mode.handle_event(&event, p_id, a_id, &gilrs) { - // NEW: Update active controllers if requested by the mode + // Update active controllers if requested by the mode if let Some(new_active) = output.set_active_controllers { runtime_settings.set_active_controllers(new_active); } From 58066ef38b73e9be13098ffe9b58de66e261151a Mon Sep 17 00:00:00 2001 From: ruffsl Date: Tue, 13 Jan 2026 16:07:57 -0600 Subject: [PATCH 28/28] docs: rewording --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef30747..02544ea 100644 --- a/README.md +++ b/README.md @@ -449,7 +449,7 @@ Other basic examples of how else CtrlAssist can be used include: - Dual wielding one for each hand, like split Nintendo Switch Joy-Cons - Combining a standard gamepad with an accessible Xbox Adaptive Controller -However, because running multiple instances is possible, more complex setups can be achieved by chaining mux and demux commands together. +However, because running multiple instances is possible, more complex setups can be achieved by chaining multiple mux and demux commands together. ## Couch Co-Op Swap @@ -479,7 +479,7 @@ flowchart LR D --> F[Virtual 2
Gamepad] ``` -Or to specify a single assist controller, toggle once before starting the second matching mux. +Or specify a single assist controller by toggling once before duplicating first mux. ## Double Agent Tag Team