Skip to main content

pfx_input/
precedence.rs

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}