1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::device::{PadId, PadInfo};
4use crate::event::InputEvent;
5use crate::family::{MICROSOFT, VALVE};
6
7pub const STEAM_VIRTUAL_PAD: u16 = 0x11ff;
8pub const XBOX_360: u16 = 0x028e;
9pub const IGNORE_DEVICES: &str = "SDL_GAMECONTROLLER_IGNORE_DEVICES";
10
11#[derive(Clone, Debug, Default, PartialEq, Eq)]
12pub struct SteamPrecedence {
13 active: bool,
14 owned: BTreeSet<(u16, u16)>,
15 xinput: bool,
16 pads: BTreeMap<PadId, PadInfo>,
17 hidden: BTreeSet<PadId>,
18}
19
20impl SteamPrecedence {
21 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn from_env() -> Self {
26 Self::new().owned(&std::env::var(IGNORE_DEVICES).unwrap_or_default())
27 }
28
29 pub fn owned(mut self, list: &str) -> Self {
30 self.owned.extend(parse_devices(list));
31 self
32 }
33
34 pub fn xinput(mut self, on: bool) -> Self {
35 self.xinput = on;
36 self
37 }
38
39 pub fn is_active(&self) -> bool {
40 self.active
41 }
42
43 pub fn is_hidden(&self, pad: PadId) -> bool {
44 self.hidden.contains(&pad)
45 }
46
47 pub fn hidden(&self) -> impl Iterator<Item = PadId> + '_ {
48 self.hidden.iter().copied()
49 }
50
51 pub fn hides(&self, info: &PadInfo) -> bool {
52 if !self.active {
53 return false;
54 }
55 if info.resolves_actions {
56 return false;
57 }
58 if info.name.contains("Steam Virtual Gamepad") {
59 return true;
60 }
61 match (info.vendor, info.product) {
62 (Some(VALVE), Some(STEAM_VIRTUAL_PAD)) => true,
63 (Some(vendor), Some(product)) if self.owned.contains(&(vendor, product)) => true,
64 (Some(MICROSOFT), Some(XBOX_360)) | (None, _) | (_, None) => self.xinput,
65 _ => false,
66 }
67 }
68
69 pub fn set_active(&mut self, active: bool, events: &mut Vec<InputEvent>) {
70 if self.active == active {
71 return;
72 }
73 self.active = active;
74 for (pad, info) in &self.pads {
75 let hide = self.hides(info);
76 if hide && self.hidden.insert(*pad) {
77 events.push(InputEvent::PadDisconnected { pad: *pad });
78 } else if !hide && self.hidden.remove(pad) {
79 events.push(InputEvent::PadConnected {
80 pad: *pad,
81 info: info.clone(),
82 });
83 }
84 }
85 }
86
87 pub fn filter(
88 &mut self,
89 incoming: impl IntoIterator<Item = InputEvent>,
90 events: &mut Vec<InputEvent>,
91 ) {
92 for event in incoming {
93 match &event {
94 InputEvent::PadConnected { pad, info } => {
95 self.pads.insert(*pad, info.clone());
96 if self.hides(info) {
97 self.hidden.insert(*pad);
98 continue;
99 }
100 self.hidden.remove(pad);
101 }
102 InputEvent::PadDisconnected { pad } => {
103 self.pads.remove(pad);
104 if self.hidden.remove(pad) {
105 continue;
106 }
107 }
108 InputEvent::PadButton { pad, .. }
109 | InputEvent::PadAxis { pad, .. }
110 | InputEvent::PadAction { pad, .. }
111 | InputEvent::PadOrigins { pad, .. }
112 if self.hidden.contains(pad) =>
113 {
114 continue;
115 }
116 _ => {}
117 }
118 events.push(event);
119 }
120 }
121}
122
123pub fn parse_devices(list: &str) -> Vec<(u16, u16)> {
124 list.split(',')
125 .filter_map(|pair| {
126 let (vendor, product) = pair.trim().split_once('/')?;
127 Some((hex(vendor)?, hex(product)?))
128 })
129 .collect()
130}
131
132fn hex(text: &str) -> Option<u16> {
133 let text = text.trim();
134 let digits = text
135 .strip_prefix("0x")
136 .or_else(|| text.strip_prefix("0X"))
137 .unwrap_or(text);
138 u16::from_str_radix(digits, 16).ok()
139}