mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Controllers: one pad reader for a session, so KDE's pointer emulation stays paused
Entering a world opened the split's gilrs and closed P1's. gilrs opens a pad twice at once, which inotify can merge into one event, and closes it twice apart. KWin counts those events to pause its emulation, so its count could reach zero while Horizon still held the pad, and the left stick also moved the mouse. The router now takes P1's reader and gives it back. Its reader also gets the extra SDL rows, which it was missing. Refs #9730 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
696d021699
commit
924c7015df
3 files changed
+80
-6
No files matched your search
@@ -113,6 +113,8 @@ mod hardware {
|
||||
#[derive(Default)]
|
||||
pub(in crate::game) struct PadRouter {}
|
||||
impl PadRouter {
|
||||
pub(in crate::game) fn adopt(&mut self, _: &mut Pads) {}
|
||||
pub(in crate::game) fn hand_back(&mut self, _: &mut Pads) {}
|
||||
pub(in crate::game) fn events(&mut self) -> Vec<super::PadEvent> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@ const EXTRA_SDL_MAPPINGS: &str = concat!(
|
||||
"03000000c82d00000b31000014010000,8BitDo Ultimate 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n",
|
||||
);
|
||||
|
||||
/// The one way to open gilrs: each reader needs the extra rows.
|
||||
fn gilrs_builder() -> gilrs::GilrsBuilder {
|
||||
gilrs::GilrsBuilder::new().add_mappings(EXTRA_SDL_MAPPINGS)
|
||||
}
|
||||
|
||||
impl Pads {
|
||||
/// Open gilrs on first use. A platform without it (or a Linux without
|
||||
/// udev) leaves controllers off; the game runs as before.
|
||||
@@ -75,10 +80,7 @@ impl Pads {
|
||||
return;
|
||||
}
|
||||
self.devices.started = true;
|
||||
self.devices.gilrs = match gilrs::GilrsBuilder::new()
|
||||
.add_mappings(EXTRA_SDL_MAPPINGS)
|
||||
.build()
|
||||
{
|
||||
self.devices.gilrs = match gilrs_builder().build() {
|
||||
Ok(g) => {
|
||||
for (_, pad) in g.gamepads() {
|
||||
log::info!("Controller connected: {}", pad.name());
|
||||
@@ -211,6 +213,28 @@ pub(in crate::game) struct PadRouter {
|
||||
}
|
||||
|
||||
impl PadRouter {
|
||||
/// Take over the reader P1 already has open, instead of opening a second and closing the
|
||||
/// first. A compositor can count who holds a pad to pause its own use of it (KWin's pointer
|
||||
/// emulation), and that count goes wrong when readers overlap: gilrs opens a pad twice at
|
||||
/// once, which inotify can merge into one event, and closes it twice apart (#9730).
|
||||
pub(in crate::game) fn adopt(&mut self, pads: &mut Pads) {
|
||||
if !self.started
|
||||
&& let Some(gilrs) = pads.devices.gilrs.take()
|
||||
{
|
||||
self.started = true;
|
||||
self.gilrs = Some(gilrs);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
if let Some(gilrs) = self.gilrs.take() {
|
||||
pads.devices.gilrs = Some(gilrs);
|
||||
pads.devices.started = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Solo play uses this reader's device IDs for feedback too: another pad cannot steal
|
||||
/// the active output while its input is being offered to join. Reclaimed after P1's turn.
|
||||
pub(in crate::game) fn lend_output(&mut self, pads: &mut Pads, id: PadId) {
|
||||
@@ -240,7 +264,7 @@ impl PadRouter {
|
||||
pub(in crate::game) fn events(&mut self) -> Vec<PadEvent> {
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
self.gilrs = gilrs::Gilrs::new().ok();
|
||||
self.gilrs = gilrs_builder().build().ok();
|
||||
}
|
||||
let Some(gilrs) = self.gilrs.as_mut() else {
|
||||
return Vec::new();
|
||||
@@ -562,3 +586,50 @@ impl Game<'_> {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod one_reader_tests {
|
||||
use super::*;
|
||||
|
||||
/// Whether a machine can open gilrs at all; a test without it has nothing to check.
|
||||
fn reader() -> Option<gilrs::Gilrs> {
|
||||
gilrs_builder().build().ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn routing_takes_p1s_reader_and_gives_it_back_without_closing_it() {
|
||||
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();
|
||||
|
||||
router.adopt(&mut pads);
|
||||
assert!(pads.devices.gilrs.is_none(), "P1 no longer holds it");
|
||||
assert!(router.gilrs.is_some(), "the router holds the same reader");
|
||||
assert!(router.started, "so the router won't open a second");
|
||||
|
||||
pads.release_hardware();
|
||||
router.hand_back(&mut pads);
|
||||
assert!(router.gilrs.is_none());
|
||||
assert!(pads.devices.gilrs.is_some(), "P1 holds it again");
|
||||
assert!(pads.devices.started, "so P1 won't open a second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_router_with_its_own_reader_leaves_a_lent_ones_owner_alone() {
|
||||
let (Some(own), Some(other)) = (reader(), reader()) else {
|
||||
return;
|
||||
};
|
||||
let mut pads = Pads::default();
|
||||
pads.devices.gilrs = Some(other);
|
||||
let mut router = PadRouter {
|
||||
gilrs: Some(own),
|
||||
started: true,
|
||||
};
|
||||
router.adopt(&mut pads);
|
||||
assert!(pads.devices.gilrs.is_some());
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ impl Split<'_> {
|
||||
self.fresh.clear();
|
||||
self.lost.clear();
|
||||
self.p1_pad = P1Pad::default();
|
||||
self.pads = PadRouter::default();
|
||||
self.pads.hand_back(&mut self.host.pad);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ impl Split<'_> {
|
||||
let offers_before = self.offers.len();
|
||||
let mut joins = Vec::new();
|
||||
let mut presses = Vec::new();
|
||||
self.pads.adopt(&mut self.host.pad);
|
||||
for event in self.pads.events() {
|
||||
match event {
|
||||
PadEvent::Press(id, button) => presses.push((id, button)),
|
||||
|
||||
Reference in new issue
Block a user