desktop: Add gamepad support using gilrs
This commit is contained in:
parent
d07b154898
commit
a03355458f
|
@ -2357,6 +2357,40 @@ dependencies = [
|
|||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gilrs"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b2e57a9cb946b5d04ae8638c5f554abb5a9f82c4c950fd5b1fee6d119592fb"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"gilrs-core",
|
||||
"log",
|
||||
"uuid",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gilrs-core"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0af1827b7dd2f36d740ae804c1b3ea0d64c12533fb61ff91883005143a0e8c5a"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"inotify",
|
||||
"io-kit-sys",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"libudev-sys",
|
||||
"log",
|
||||
"nix 0.27.1",
|
||||
"uuid",
|
||||
"vec_map",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
|
@ -2733,6 +2767,26 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.35.1"
|
||||
|
@ -2774,6 +2828,16 @@ dependencies = [
|
|||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-kit-sys"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"mach2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isahc"
|
||||
version = "1.7.2"
|
||||
|
@ -3040,6 +3104,16 @@ dependencies = [
|
|||
"threadpool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.15"
|
||||
|
@ -3498,6 +3572,17 @@ dependencies = [
|
|||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
|
@ -4467,6 +4552,7 @@ dependencies = [
|
|||
"futures",
|
||||
"futures-lite 2.2.0",
|
||||
"generational-arena",
|
||||
"gilrs",
|
||||
"image",
|
||||
"isahc",
|
||||
"macro_rules_attribute",
|
||||
|
@ -5900,6 +5986,12 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
@ -5912,6 +6004,12 @@ version = "0.2.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "8.3.1"
|
||||
|
|
|
@ -46,6 +46,7 @@ futures-lite = "2.2.0"
|
|||
async-io = "2.3.1"
|
||||
async-net = "2.0.0"
|
||||
async-channel = "2.2.0"
|
||||
gilrs = "0.10"
|
||||
|
||||
# Deliberately held back to match tracy client used by profiling crate
|
||||
tracing-tracy = { version = "=0.10.4", optional = true }
|
||||
|
|
|
@ -3,10 +3,11 @@ use crate::custom_event::RuffleEvent;
|
|||
use crate::gui::{GuiController, MENU_HEIGHT};
|
||||
use crate::player::{PlayerController, PlayerOptions};
|
||||
use crate::util::{
|
||||
get_screen_size, parse_url, pick_file, plot_stats_in_tracy, winit_to_ruffle_key_code,
|
||||
winit_to_ruffle_text_control,
|
||||
get_screen_size, gilrs_button_to_gamepad_button, parse_url, pick_file, plot_stats_in_tracy,
|
||||
winit_to_ruffle_key_code, winit_to_ruffle_text_control,
|
||||
};
|
||||
use anyhow::{Context, Error};
|
||||
use gilrs::{Event, EventType, Gilrs};
|
||||
use ruffle_core::{PlayerEvent, StageDisplayState};
|
||||
use ruffle_render::backend::ViewportDimensions;
|
||||
use std::cell::RefCell;
|
||||
|
@ -99,6 +100,12 @@ impl App {
|
|||
loaded = LoadingState::Loaded;
|
||||
}
|
||||
|
||||
let mut gilrs = Gilrs::new()
|
||||
.inspect_err(|err| {
|
||||
tracing::warn!("Gamepad support could not be initialized: {err}");
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Poll UI events.
|
||||
let event_loop = self.event_loop.take().expect("App already running");
|
||||
event_loop.run(move |event, elwt| {
|
||||
|
@ -449,6 +456,26 @@ impl App {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(Event { event, .. }) = gilrs.as_mut().and_then(|gilrs| gilrs.next_event()) {
|
||||
match event {
|
||||
EventType::ButtonPressed(button, _) => {
|
||||
if let Some(button) = gilrs_button_to_gamepad_button(button) {
|
||||
self.player
|
||||
.handle_event(PlayerEvent::GamepadButtonDown { button });
|
||||
check_redraw = true;
|
||||
}
|
||||
}
|
||||
EventType::ButtonReleased(button, _) => {
|
||||
if let Some(button) = gilrs_button_to_gamepad_button(button) {
|
||||
self.player
|
||||
.handle_event(PlayerEvent::GamepadButtonUp { button });
|
||||
check_redraw = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a redraw request.
|
||||
if check_redraw {
|
||||
let player = self.player.get();
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::RUFFLE_VERSION;
|
||||
use anyhow::Error;
|
||||
use clap::Parser;
|
||||
use anyhow::{anyhow, Error};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use ruffle_core::backend::navigator::{OpenURLMode, SocketMode};
|
||||
use ruffle_core::config::Letterbox;
|
||||
use ruffle_core::events::{GamepadButton, KeyCode};
|
||||
use ruffle_core::{LoadBehavior, PlayerRuntime, StageAlign, StageScaleMode};
|
||||
use ruffle_render::quality::StageQuality;
|
||||
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
|
||||
|
@ -140,12 +141,76 @@ pub struct Opt {
|
|||
/// Hides the menu bar (the bar at the top of the window).
|
||||
#[clap(long)]
|
||||
pub no_gui: bool,
|
||||
|
||||
/// Remaps a specific button on a gamepad to a keyboard key.
|
||||
/// This can be used to add new gamepad support to existing games, for example mapping
|
||||
/// the D-pad to the arrow keys with -B d-pad-up=up -B d-pad-down=down etc.
|
||||
///
|
||||
/// A case-insensitive list of supported gamepad-buttons is:
|
||||
/// - north, east, south, west
|
||||
/// - d-pad-up, d-pad-down, d-pad-left, d-pad-right
|
||||
/// - left-trigger, left-trigger2
|
||||
/// - right-trigger, right-trigger2
|
||||
/// - select, start
|
||||
///
|
||||
/// A case-insensitive (non-exhaustive) list of common key-names is:
|
||||
/// - a, b, c, ..., z
|
||||
/// - up, down, left, right
|
||||
/// - return
|
||||
/// - space
|
||||
/// - comma, semicolon
|
||||
/// - key0, key1, ..., key9
|
||||
/// The complete list of supported key-names can be found by using -B start=nonexistent.
|
||||
#[clap(
|
||||
long,
|
||||
short = 'B',
|
||||
value_parser(parse_gamepad_button),
|
||||
verbatim_doc_comment,
|
||||
value_name = "GAMEPAD BUTTON>=<KEY NAME"
|
||||
)]
|
||||
pub gamepad_button: Vec<(GamepadButton, KeyCode)>,
|
||||
}
|
||||
|
||||
fn parse_movie_file_or_url(path: &str) -> Result<Url, Error> {
|
||||
crate::util::parse_url(Path::new(path))
|
||||
}
|
||||
|
||||
fn parse_gamepad_button(mapping: &str) -> Result<(GamepadButton, KeyCode), Error> {
|
||||
let pos = mapping.find('=').ok_or_else(|| {
|
||||
anyhow!("invalid <gamepad button>=<key name>: no `=` found in `{mapping}`")
|
||||
})?;
|
||||
|
||||
fn to_aliases<T: ValueEnum>(variants: &[T]) -> String {
|
||||
let aliases: Vec<String> = variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
variant
|
||||
.to_possible_value()
|
||||
.expect("Must have a PossibleValue")
|
||||
.get_name_and_aliases()
|
||||
.next()
|
||||
.expect("Must have one alias")
|
||||
.to_owned()
|
||||
})
|
||||
.collect();
|
||||
aliases.join(", ")
|
||||
}
|
||||
|
||||
let button = GamepadButton::from_str(&mapping[..pos], true).map_err(|err| {
|
||||
anyhow!(
|
||||
"Could not parse <gamepad button>: {err}\n The possible values are: {}",
|
||||
to_aliases(GamepadButton::value_variants())
|
||||
)
|
||||
})?;
|
||||
let key_code = KeyCode::from_str(&mapping[pos + 1..], true).map_err(|err| {
|
||||
anyhow!(
|
||||
"Could not parse <key name>: {err}\n The possible values are: {}",
|
||||
to_aliases(KeyCode::value_variants())
|
||||
)
|
||||
})?;
|
||||
Ok((button, key_code))
|
||||
}
|
||||
|
||||
impl Opt {
|
||||
#[cfg(feature = "render_trace")]
|
||||
pub fn trace_path(&self) -> Option<&Path> {
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::{CALLSTACK, RENDER_INFO, SWF_INFO};
|
|||
use anyhow::anyhow;
|
||||
use ruffle_core::backend::navigator::{OpenURLMode, SocketMode};
|
||||
use ruffle_core::config::Letterbox;
|
||||
use ruffle_core::events::{GamepadButton, KeyCode};
|
||||
use ruffle_core::{
|
||||
DefaultFont, LoadBehavior, Player, PlayerBuilder, PlayerEvent, PlayerRuntime, StageAlign,
|
||||
StageScaleMode,
|
||||
|
@ -18,7 +19,7 @@ use ruffle_render::backend::RenderBackend;
|
|||
use ruffle_render::quality::StageQuality;
|
||||
use ruffle_render_wgpu::backend::WgpuRenderBackend;
|
||||
use ruffle_render_wgpu::descriptors::Descriptors;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::time::Duration;
|
||||
|
@ -52,6 +53,7 @@ pub struct PlayerOptions {
|
|||
pub frame_rate: Option<f64>,
|
||||
pub open_url_mode: OpenURLMode,
|
||||
pub dummy_external_interface: bool,
|
||||
pub gamepad_button_mapping: HashMap<GamepadButton, KeyCode>,
|
||||
}
|
||||
|
||||
impl From<&Opt> for PlayerOptions {
|
||||
|
@ -79,6 +81,7 @@ impl From<&Opt> for PlayerOptions {
|
|||
dummy_external_interface: value.dummy_external_interface,
|
||||
socket_allowed: HashSet::from_iter(value.socket_allow.iter().cloned()),
|
||||
tcp_connections: value.tcp_connections,
|
||||
gamepad_button_mapping: HashMap::from_iter(value.gamepad_button.iter().cloned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +147,10 @@ impl ActivePlayer {
|
|||
Duration::from_secs_f64(opt.max_execution_duration)
|
||||
};
|
||||
|
||||
if !opt.gamepad_button_mapping.is_empty() {
|
||||
builder = builder.with_gamepad_button_mapping(opt.gamepad_button_mapping.clone());
|
||||
}
|
||||
|
||||
builder = builder
|
||||
.with_navigator(navigator)
|
||||
.with_renderer(renderer)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::custom_event::RuffleEvent;
|
||||
use anyhow::{anyhow, Error};
|
||||
use gilrs::Button;
|
||||
use rfd::FileDialog;
|
||||
use ruffle_core::events::{KeyCode, TextControlCode};
|
||||
use ruffle_core::events::{GamepadButton, KeyCode, TextControlCode};
|
||||
use std::path::{Path, PathBuf};
|
||||
use url::Url;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
@ -171,6 +172,28 @@ pub fn winit_to_ruffle_key_code(event: &KeyEvent) -> KeyCode {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn gilrs_button_to_gamepad_button(button: Button) -> Option<GamepadButton> {
|
||||
match button {
|
||||
Button::South => Some(GamepadButton::South),
|
||||
Button::East => Some(GamepadButton::East),
|
||||
Button::North => Some(GamepadButton::North),
|
||||
Button::West => Some(GamepadButton::West),
|
||||
Button::LeftTrigger => Some(GamepadButton::LeftTrigger),
|
||||
Button::LeftTrigger2 => Some(GamepadButton::LeftTrigger2),
|
||||
Button::RightTrigger => Some(GamepadButton::RightTrigger),
|
||||
Button::RightTrigger2 => Some(GamepadButton::RightTrigger2),
|
||||
Button::Select => Some(GamepadButton::Select),
|
||||
Button::Start => Some(GamepadButton::Start),
|
||||
Button::DPadUp => Some(GamepadButton::DPadUp),
|
||||
Button::DPadDown => Some(GamepadButton::DPadDown),
|
||||
Button::DPadLeft => Some(GamepadButton::DPadLeft),
|
||||
Button::DPadRight => Some(GamepadButton::DPadRight),
|
||||
// GilRs has some more buttons that are seemingly not supported anywhere
|
||||
// like C or Z.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_screen_size(event_loop: &EventLoop<RuffleEvent>) -> PhysicalSize<u32> {
|
||||
let mut min_x = 0;
|
||||
let mut min_y = 0;
|
||||
|
|
Loading…
Reference in New Issue