mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Show each controller's mapping source and uuid in pad status
A mapping bug report needs to know whether gilrs found an SDL row for a pad and which id such a row would need. pad status now lists mapping_source (sdl, driver or none) and uuid (32 hex digits, the id an SDL row starts with). With splitscreen on, PadRouter holds the gilrs reader, so pad status said backend:false and listed no controllers, unless the reader was lent to P1. The router now leaves what it sees for the status of each game that has a control socket; that list is only built when one is open. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2a013743f2
commit
7ac3021dfb
3 files changed
+87
-20
No files matched your search
@@ -142,6 +142,7 @@ mod hardware {
|
||||
}
|
||||
pub(in crate::game) fn lend_output(&mut self, _: &mut Pads, _: PadId) {}
|
||||
pub(in crate::game) fn reclaim_output(&mut self, _: &mut Pads) {}
|
||||
pub(in crate::game) fn report_status(&self, _: &mut Pads, _: Option<PadId>) {}
|
||||
pub(in crate::game) fn connected(&self, _: PadId) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -34,6 +34,51 @@ pub(super) struct Devices {
|
||||
haptics_tried: bool,
|
||||
/// What it was last told to show.
|
||||
shown: Option<horizon_dualsense::device::State>,
|
||||
/// While `PadRouter` has the reader and isn't lending it: what `pad status` lists.
|
||||
routed: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
/// `pad status`'s line for a controller. `uuid` is the id an SDL mapping row starts with, so a
|
||||
/// report says which row a pad needs; `mapping_source` says whether one was found.
|
||||
fn controller_status(
|
||||
id: gilrs::GamepadId,
|
||||
pad: &gilrs::Gamepad,
|
||||
active: Option<gilrs::GamepadId>,
|
||||
) -> Value {
|
||||
let mapping_source = match pad.mapping_source() {
|
||||
gilrs::MappingSource::SdlMappings => "sdl",
|
||||
gilrs::MappingSource::Driver => "driver",
|
||||
gilrs::MappingSource::None => "none",
|
||||
};
|
||||
let uuid: String = pad.uuid().iter().map(|b| format!("{b:02x}")).collect();
|
||||
json!({
|
||||
"name": pad.name(),
|
||||
"vendor": pad.vendor_id(),
|
||||
"product": pad.product_id(),
|
||||
"mapping_source": mapping_source,
|
||||
"uuid": uuid,
|
||||
"active": active == Some(id),
|
||||
})
|
||||
}
|
||||
|
||||
impl Devices {
|
||||
/// The hardware part of `pad status`.
|
||||
fn status(&self) -> Value {
|
||||
let pads: Vec<Value> = match (&self.gilrs, &self.routed) {
|
||||
(Some(g), _) => g
|
||||
.gamepads()
|
||||
.map(|(id, p)| controller_status(id, &p, self.active))
|
||||
.collect(),
|
||||
(None, Some(routed)) => routed.clone(),
|
||||
(None, None) => Vec::new(),
|
||||
};
|
||||
json!({
|
||||
"backend": self.gilrs.is_some() || self.routed.is_some(),
|
||||
"controllers": pads,
|
||||
"dualsense_output": self.output.as_ref().and_then(|o| o.open()).map(|t| format!("{t:?}")),
|
||||
"trigger_effects": self.shown.map(|s| s.triggers),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn gilrs_button(button: Button) -> gilrs::Button {
|
||||
@@ -249,6 +294,7 @@ impl PadRouter {
|
||||
/// Routing ends: P1 gets the reader back, open as it was.
|
||||
pub(in crate::game) fn hand_back(&mut self, pads: &mut Pads) {
|
||||
self.started = false;
|
||||
pads.devices.routed = None;
|
||||
if let Some(gilrs) = self.gilrs.take() {
|
||||
pads.devices.gilrs = Some(gilrs);
|
||||
pads.devices.started = true;
|
||||
@@ -280,6 +326,16 @@ impl PadRouter {
|
||||
self.gilrs = pads.devices.gilrs.take();
|
||||
}
|
||||
|
||||
/// `pad status` is answered in P1's turn, which can't reach this reader: leave it what the
|
||||
/// reader sees. `owner`: the pad driving P1.
|
||||
pub(in crate::game) fn report_status(&self, pads: &mut Pads, owner: Option<PadId>) {
|
||||
pads.devices.routed = self.gilrs.as_ref().map(|g| {
|
||||
g.gamepads()
|
||||
.map(|(id, p)| controller_status(id, &p, owner))
|
||||
.collect()
|
||||
});
|
||||
}
|
||||
|
||||
/// The buttons pressed and the controllers gone or back since the last call, in order.
|
||||
pub(in crate::game) fn events(&mut self) -> Vec<PadEvent> {
|
||||
if !self.started {
|
||||
@@ -591,27 +647,8 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The hardware part of `pad status`.
|
||||
pub(super) fn pad_devices_status(&self) -> Value {
|
||||
let devices = &self.pad.devices;
|
||||
let pads: Vec<Value> = devices.gilrs.as_ref().map_or(Vec::new(), |g| {
|
||||
g.gamepads()
|
||||
.map(|(id, p)| {
|
||||
json!({
|
||||
"name": p.name(),
|
||||
"vendor": p.vendor_id(),
|
||||
"product": p.product_id(),
|
||||
"active": devices.active == Some(id),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
json!({
|
||||
"backend": devices.gilrs.is_some(),
|
||||
"controllers": pads,
|
||||
"dualsense_output": devices.output.as_ref().and_then(|o| o.open()).map(|t| format!("{t:?}")),
|
||||
"trigger_effects": devices.shown.map(|s| s.triggers),
|
||||
})
|
||||
self.pad.devices.status()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,6 +683,28 @@ mod one_reader_tests {
|
||||
assert!(pads.devices.started, "so P1 won't open a second");
|
||||
}
|
||||
|
||||
/// While the router has the reader, `pad status` still says controllers can be read.
|
||||
#[test]
|
||||
fn status_sees_the_routers_reader_until_it_comes_back() {
|
||||
let Some(gilrs) = reader() else {
|
||||
return;
|
||||
};
|
||||
let mut pads = Pads::default();
|
||||
pads.devices.gilrs = Some(gilrs);
|
||||
pads.devices.started = true;
|
||||
let mut router = PadRouter::default();
|
||||
let backend = |pads: &Pads| pads.devices.status()["backend"].as_bool().unwrap();
|
||||
|
||||
router.adopt(&mut pads);
|
||||
assert!(!backend(&pads), "nothing reported yet");
|
||||
router.report_status(&mut pads, None);
|
||||
assert!(backend(&pads), "the router's reader counts");
|
||||
|
||||
router.hand_back(&mut pads);
|
||||
assert!(pads.devices.routed.is_none(), "P1 reads its own again");
|
||||
assert!(backend(&pads));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_router_with_its_own_reader_leaves_a_lent_ones_owner_alone() {
|
||||
let (Some(own), Some(other)) = (reader(), reader()) else {
|
||||
|
||||
@@ -273,10 +273,17 @@ impl Split<'_> {
|
||||
self.host.pad.release_hardware();
|
||||
}
|
||||
let owner = self.p1_pad.owner;
|
||||
// Only something on the control socket can ask.
|
||||
if self.host.control.is_some() {
|
||||
self.pads.report_status(&mut self.host.pad, owner);
|
||||
}
|
||||
let state = self.pad_state(owner);
|
||||
self.host.pad.feed_from(owner, state);
|
||||
for i in 0..self.guests.len() {
|
||||
let pad = self.guests[i].pad;
|
||||
if self.guests[i].game.control.is_some() {
|
||||
self.pads.report_status(&mut self.guests[i].game.pad, pad);
|
||||
}
|
||||
let state = self.pad_state(pad);
|
||||
self.guests[i].game.pad.feed_from(pad, state);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user