Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"d92728971ab6bcd4b54023b405b706db63be047cdc95e263f25d4326ac0cd4b0","Cargo.lock":"666f1404db3deaa3754ae812f4220f78ae4aa0e73ef37fccafb45b3b4286a383","Cargo.toml":"d32bfd9876603069636c0d5f3a688045151ed49264e7ff1b3648287c68a833b5","README.md":"c06d73dde0802fc0794821b679414c0845db901ddd4d4aa65a9fd4a99be80270","examples/ev_core.rs":"f20b81bc66ad5e35f3f06466b8a5594d3ba2bc9e84b9e5a8da75ed97b321405e","src/lib.rs":"53a97798e5d6f5d6bf7e7f83edd654fc391d21727a84cc25a980f0cb57a2d350","src/platform/default/ff.rs":"1781423e91daf0669b08824be64d378185b9715e67be88eabbfa9b3911cc430b","src/platform/default/gamepad.rs":"c6b76785454109a44402afb29d4ab7f469c2bca685c4c5ccd3d1fe04f98c30ae","src/platform/default/mod.rs":"6eb16af421252b824e594309b20503f8bddfa943937a597cef11b84da70bddb3","src/platform/linux/ff.rs":"e3f822a94f47339caa2f645dd73c1cb2bbe2ebfe3f7c4a3a6f4447eccdb8df7a","src/platform/linux/gamepad.rs":"e260f2d236f9f061932a3170d8f2d7b5631b49c6af0db69403631eca9879ad9c","src/platform/linux/ioctl.rs":"b52a41e395e9d2fe67bff4d76be720c3b56826077f6c06a06055383aa18b10bf","src/platform/linux/mod.rs":"e1bc0b31cb742d0afb4612ffdd884db778511a68d31f1657d9df7f444c62b9d4","src/platform/linux/udev.rs":"cd47f7eceb56ba1bda52c6e8b47adfffa75f7dfbe0226f64dd862a627e136037","src/platform/macos/ff.rs":"a964c5f137ae105ca84d306178107865d903912c185ab979c146d4d7ab026643","src/platform/macos/gamepad.rs":"14150ddb12bdc835ec18e08c839939b6ad73821aa77fe3f2df3cf6baad3dcb89","src/platform/macos/io_kit.rs":"1be690e5874834481473fc4409c042e696ff224d5d77022d484fa42f43af9910","src/platform/macos/mod.rs":"0d917656dcceb167692261b2612f48edff6a5ef2ceb53f7c4957bee822b07483","src/platform/mod.rs":"e02c475576a638418848bb7f5990d29eadf06c9c4617f1fb276bbbb590ea1697","src/platform/wasm/ff.rs":"afac021047e93585cac4e312ad40220c0f162ca6716dc1f13c36cfc302f8f0c4","src/platform/wasm/gamepad.rs":"7cd1883ef1f55fdabb65150f55d9f712899ab5189ba8c547de09ff2944d4464f","src/platform/wasm/mod.rs":"83a51121a371a308a1b0164b61a5e2e1d95ccb4e24b2e2f7068ae94ec2912350","src/platform/windows_wgi/ff.rs":"d870c04e176f02395cb0ec65f021ca08c6c9c9546c27900002b48f7087de39f3","src/platform/windows_wgi/gamepad.rs":"3ead9580b4ea844e30c87e96b872059ec26b3c9eea55dfa61ec9dc5d587f89cf","src/platform/windows_wgi/mod.rs":"be70fc8060f0bd315bb178986e67ad238ff0f46616c84511811d2b3f59bf3ab6","src/platform/windows_xinput/ff.rs":"defad2540ed9282b067f9612503263d427fca5799cf6233141d429bbd29ee596","src/platform/windows_xinput/gamepad.rs":"c020d969ab05d2ea518ed29ede5799bfea4cbb8125b5837ff6721a311d020ffb","src/platform/windows_xinput/mod.rs":"2db10284e653d020c0af0e3e16aa5fdd21dfb94e782508445f0c2bc580e64d35","src/utils.rs":"7891ebf61a0ae6390441dd3850cf344c6997548cb7f2f2f8a40ffd677fc5c7b5"},"package":"f3579883f2f2419e20a55b19ce7cc7bd96b50321ad99082a1637a6c01631ee82"}

340
vendor/gilrs-core/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,340 @@
Change Log
==========
v0.6.5 - 2025-07-21
----------
### Changed
- Updated `windows` and `nix` crates
v0.6.4 - 2025-04-06
----------
### Changed
- Updated `windows` crate
v0.6.3 - 2025-03-10
----------
### Fixed
- Fixed panic on macOS when gamepads vec had unexpected length after failing to open a device
### Changed
- Updated `windows` crate
v0.6.2 - 2025-02-09
----------
### Fixed
- Fixed possible panic on Windows when `NonRoamableId()` could return error.
### Changed
- Minimal supported Rust version is now 1.80.
v0.6.1 - 2025-01-13
----------
### Changed
- Minimal supported Rust version is now 1.74
- Updated `windows` crate
v0.6.0 - 2024-09-15
----------
### Breaking changes
- Mark Error enums, `EventType` and `Event` as `non_exhaustive`
### Changed
- Minimal supported Rust version is now 1.73
- Updated dependencies
v0.5.15 - 2024-08-25
----------
### Fixed
- wasm: Fixed panic when browser assigned unexpected gamepad ID
- windows: Fixed panic when receiving connected/disconnected events after instance of `Gilrs`
was dropped.
- windows: Dont panic on Reading::update() returning error
v0.5.13 - 2024-07-08
----------
### Changed
- Updated `windows` crate
v0.5.12 - 2024-06-15
----------
### Fixed
- Fixed building on FreeBSD and DragonFly by not using linux implementation
### Changed
- Updated dependencies
v0.5.11 - 2024-03-06
----------
### Added
- Added `vendor_id()` and `product_id()` to `Gamepad`.
### Changed
- Updated `windows` crate to 0.54.
v0.5.10 - 2023-12-17
----------
### Changed
- Updated `windows` crate to 0.52.
v0.5.9 - 2023-11-13
----------
### Fixed
- Disabled unnecessary default features for `inotify`.
v0.5.8 - 2023-11-11
----------
### Added
- Flatpak is now supported by using inotify instead of udev. (!104)
### Changed
- All thread spawned by gilrs are now named. (!102)
- MSRV is now 1.65.
### Fixed
- Linux: Fixed delay in Gilrs::new by limiting udev scan to the input
subsystem. (!101)
### Fixed
v0.5.7 - 2023-08-22
----------
### Fixed
- windows: Join wgi thread on `Gilrs`'s drop
- wasm: Fix trigger2 only sending binary values
## Changed
- Update `windows` to 0.51
v0.5.6 - 2023-06-19
----------
### Fixed
- Linux: fixed panic when calling `get_power_info` on disconnected gamepad.
v0.5.5 - 2023-04-23
----------
### Added
- `Gilrs::next_event_blocking()`
v0.5.4 - 2023-04-03
----------
### Changed
- Updated `io-kit-sys`, `windows` and `nix`
v0.5.3 - 2023-03-29
----------
### Changed
- Updated `windows` to 0.44
### Fixed
- web: Fixed handling of disconnected gamepads
v0.5.2 - 2022-12-16
----------
### Changed
- `Gilrs` is now `Send` on Linux.
### Fixed
- Crash when app is launched through steam on Windows (see
https://github.com/microsoft/windows-rs/issues/2252 for details).
v0.5.1 - 2022-11-13
-------------------
### Fixed
- macOS: Fixed that hat axes were sometimes added before other axes breaking
SDL mappings.
- web: Fixed swapped north and west buttons for gamepads with "standard"
mapping
v0.5.0 - 2022-11-06
--------------------
### Changed
- Windows now defaults to using Windows Gaming Input instead of xinput.
If you need to use xInput you can disable the `wgi` feature (It's enabled by
default) and enable the `xinput` feature.
``` toml
gilrs-core = {version = "0.5.0", default-features = false, features = ["wgi"]}
```
- Apps on Windows will now require a focused window to receive inputs by
default.
This is a limitation of Windows Gaming Input. It requires an in focus Window
be associated with the process to receive events. You can still switch back
to using xInput by turning off default features and enabling the `xinput`
feature.
- Minimal supported rust version is now 1.64.
### Fixed
- `Gamepad::axes()` on macos now also returns "hat" axes. This should fix dpad
on single Switch Joy-Con.
v0.4.1 - 2022-05-29
-------------------
### Changed
- Updated io-kit-sys to 0.2 and core-foundation to 0.9 (@jtakakura).
- Reduced numer of enabled features for nix crate (@rtzoeller).
v0.4.0 - 2022-05-22
-------------------
### Changed
- wasm: web-sys/wasm-bindgen is now used by default, dependency on stdweb
and `wasm-bindgen` feature are removed.
- Minimal supported rust version is now 1.56.
- Updated `uuid` and `nix` to current version.
### Fixed
- wasm: `next_event()` no longer panic if `getGamepads()` is not available.
v0.3.2 - 2021-12-30
-------------------
### Changed
- Updated dependencies
v0.3.1 - 2021-03-30
-------------------
### Added
- Add support for wasm-bindgen (@coolreader18)
v0.3.0 - 2020-10-09
-------------------
### Added
- macos: dpad is supported as a set of dpad axes (gilrs filters dpad axes to
dpad buttons) (@cleancut).
### Changed
- Minimal supported version is now 1.40
v0.2.6 - 2020-05-11
-------------------
Fixed compilation on musl.
v0.2.5 - 2019-11-30
-------------------
Updated dependencies.
v0.2.4 - 2019-09-05
-------------------
### Fixed
- Fixed compilation on platforms with dummy impl
v0.2.3 - 2019-08-06
-------------------
### Fixed
- xinput: Removed unneeded logging
- macos: `IS_Y_AXIS_REVERSED` is now correctly set to `true`
- macos: Fixed UUID calculation
v0.2.2 - 2019-04-06
-------------------
### Changed
- Windows: XInput is now dynamically loaded using rusty-xinput
### Fixed
- xinput: incorrect `is_connected()` after hotplugging
- wasm: Incorrect gamepad IDs in `Disconnected` event (@ryanisaacg)
v0.2.1 - 2019-02-25
-------------------
### Fixed
- Compilation error on macOS
v0.2.0 - 2019-02-21
-------------------
### Added
- Initial support for macOS (@jtakakura). There are still some functionality
missing, check related issues in #58.
- Wasm support, using stdweb (@ryanisaacg).
### Changed
- `AxisInfo::deadzone` is now a `Option`.
- Minimal supported version is now 1.31.1. The crate can still be build with
older rustc, but it may change during next patch release.
### Removed
- `AxisInfo::deadzone()` function.
### Fixed
- xinput: Incorrect gamepad ID when more than one gamepad is connected (
@DTibbs).

744
vendor/gilrs-core/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,744 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "gilrs-core"
version = "0.6.5"
dependencies = [
"core-foundation",
"env_logger",
"inotify",
"io-kit-sys",
"js-sys",
"libc",
"libudev-sys",
"log",
"nix",
"rusty-xinput",
"serde",
"uuid",
"vec_map",
"wasm-bindgen",
"web-sys",
"winapi",
"windows",
]
[[package]]
name = "inotify"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags",
"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 = "io-kit-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b"
dependencies = [
"core-foundation-sys",
"mach2",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "jiff"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "js-sys"
version = "0.3.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[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 = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "mach2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "rusty-xinput"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3335c2b62e1e48dd927f6c8941705386e3697fa944aabcb10431bea7ee47ef3"
dependencies = [
"lazy_static",
"log",
"winapi",
]
[[package]]
name = "serde"
version = "1.0.226"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.226"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.226"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "wasm-bindgen"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
"windows-core",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-strings",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.3",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"

135
vendor/gilrs-core/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,135 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.80.0"
name = "gilrs-core"
version = "0.6.5"
authors = ["Mateusz Sieczko <arvamer@gmail.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Minimal event-based abstraction for working with gamepads"
documentation = "https://docs.rs/gilrs-core/"
readme = "README.md"
keywords = [
"gamepad",
"joystick",
"input",
]
categories = ["game-engines"]
license = "Apache-2.0/MIT"
repository = "https://gitlab.com/gilrs-project/gilrs"
[package.metadata.docs.rs]
features = ["serde"]
[features]
default = ["wgi"]
serde-serialize = ["serde"]
wgi = ["windows"]
xinput = [
"rusty-xinput",
"winapi",
]
[lib]
name = "gilrs_core"
path = "src/lib.rs"
[[example]]
name = "ev_core"
path = "examples/ev_core.rs"
[dependencies.log]
version = "0.4.1"
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dependencies.uuid]
version = "1.0.0"
[dev-dependencies.env_logger]
version = "0.11.5"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies.inotify]
version = "0.11.0"
default-features = false
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies.libc]
version = "0.2"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies.libudev-sys]
version = "0.1.4"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies.nix]
version = "0.30.1"
features = [
"ioctl",
"event",
]
default-features = false
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies.vec_map]
version = "0.8"
[target.'cfg(target_arch = "wasm32")'.dependencies.js-sys]
version = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2"
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3"
features = [
"Gamepad",
"GamepadButton",
"GamepadMappingType",
"Window",
"Navigator",
"DomException",
]
[target.'cfg(target_os = "macos")'.dependencies.core-foundation]
version = "0.10.0"
[target.'cfg(target_os = "macos")'.dependencies.io-kit-sys]
version = "0.4.1"
[target.'cfg(target_os = "macos")'.dependencies.vec_map]
version = "0.8"
[target.'cfg(target_os = "windows")'.dependencies.rusty-xinput]
version = "1.2.0"
optional = true
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.4"
features = ["xinput"]
optional = true
[target.'cfg(target_os = "windows")'.dependencies.windows]
version = ">=0.44, <=0.62"
features = [
"Gaming_Input",
"Foundation_Collections",
"Devices_Power",
"System_Power",
"Gaming_Input_ForceFeedback",
]
optional = true

29
vendor/gilrs-core/README.md vendored Normal file
View File

@@ -0,0 +1,29 @@
GilRs Core
==========
[![pipeline status](https://gitlab.com/gilrs-project/gilrs/badges/master/pipeline.svg)](https://gitlab.com/gilrs-project/gilrs-core/commits/master)
[![Minimum rustc version](https://img.shields.io/badge/rustc-1.64.0+-yellow.svg)](https://gitlab.com/gilrs-project/gilrs)
This library is minimal event-based abstraction for working with gamepads. If
you are looking for something more high level, take a look at `gilrs` crate.
Platform specific notes
======================
Linux
-----
On Linux, GilRs read (and write, in case of force feedback) directly from
appropriate `/dev/input/event*` file. This means that user has to have read and
write access to this file. On most distros it shouldnt be a problem, but if
it is, you will have to create udev rule.
To build GilRs, you will need pkg-config and libudev .pc file. On some
distributions this file is packaged in separate archive (for example
`libudev-dev` in Debian).
License
=======
This project is licensed under the terms of both the Apache License (Version
2.0) and the MIT license. See LICENSE-APACHE and LICENSE-MIT for details.

12
vendor/gilrs-core/examples/ev_core.rs vendored Normal file
View File

@@ -0,0 +1,12 @@
use gilrs_core::Gilrs;
fn main() {
env_logger::init();
let mut gilrs = Gilrs::new().unwrap();
loop {
while let Some(ev) = gilrs.next_event_blocking(None) {
println!("{:0x?}", ev);
}
}
}

348
vendor/gilrs-core/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,348 @@
#[macro_use]
extern crate log;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::error;
use std::time::Duration;
use std::time::SystemTime;
mod platform;
pub mod utils;
/// True, if Y axis of sticks commonly points downwards.
pub const IS_Y_AXIS_REVERSED: bool = platform::IS_Y_AXIS_REVERSED;
/// Allow control of gamepad's force feedback.
#[derive(Debug)]
pub struct FfDevice {
inner: platform::FfDevice,
}
impl FfDevice {
/// Sets magnitude for strong and weak ff motors.
pub fn set_ff_state(&mut self, strong: u16, weak: u16, min_duration: Duration) {
self.inner.set_ff_state(strong, weak, min_duration)
}
}
/// Holds information about gamepad event.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct Event {
/// Id of gamepad.
pub id: usize,
/// Event's data.
pub event: EventType,
/// Time when event was emitted.
pub time: SystemTime,
}
impl Event {
/// Creates new event with current time.
pub fn new(id: usize, event: EventType) -> Self {
let time = utils::time_now();
Event { id, event, time }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Gamepad event.
#[non_exhaustive]
pub enum EventType {
ButtonPressed(EvCode),
ButtonReleased(EvCode),
AxisValueChanged(i32, EvCode),
Connected,
Disconnected,
}
/// Holds information about expected axis range and deadzone.
#[derive(Copy, Clone, Debug)]
pub struct AxisInfo {
pub min: i32,
pub max: i32,
pub deadzone: Option<u32>,
}
/// State of device's power supply.
///
/// Battery level is reported as integer between 0 and 100.
///
/// ## Example
///
/// ```
/// use gilrs_core::PowerInfo;
/// # let gilrs = gilrs_core::Gilrs::new().unwrap();
///
/// match gilrs.gamepad(0).map(|g| g.power_info()) {
/// Some(PowerInfo::Discharging(lvl)) if lvl <= 10 => println!("Low battery level, you should \
/// plug your gamepad"),
/// _ => (),
/// };
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PowerInfo {
/// Failed to determine power status.
Unknown,
/// Device doesn't have battery.
Wired,
/// Device is running on the battery.
Discharging(u8),
/// Battery is charging.
Charging(u8),
/// Battery is charged.
Charged,
}
/// Struct used to manage gamepads and retrieve events.
#[derive(Debug)]
pub struct Gilrs {
inner: platform::Gilrs,
}
impl Gilrs {
pub fn new() -> Result<Self, Error> {
let inner = platform::Gilrs::new().map_err(|e| match e {
PlatformError::NotImplemented(inner) => Error::NotImplemented(Gilrs { inner }),
PlatformError::Other(e) => Error::Other(e),
})?;
Ok(Gilrs { inner })
}
/// Returns oldest event or `None` if all events were processed.
pub fn next_event(&mut self) -> Option<Event> {
self.inner.next_event()
}
/// Returns oldest event, waiting for new event if necessary.
pub fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
self.inner.next_event_blocking(timeout)
}
/// Borrows `Gamepad` or return `None` if index is invalid. Returned gamepad may be disconnected.
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
unsafe {
let gp: Option<&platform::Gamepad> = self.inner.gamepad(id);
gp.map(|gp| &*(gp as *const _ as *const Gamepad))
}
}
/// Returns id greater than id of last connected gamepad. The returned value is only hint
/// and may be much larger than number of observed gamepads. For example, it may return maximum
/// number of connected gamepads on platforms when this limit is small.
///
/// `gamepad(id)` should return `Some` if using id that is smaller than value returned from this
/// function.
pub fn last_gamepad_hint(&self) -> usize {
self.inner.last_gamepad_hint()
}
}
/// Provides information about gamepad.
#[derive(Debug)]
#[repr(transparent)]
pub struct Gamepad {
inner: platform::Gamepad,
}
impl Gamepad {
/// Returns name of gamepad.
pub fn name(&self) -> &str {
self.inner.name()
}
/// Returns true if gamepad is connected.
pub fn is_connected(&self) -> bool {
self.inner.is_connected()
}
/// Returns UUID that represents gamepad model.
///
/// Returned UUID should be the same as SLD2 uses. If platform does not provide any method to
/// distinguish between gamepad models, nil UUID is returned.
///
/// It is recommended to process with the [UUID crate](https://crates.io/crates/uuid).
/// Use `Uuid::from_bytes` method to create a `Uuid` from the returned bytes.
pub fn uuid(&self) -> [u8; 16] {
*self.inner.uuid().as_bytes()
}
/// Returns the vendor ID, as assigned by the USB-IF, when available.
pub fn vendor_id(&self) -> Option<u16> {
self.inner.vendor_id()
}
/// Returns the product ID, as assigned by the vendor, when available.
pub fn product_id(&self) -> Option<u16> {
self.inner.product_id()
}
/// Returns device's power supply state.
pub fn power_info(&self) -> PowerInfo {
self.inner.power_info()
}
/// Returns true if force feedback is supported by device,
pub fn is_ff_supported(&self) -> bool {
self.inner.is_ff_supported()
}
/// Creates `FfDevice` corresponding to this gamepad.
pub fn ff_device(&self) -> Option<FfDevice> {
self.inner.ff_device().map(|inner| FfDevice { inner })
}
/// Returns slice with EvCodes that may appear in button related events.
pub fn buttons(&self) -> &[EvCode] {
unsafe {
let bt: &[platform::EvCode] = self.inner.buttons();
&*(bt as *const _ as *const [EvCode])
}
}
/// Returns slice with EvCodes that may appear in axis related events.
pub fn axes(&self) -> &[EvCode] {
unsafe {
let ax: &[platform::EvCode] = self.inner.axes();
&*(ax as *const _ as *const [EvCode])
}
}
/// Returns information about a specific axis. `None` may be returned if a device doesn't have an axis
/// with provided `EvCode`.
pub fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
self.inner.axis_info(nec.0)
}
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
/// Platform specific representation of axis or button.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct EvCode(platform::EvCode);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0.into_u32()
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
/// Error type which can be returned when creating `Gilrs`.
///
/// Private version of `Error` that use `platform::Gilrs`.
#[derive(Debug)]
enum PlatformError {
/// Gilrs does not support the current platform, but you can use dummy context from this error if
/// gamepad input is not essential.
#[allow(dead_code)]
NotImplemented(platform::Gilrs),
/// Platform specific error.
#[allow(dead_code)]
Other(Box<dyn error::Error + Send + Sync>),
}
impl Display for PlatformError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
PlatformError::NotImplemented(_) => {
f.write_str("Gilrs does not support current platform.")
}
PlatformError::Other(ref e) => e.fmt(f),
}
}
}
impl error::Error for PlatformError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
PlatformError::Other(e) => Some(e.as_ref()),
_ => None,
}
}
}
/// Error type which can be returned when creating `Gilrs`.
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
/// Gilrs does not support current platform, but you can use dummy context from this error if
/// gamepad input is not essential.
NotImplemented(Gilrs),
/// Platform specific error.
Other(Box<dyn error::Error + Send + Sync + 'static>),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::NotImplemented(_) => f.write_str("Gilrs does not support current platform."),
Error::Other(ref e) => e.fmt(f),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Other(e) => Some(e.as_ref()),
_ => None,
}
}
}
/// Provides the most common mappings of physical location of gamepad elements to their EvCodes.
/// Some (or most) gamepads may use different mappings.
pub mod native_ev_codes {
use super::EvCode;
use crate::platform::native_ev_codes as nec;
pub const AXIS_LSTICKX: EvCode = EvCode(nec::AXIS_LSTICKX);
pub const AXIS_LSTICKY: EvCode = EvCode(nec::AXIS_LSTICKY);
pub const AXIS_LEFTZ: EvCode = EvCode(nec::AXIS_LEFTZ);
pub const AXIS_RSTICKX: EvCode = EvCode(nec::AXIS_RSTICKX);
pub const AXIS_RSTICKY: EvCode = EvCode(nec::AXIS_RSTICKY);
pub const AXIS_RIGHTZ: EvCode = EvCode(nec::AXIS_RIGHTZ);
pub const AXIS_DPADX: EvCode = EvCode(nec::AXIS_DPADX);
pub const AXIS_DPADY: EvCode = EvCode(nec::AXIS_DPADY);
pub const AXIS_RT: EvCode = EvCode(nec::AXIS_RT);
pub const AXIS_LT: EvCode = EvCode(nec::AXIS_LT);
pub const AXIS_RT2: EvCode = EvCode(nec::AXIS_RT2);
pub const AXIS_LT2: EvCode = EvCode(nec::AXIS_LT2);
pub const BTN_SOUTH: EvCode = EvCode(nec::BTN_SOUTH);
pub const BTN_EAST: EvCode = EvCode(nec::BTN_EAST);
pub const BTN_C: EvCode = EvCode(nec::BTN_C);
pub const BTN_NORTH: EvCode = EvCode(nec::BTN_NORTH);
pub const BTN_WEST: EvCode = EvCode(nec::BTN_WEST);
pub const BTN_Z: EvCode = EvCode(nec::BTN_Z);
pub const BTN_LT: EvCode = EvCode(nec::BTN_LT);
pub const BTN_RT: EvCode = EvCode(nec::BTN_RT);
pub const BTN_LT2: EvCode = EvCode(nec::BTN_LT2);
pub const BTN_RT2: EvCode = EvCode(nec::BTN_RT2);
pub const BTN_SELECT: EvCode = EvCode(nec::BTN_SELECT);
pub const BTN_START: EvCode = EvCode(nec::BTN_START);
pub const BTN_MODE: EvCode = EvCode(nec::BTN_MODE);
pub const BTN_LTHUMB: EvCode = EvCode(nec::BTN_LTHUMB);
pub const BTN_RTHUMB: EvCode = EvCode(nec::BTN_RTHUMB);
pub const BTN_DPAD_UP: EvCode = EvCode(nec::BTN_DPAD_UP);
pub const BTN_DPAD_DOWN: EvCode = EvCode(nec::BTN_DPAD_DOWN);
pub const BTN_DPAD_LEFT: EvCode = EvCode(nec::BTN_DPAD_LEFT);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(nec::BTN_DPAD_RIGHT);
}

View File

@@ -0,0 +1,17 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
#[derive(Debug)]
/// Represents gamepad. Reexported as FfDevice
pub struct Device;
impl Device {
/// Sets magnitude for strong and weak ff motors.
pub fn set_ff_state(&mut self, strong: u16, weak: u16, min_duration: Duration) {}
}

View File

@@ -0,0 +1,149 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
#![allow(unused_variables)]
use super::FfDevice;
use crate::{AxisInfo, Event, PlatformError, PowerInfo};
use uuid::Uuid;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::time::Duration;
#[derive(Debug)]
pub struct Gilrs {}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
Err(PlatformError::NotImplemented(Gilrs {}))
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
None
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
None
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
None
}
/// Returns index greater than index of last connected gamepad.
pub fn last_gamepad_hint(&self) -> usize {
0
}
}
#[derive(Debug)]
pub struct Gamepad {
_priv: u8, // required for `#[repr(transparent)]`
}
impl Gamepad {
pub fn name(&self) -> &str {
""
}
pub fn uuid(&self) -> Uuid {
Uuid::nil()
}
pub fn vendor_id(&self) -> Option<u16> {
None
}
pub fn product_id(&self) -> Option<u16> {
None
}
pub fn power_info(&self) -> PowerInfo {
PowerInfo::Unknown
}
pub fn is_ff_supported(&self) -> bool {
false
}
/// Creates Ffdevice corresponding to this gamepad.
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice)
}
pub fn buttons(&self) -> &[EvCode] {
&[]
}
pub fn axes(&self) -> &[EvCode] {
&[]
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
None
}
pub fn is_connected(&self) -> bool {
false
}
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode(u16);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0 as u32
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.0.fmt(f)
}
}
pub mod native_ev_codes {
use super::EvCode;
pub const AXIS_LSTICKX: EvCode = EvCode(0);
pub const AXIS_LSTICKY: EvCode = EvCode(1);
pub const AXIS_LEFTZ: EvCode = EvCode(2);
pub const AXIS_RSTICKX: EvCode = EvCode(3);
pub const AXIS_RSTICKY: EvCode = EvCode(4);
pub const AXIS_RIGHTZ: EvCode = EvCode(5);
pub const AXIS_DPADX: EvCode = EvCode(6);
pub const AXIS_DPADY: EvCode = EvCode(7);
pub const AXIS_RT: EvCode = EvCode(8);
pub const AXIS_LT: EvCode = EvCode(9);
pub const AXIS_RT2: EvCode = EvCode(10);
pub const AXIS_LT2: EvCode = EvCode(11);
pub const BTN_SOUTH: EvCode = EvCode(12);
pub const BTN_EAST: EvCode = EvCode(13);
pub const BTN_C: EvCode = EvCode(14);
pub const BTN_NORTH: EvCode = EvCode(15);
pub const BTN_WEST: EvCode = EvCode(16);
pub const BTN_Z: EvCode = EvCode(17);
pub const BTN_LT: EvCode = EvCode(18);
pub const BTN_RT: EvCode = EvCode(19);
pub const BTN_LT2: EvCode = EvCode(20);
pub const BTN_RT2: EvCode = EvCode(21);
pub const BTN_SELECT: EvCode = EvCode(22);
pub const BTN_START: EvCode = EvCode(23);
pub const BTN_MODE: EvCode = EvCode(24);
pub const BTN_LTHUMB: EvCode = EvCode(25);
pub const BTN_RTHUMB: EvCode = EvCode(26);
pub const BTN_DPAD_UP: EvCode = EvCode(27);
pub const BTN_DPAD_DOWN: EvCode = EvCode(28);
pub const BTN_DPAD_LEFT: EvCode = EvCode(29);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(30);
}

View File

@@ -0,0 +1,14 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
// True, if Y axis of sticks points downwards.
pub const IS_Y_AXIS_REVERSED: bool = false;

View File

@@ -0,0 +1,124 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::fs::File;
use std::io::{Error as IoError, ErrorKind, Result as IoResult, Write};
use std::os::unix::io::AsRawFd;
use std::{mem, slice};
use super::ioctl::{self, ff_effect, ff_replay, ff_rumble_effect, input_event};
use nix::errno::Errno;
use std::time::Duration;
#[derive(Debug)]
pub struct Device {
effect: i16,
file: File,
}
impl Device {
pub(crate) fn new(path: &str) -> IoResult<Self> {
let file = File::create(path)?;
let mut effect = ff_effect {
type_: FF_RUMBLE,
id: -1,
direction: 0,
trigger: Default::default(),
replay: Default::default(),
u: Default::default(),
};
#[allow(clippy::unnecessary_mut_passed)]
let res = unsafe { ioctl::eviocsff(file.as_raw_fd(), &mut effect) };
if res.is_err() {
Err(IoError::new(ErrorKind::Other, "Failed to create effect"))
} else {
Ok(Device {
effect: effect.id,
file,
})
}
}
pub fn set_ff_state(&mut self, strong: u16, weak: u16, min_duration: Duration) {
let duration = min_duration.as_secs() * 1000 + u64::from(min_duration.subsec_millis());
let duration = if duration > u64::from(u16::MAX) {
u16::MAX
} else {
duration as u16
};
let mut effect = ff_effect {
type_: FF_RUMBLE,
id: self.effect,
direction: 0,
trigger: Default::default(),
replay: ff_replay {
delay: 0,
length: duration,
},
u: Default::default(),
};
unsafe {
let rumble = &mut effect.u as *mut _ as *mut ff_rumble_effect;
(*rumble).strong_magnitude = strong;
(*rumble).weak_magnitude = weak;
if let Err(err) = ioctl::eviocsff(self.file.as_raw_fd(), &effect) {
error!(
"Failed to modify effect of gamepad {:?}, error: {}",
self.file, err
);
return;
}
};
let time = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
let ev = input_event {
type_: EV_FF,
code: self.effect as u16,
value: 1,
time,
};
let size = mem::size_of::<input_event>();
let s = unsafe { slice::from_raw_parts(&ev as *const _ as *const u8, size) };
match self.file.write(s) {
Ok(s) if s == size => (),
Ok(_) => unreachable!(),
Err(e) => error!("Failed to set ff state: {}", e),
}
}
}
impl Drop for Device {
fn drop(&mut self) {
#[cfg(target_os = "linux")]
let effect = self.effect as ::libc::c_ulong;
#[cfg(not(target_os = "linux"))]
let effect = self.effect as ::libc::c_int;
if let Err(err) = unsafe { ioctl::eviocrmff(self.file.as_raw_fd(), effect) } {
if err != Errno::ENODEV {
error!(
"Failed to remove effect of gamepad {:?}: {}",
self.file, err
)
}
};
}
}
const EV_FF: u16 = 0x15;
const FF_RUMBLE: u16 = 0x50;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
// Some ioctls are exported by ioctl crate only for x86_64, so we have to define them anyway.
// Diffing linux/input.h across different architectures (i686, x86_64 and arm) didn't show any
// difference, so it looks like conditional compilation is not needed.
#![allow(dead_code)]
use nix::{ioctl_read, ioctl_read_buf, ioctl_write_int, ioctl_write_ptr, request_code_read};
use std::mem::MaybeUninit;
#[cfg(target_env = "musl")]
pub type IoctlRequest = libc::c_int;
#[cfg(not(target_env = "musl"))]
pub type IoctlRequest = libc::c_ulong;
ioctl_read!(eviocgid, b'E', 0x02, /*struct*/ input_id);
ioctl_write_int!(eviocrmff, b'E', 0x81);
ioctl_write_ptr!(eviocsff, b'E', 0x80, ff_effect);
ioctl_read_buf!(eviocgname, b'E', 0x06, MaybeUninit<u8>);
ioctl_read_buf!(eviocgkey, b'E', 0x18, u8);
pub unsafe fn eviocgbit(fd: libc::c_int, ev: u32, len: libc::c_int, buf: *mut u8) -> libc::c_int {
::nix::libc::ioctl(
fd,
request_code_read!(b'E', 0x20 + ev, len) as IoctlRequest,
buf,
)
}
pub unsafe fn eviocgabs(fd: ::libc::c_int, abs: u32, buf: *mut input_absinfo) -> libc::c_int {
::nix::libc::ioctl(
fd,
request_code_read!(b'E', 0x40 + abs, ::std::mem::size_of::<input_absinfo>())
as IoctlRequest,
buf,
)
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct input_event {
pub time: libc::timeval,
pub type_: u16,
pub code: u16,
pub value: i32,
}
impl ::std::default::Default for input_event {
fn default() -> Self {
unsafe { ::std::mem::zeroed() }
}
}
impl ::std::fmt::Debug for input_event {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(
f,
"input_event {{ time: {{ tv_sec: {}, tv_usec: {} }}, type_: {}, code: {}, value: {}",
self.time.tv_sec, self.time.tv_usec, self.type_, self.code, self.value
)
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct input_id {
pub bustype: u16,
pub vendor: u16,
pub product: u16,
pub version: u16,
}
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct input_absinfo {
pub value: i32,
pub minimum: i32,
pub maximum: i32,
pub fuzz: i32,
pub flat: i32,
pub resolution: i32,
}
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct ff_replay {
pub length: u16,
pub delay: u16,
}
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct ff_trigger {
pub button: u16,
pub interval: u16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_envelope {
pub attack_length: u16,
pub attack_level: u16,
pub fade_length: u16,
pub fade_level: u16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_constant_effect {
pub level: i16,
pub envelope: ff_envelope,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_ramp_effect {
pub start_level: i16,
pub end_level: i16,
pub envelope: ff_envelope,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_condition_effect {
pub right_saturation: u16,
pub left_saturation: u16,
pub right_coeff: i16,
pub left_coeff: i16,
pub deadband: u16,
pub center: i16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_periodic_effect {
pub waveform: u16,
pub period: u16,
pub magnitude: i16,
pub offset: i16,
pub phase: u16,
pub envelope: ff_envelope,
pub custom_len: u32,
pub custom_data: *mut i16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_rumble_effect {
pub strong_magnitude: u16,
pub weak_magnitude: u16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_effect {
pub type_: u16,
pub id: i16,
pub direction: u16,
pub trigger: ff_trigger,
pub replay: ff_replay,
// FIXME this is actually a union
#[cfg(target_pointer_width = "64")]
pub u: [u64; 4],
#[cfg(target_pointer_width = "32")]
pub u: [u32; 7],
}

View File

@@ -0,0 +1,16 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
// Copyright 2016 GilRs Developers
mod ff;
mod gamepad;
mod ioctl;
mod udev;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = true;

View File

@@ -0,0 +1,254 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use libc as c;
use libudev_sys as ud;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
#[derive(Debug)]
pub struct Udev(*mut ud::udev);
impl Udev {
pub fn new() -> Option<Self> {
let u = unsafe { ud::udev_new() };
if u.is_null() {
None
} else {
Some(Udev(u))
}
}
pub fn enumerate(&self) -> Option<Enumerate> {
let en = unsafe { ud::udev_enumerate_new(self.0) };
if en.is_null() {
None
} else {
let en = Enumerate(en);
Some(en)
}
}
}
impl Drop for Udev {
fn drop(&mut self) {
unsafe {
ud::udev_unref(self.0);
}
}
}
impl Clone for Udev {
fn clone(&self) -> Self {
Udev(unsafe { ud::udev_ref(self.0) })
}
}
pub struct Enumerate(*mut ud::udev_enumerate);
impl Enumerate {
pub fn scan_devices(&self) {
// TODO: Check for error
let _ = unsafe { ud::udev_enumerate_scan_devices(self.0) };
}
pub fn add_match_property(&self, key: &CStr, val: &CStr) {
// TODO: Check for error
unsafe {
ud::udev_enumerate_add_match_property(self.0, key.as_ptr(), val.as_ptr());
}
}
pub fn add_match_subsystem(&self, subsystem: &CStr) {
// TODO: Check for error
unsafe {
ud::udev_enumerate_add_match_subsystem(self.0, subsystem.as_ptr());
}
}
pub fn iter(&self) -> DeviceIterator {
DeviceIterator(unsafe { ud::udev_enumerate_get_list_entry(self.0) })
}
}
impl Drop for Enumerate {
fn drop(&mut self) {
unsafe {
ud::udev_enumerate_unref(self.0);
}
}
}
pub struct DeviceIterator(*mut ud::udev_list_entry);
impl Iterator for DeviceIterator {
type Item = CString;
fn next(&mut self) -> Option<CString> {
if self.0.is_null() {
None
} else {
let p_name = unsafe { ud::udev_list_entry_get_name(self.0) };
let name = if p_name.is_null() {
return None;
} else {
unsafe { CStr::from_ptr(p_name).to_owned() }
};
self.0 = unsafe { ud::udev_list_entry_get_next(self.0) };
Some(name)
}
}
}
pub struct Device(*mut ud::udev_device);
impl Device {
pub fn from_syspath(udev: &Udev, path: &CStr) -> Option<Self> {
let dev = unsafe { ud::udev_device_new_from_syspath(udev.0, path.as_ptr()) };
if dev.is_null() {
None
} else {
Some(Device(dev))
}
}
pub fn syspath(&self) -> &CStr {
// Always returns cstring
unsafe { CStr::from_ptr(ud::udev_device_get_syspath(self.0)) }
}
pub fn devnode(&self) -> Option<&CStr> {
unsafe {
let s = ud::udev_device_get_devnode(self.0);
if s.is_null() {
None
} else {
Some(CStr::from_ptr(s))
}
}
}
#[allow(dead_code)]
pub fn properties(&self) -> PropertyIterator {
let prop = unsafe { ud::udev_device_get_properties_list_entry(self.0) };
PropertyIterator(prop)
}
pub fn action(&self) -> Option<&CStr> {
unsafe {
let s = ud::udev_device_get_action(self.0);
if s.is_null() {
None
} else {
Some(CStr::from_ptr(s))
}
}
}
pub fn property_value(&self, key: &CStr) -> Option<&CStr> {
unsafe {
let s = ud::udev_device_get_property_value(self.0, key.as_ptr());
if s.is_null() {
None
} else {
Some(CStr::from_ptr(s))
}
}
}
}
impl Clone for Device {
fn clone(&self) -> Self {
unsafe { Device(ud::udev_device_ref(self.0)) }
}
}
impl Drop for Device {
fn drop(&mut self) {
unsafe {
ud::udev_device_unref(self.0);
}
}
}
#[allow(dead_code)]
pub struct PropertyIterator(*mut ud::udev_list_entry);
impl Iterator for PropertyIterator {
type Item = (String, String);
fn next(&mut self) -> Option<(String, String)> {
if self.0.is_null() {
None
} else {
let p_name = unsafe { ud::udev_list_entry_get_name(self.0) };
let p_val = unsafe { ud::udev_list_entry_get_value(self.0) };
let name = if p_name.is_null() {
return None;
} else {
unsafe { CStr::from_ptr(p_name).to_string_lossy().into_owned() }
};
let value = if p_val.is_null() {
return None;
} else {
unsafe { CStr::from_ptr(p_val).to_string_lossy().into_owned() }
};
self.0 = unsafe { ud::udev_list_entry_get_next(self.0) };
Some((name, value))
}
}
}
#[derive(Debug)]
pub struct Monitor(*mut ud::udev_monitor);
impl Monitor {
pub fn new(udev: &Udev) -> Option<Self> {
unsafe {
let monitor =
ud::udev_monitor_new_from_netlink(udev.0, c"udev".as_ptr() as *const c_char);
if monitor.is_null() {
None
} else {
ud::udev_monitor_filter_add_match_subsystem_devtype(
monitor,
c"input".as_ptr() as *const c_char,
ptr::null(),
);
ud::udev_monitor_enable_receiving(monitor);
Some(Monitor(monitor))
}
}
}
pub fn wait_hotplug_available(&self) -> bool {
unsafe {
let mut fds = c::pollfd {
fd: ud::udev_monitor_get_fd(self.0),
events: c::POLLIN,
revents: 0,
};
(c::poll(&mut fds, 1, -1) == 1) && (fds.revents & c::POLLIN != 0)
}
}
pub fn device(&self) -> Device {
Device(unsafe { ud::udev_monitor_receive_device(self.0) })
}
}
impl Drop for Monitor {
fn drop(&mut self) {
unsafe {
ud::udev_monitor_unref(self.0);
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
#[derive(Debug)]
/// Represents gamepad. Reexported as FfDevice
pub struct Device;
impl Device {
/// Sets magnitude for strong and weak ff motors.
pub fn set_ff_state(&mut self, _strong: u16, _weak: u16, _min_duration: Duration) {}
}

View File

@@ -0,0 +1,892 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::io_kit::*;
use super::FfDevice;
use crate::{AxisInfo, Event, EventType, PlatformError, PowerInfo};
use io_kit_sys::hid::usage_tables::kHIDPage_VendorDefinedStart;
use uuid::Uuid;
use core_foundation::runloop::{kCFRunLoopDefaultMode, CFRunLoop};
use io_kit_sys::hid::base::{IOHIDDeviceRef, IOHIDValueRef};
use io_kit_sys::hid::usage_tables::{
kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, kHIDUsage_GD_Joystick,
kHIDUsage_GD_MultiAxisController,
};
use io_kit_sys::ret::IOReturn;
use vec_map::VecMap;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::os::raw::c_void;
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Debug)]
pub struct Gilrs {
gamepads: Vec<Gamepad>,
device_infos: Arc<Mutex<Vec<DeviceInfo>>>,
rx: Receiver<(Event, Option<IOHIDDevice>)>,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let gamepads = Vec::new();
let device_infos = Arc::new(Mutex::new(Vec::new()));
let (tx, rx) = mpsc::channel();
Self::spawn_thread(tx, device_infos.clone());
Ok(Gilrs {
gamepads,
device_infos,
rx,
})
}
fn spawn_thread(
tx: Sender<(Event, Option<IOHIDDevice>)>,
device_infos: Arc<Mutex<Vec<DeviceInfo>>>,
) {
thread::Builder::new()
.name("gilrs".to_owned())
.spawn(move || unsafe {
let mut manager = match IOHIDManager::new() {
Some(manager) => manager,
None => {
error!("Failed to create IOHIDManager object");
return;
}
};
manager.schedule_with_run_loop(CFRunLoop::get_current(), kCFRunLoopDefaultMode);
let context = &(tx.clone(), device_infos.clone()) as *const _ as *mut c_void;
manager.register_device_matching_callback(device_matching_cb, context);
let context = &(tx.clone(), device_infos.clone()) as *const _ as *mut c_void;
manager.register_device_removal_callback(device_removal_cb, context);
let context = &(tx, device_infos) as *const _ as *mut c_void;
manager.register_input_value_callback(input_value_cb, context);
CFRunLoop::run_current();
manager.unschedule_from_run_loop(CFRunLoop::get_current(), kCFRunLoopDefaultMode);
})
.expect("failed to spawn thread");
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
let event = self.rx.try_recv().ok();
self.handle_event(event)
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
let event = if let Some(timeout) = timeout {
self.rx.recv_timeout(timeout).ok()
} else {
self.rx.recv().ok()
};
self.handle_event(event)
}
fn handle_event(&mut self, event: Option<(Event, Option<IOHIDDevice>)>) -> Option<Event> {
match event {
Some((event, Some(device))) => {
if event.event == EventType::Connected {
if self.gamepads.get(event.id).is_some() {
self.gamepads[event.id].is_connected = true;
} else {
match Gamepad::open(device) {
Some(gamepad) => {
self.gamepads.push(gamepad);
}
None => {
error!("Failed to open gamepad: {:?}", event.id);
return None;
}
};
}
}
Some(event)
}
Some((event, None)) => {
if event.event == EventType::Disconnected {
match self.gamepads.get_mut(event.id) {
Some(gamepad) => {
match self.device_infos.lock().unwrap().get_mut(event.id) {
Some(device_info) => device_info.is_connected = false,
None => {
error!("Failed to find device_info: {:?}", event.id);
return None;
}
};
gamepad.is_connected = false;
}
None => {
error!("Failed to find gamepad: {:?}", event.id);
return None;
}
}
}
Some(event)
}
None => None,
}
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
/// Returns index greater than index of last connected gamepad.
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct Gamepad {
name: String,
vendor: Option<u16>,
product: Option<u16>,
uuid: Uuid,
entry_id: u64,
location_id: u32,
page: u32,
usage: u32,
axes_info: VecMap<AxisInfo>,
axes: Vec<EvCode>,
hats: Vec<EvCode>,
buttons: Vec<EvCode>,
is_connected: bool,
}
impl Gamepad {
fn open(device: IOHIDDevice) -> Option<Gamepad> {
let io_service = match device.get_service() {
Some(io_service) => io_service,
None => {
error!("Failed to get device service");
return None;
}
};
let entry_id = match io_service.get_registry_entry_id() {
Some(entry_id) => entry_id,
None => {
error!("Failed to get entry id of device");
return None;
}
};
let location_id = match device.get_location_id() {
Some(location_id) => location_id,
None => {
error!("Failed to get location id of device");
return None;
}
};
let page = match device.get_page() {
Some(page) => {
if page >= kHIDPage_VendorDefinedStart {
error!("Device HID page is Vendor Defined. {device:?}");
return None;
}
if page == kHIDPage_GenericDesktop {
page
} else {
error!("Failed to get valid device. Expecting kHIDPage_GenericDesktop. Got 0x{:X?}", page);
return None;
}
}
None => {
error!("Failed to get page of device");
return None;
}
};
let usage = match device.get_usage() {
Some(usage) => {
if usage == kHIDUsage_GD_GamePad
|| usage == kHIDUsage_GD_Joystick
|| usage == kHIDUsage_GD_MultiAxisController
{
usage
} else {
error!("Failed to get valid device: {:?}", usage);
return None;
}
}
None => {
error!("Failed to get usage of device");
return None;
}
};
let name = device.get_name().unwrap_or_else(|| {
warn!("Failed to get name of device");
"Unknown".into()
});
let uuid = Self::create_uuid(&device).unwrap_or_default();
let mut gamepad = Gamepad {
name,
vendor: device.get_vendor_id(),
product: device.get_product_id(),
uuid,
entry_id,
location_id,
page,
usage,
axes_info: VecMap::with_capacity(8),
axes: Vec::with_capacity(8),
hats: Vec::with_capacity(4),
buttons: Vec::with_capacity(16),
is_connected: true,
};
gamepad.collect_axes_and_buttons(&device.get_elements());
Some(gamepad)
}
fn create_uuid(device: &IOHIDDevice) -> Option<Uuid> {
// SDL always uses USB bus for UUID
let bustype = u32::to_be(0x03);
let vendor_id = match device.get_vendor_id() {
Some(vendor_id) => vendor_id.to_be(),
None => {
warn!("Failed to get vendor id of device");
0
}
};
let product_id = match device.get_product_id() {
Some(product_id) => product_id.to_be(),
None => {
warn!("Failed to get product id of device");
0
}
};
let version = match device.get_version() {
Some(version) => version.to_be(),
None => {
warn!("Failed to get version of device");
0
}
};
if vendor_id == 0 && product_id == 0 && version == 0 {
None
} else {
Some(Uuid::from_fields(
bustype,
vendor_id,
0,
&[
(product_id >> 8) as u8,
product_id as u8,
0,
0,
(version >> 8) as u8,
version as u8,
0,
0,
],
))
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn vendor_id(&self) -> Option<u16> {
self.vendor
}
pub fn product_id(&self) -> Option<u16> {
self.product
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn power_info(&self) -> PowerInfo {
PowerInfo::Unknown
}
pub fn is_ff_supported(&self) -> bool {
false
}
/// Creates Ffdevice corresponding to this gamepad.
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice)
}
pub fn buttons(&self) -> &[EvCode] {
&self.buttons
}
pub fn axes(&self) -> &[EvCode] {
&self.axes
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
self.axes_info.get(nec.usage as usize)
}
pub fn is_connected(&self) -> bool {
self.is_connected
}
fn collect_axes_and_buttons(&mut self, elements: &Vec<IOHIDElement>) {
let mut cookies = Vec::new();
self.collect_axes(elements, &mut cookies);
self.axes.sort_by_key(|axis| axis.usage);
self.hats.sort_by_key(|axis| axis.usage);
// Because "hat is axis" is a gilrs thing, we want to ensure that all hats are at the end of
// the axis vector, so the SDL mappings still work.
self.axes.extend(&self.hats);
self.collect_buttons(elements, &mut cookies);
self.buttons.sort_by_key(|button| button.usage);
}
fn collect_axes(&mut self, elements: &Vec<IOHIDElement>, cookies: &mut Vec<u32>) {
for element in elements {
let type_ = element.get_type();
let cookie = element.get_cookie();
let page = element.get_page();
let usage = element.get_usage();
if IOHIDElement::is_collection_type(type_) {
let children = element.get_children();
self.collect_axes(&children, cookies);
} else if IOHIDElement::is_axis(type_, page, usage) && !cookies.contains(&cookie) {
cookies.push(cookie);
self.axes_info.insert(
usage as usize,
AxisInfo {
min: element.get_logical_min() as _,
max: element.get_logical_max() as _,
deadzone: None,
},
);
self.axes.push(EvCode::new(page, usage));
} else if IOHIDElement::is_hat(type_, page, usage) && !cookies.contains(&cookie) {
cookies.push(cookie);
self.axes_info.insert(
usage as usize,
AxisInfo {
min: -1,
max: 1,
deadzone: None,
},
);
self.hats.push(EvCode::new(page, usage));
// All hat switches are translated into *two* axes
self.axes_info.insert(
(usage + 1) as usize, // "+ 1" is assumed for usage of 2nd hat switch axis
AxisInfo {
min: -1,
max: 1,
deadzone: None,
},
);
self.hats.push(EvCode::new(page, usage + 1));
}
}
}
fn collect_buttons(&mut self, elements: &Vec<IOHIDElement>, cookies: &mut Vec<u32>) {
for element in elements {
let type_ = element.get_type();
let cookie = element.get_cookie();
let page = element.get_page();
let usage = element.get_usage();
if IOHIDElement::is_collection_type(type_) {
let children = element.get_children();
self.collect_buttons(&children, cookies);
} else if IOHIDElement::is_button(type_, page, usage) && !cookies.contains(&cookie) {
cookies.push(cookie);
self.buttons.push(EvCode::new(page, usage));
}
}
}
}
#[derive(Debug)]
struct DeviceInfo {
entry_id: u64,
location_id: u32,
is_connected: bool,
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode {
page: u32,
usage: u32,
}
impl EvCode {
fn new(page: u32, usage: u32) -> Self {
EvCode { page, usage }
}
pub fn into_u32(self) -> u32 {
(self.page << 16) | self.usage
}
}
impl From<IOHIDElement> for crate::EvCode {
fn from(e: IOHIDElement) -> Self {
crate::EvCode(EvCode {
page: e.get_page(),
usage: e.get_usage(),
})
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self.page {
PAGE_GENERIC_DESKTOP => f.write_str("GENERIC_DESKTOP")?,
PAGE_BUTTON => f.write_str("BUTTON")?,
page => f.write_fmt(format_args!("PAGE_{}", page))?,
}
f.write_fmt(format_args!("({})", self.usage))
}
}
pub mod native_ev_codes {
use super::*;
pub const AXIS_LSTICKX: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LSTICKX,
};
pub const AXIS_LSTICKY: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LSTICKY,
};
pub const AXIS_LEFTZ: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LEFTZ,
};
pub const AXIS_RSTICKX: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RSTICKX,
};
pub const AXIS_RSTICKY: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RSTICKY,
};
pub const AXIS_RIGHTZ: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RIGHTZ,
};
pub const AXIS_DPADX: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_DPADX,
};
pub const AXIS_DPADY: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_DPADY,
};
pub const AXIS_RT: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RT,
};
pub const AXIS_LT: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LT,
};
pub const AXIS_RT2: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RT2,
};
pub const AXIS_LT2: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LT2,
};
pub const BTN_SOUTH: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_SOUTH,
};
pub const BTN_EAST: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_EAST,
};
pub const BTN_C: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_C,
};
pub const BTN_NORTH: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_NORTH,
};
pub const BTN_WEST: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_WEST,
};
pub const BTN_Z: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_Z,
};
pub const BTN_LT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_LT,
};
pub const BTN_RT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_RT,
};
pub const BTN_LT2: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_LT2,
};
pub const BTN_RT2: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_RT2,
};
pub const BTN_SELECT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_SELECT,
};
pub const BTN_START: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_START,
};
pub const BTN_MODE: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_MODE,
};
pub const BTN_LTHUMB: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_LTHUMB,
};
pub const BTN_RTHUMB: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_RTHUMB,
};
pub const BTN_DPAD_UP: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_UP,
};
pub const BTN_DPAD_DOWN: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_DOWN,
};
pub const BTN_DPAD_LEFT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_LEFT,
};
pub const BTN_DPAD_RIGHT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_RIGHT,
};
}
#[allow(clippy::type_complexity)]
extern "C" fn device_matching_cb(
context: *mut c_void,
_result: IOReturn,
_sender: *mut c_void,
value: IOHIDDeviceRef,
) {
let (tx, device_infos): &(
Sender<(Event, Option<IOHIDDevice>)>,
Arc<Mutex<Vec<DeviceInfo>>>,
) = unsafe { &*(context as *mut _) };
let device = match IOHIDDevice::new(value) {
Some(device) => device,
None => {
error!("Failed to get device");
return;
}
};
let io_service = match device.get_service() {
Some(io_service) => io_service,
None => {
error!("Failed to get device service");
return;
}
};
let entry_id = match io_service.get_registry_entry_id() {
Some(entry_id) => entry_id,
None => {
error!("Failed to get entry id of device");
return;
}
};
// Filter devices which will not succed in open(). If devices are added to the
// DeviceInfo vec, it will cause a mismatch of IDs as they're derived from
// the length of the DeviceInfo Vec, but devices which fail to open() will not
// be pushed into the Gilrs inner gamepads vec, and panics will ensue.
//
// Try to open the device early, and if it fails, do not add it to device_infos
match Gamepad::open(device.clone()) {
Some(gamepad) => drop(gamepad),
None => {
warn!("Failed to open device {device:?}. Skipping.");
return;
}
}
let mut device_infos = device_infos.lock().unwrap();
let id = match device_infos
.iter()
.position(|info| info.entry_id == entry_id && info.is_connected)
{
Some(id) => {
info!("Device is already registered: {:?}", entry_id);
id
}
None => {
let location_id = match device.get_location_id() {
Some(location_id) => location_id,
None => {
error!("Failed to get location id of device");
return;
}
};
device_infos.push(DeviceInfo {
entry_id,
location_id,
is_connected: true,
});
device_infos.len() - 1
}
};
let _ = tx.send((Event::new(id, EventType::Connected), Some(device)));
}
#[allow(clippy::type_complexity)]
extern "C" fn device_removal_cb(
context: *mut c_void,
_result: IOReturn,
_sender: *mut c_void,
value: IOHIDDeviceRef,
) {
let (tx, device_infos): &(
Sender<(Event, Option<IOHIDDevice>)>,
Arc<Mutex<Vec<DeviceInfo>>>,
) = unsafe { &*(context as *mut _) };
let device = match IOHIDDevice::new(value) {
Some(device) => device,
None => {
error!("Failed to get device");
return;
}
};
let location_id = match device.get_location_id() {
Some(location_id) => location_id,
None => {
error!("Failed to get location id of device");
return;
}
};
let device_infos = device_infos.lock().unwrap();
let id = match device_infos
.iter()
.position(|info| info.location_id == location_id && info.is_connected)
{
Some(id) => id,
None => {
warn!("Failed to find device: {:?}", location_id);
return;
}
};
let _ = tx.send((Event::new(id, EventType::Disconnected), None));
}
#[allow(clippy::type_complexity)]
extern "C" fn input_value_cb(
context: *mut c_void,
_result: IOReturn,
sender: *mut c_void,
value: IOHIDValueRef,
) {
let (tx, device_infos): &(
Sender<(Event, Option<IOHIDDevice>)>,
Arc<Mutex<Vec<DeviceInfo>>>,
) = unsafe { &*(context as *mut _) };
let device = match IOHIDDevice::new(sender as _) {
Some(device) => device,
None => {
error!("Failed to get device");
return;
}
};
let io_service = match device.get_service() {
Some(io_service) => io_service,
None => {
error!("Failed to get device service");
return;
}
};
let entry_id = match io_service.get_registry_entry_id() {
Some(entry_id) => entry_id,
None => {
error!("Failed to get entry id of device");
return;
}
};
let device_infos = device_infos.lock().unwrap();
let id = match device_infos
.iter()
.position(|info| info.entry_id == entry_id && info.is_connected)
{
Some(id) => id,
None => {
warn!("Failed to find device: {:?}", entry_id);
return;
}
};
let value = match IOHIDValue::new(value) {
Some(value) => value,
None => {
error!("Failed to get value");
return;
}
};
let element = match value.get_element() {
Some(element) => element,
None => {
error!("Failed to get element of value");
return;
}
};
let type_ = element.get_type();
let page = element.get_page();
let usage = element.get_usage();
if IOHIDElement::is_axis(type_, page, usage) {
let event = Event::new(
id,
EventType::AxisValueChanged(
value.get_value() as i32,
crate::EvCode(EvCode { page, usage }),
),
);
let _ = tx.send((event, None));
} else if IOHIDElement::is_button(type_, page, usage) {
if value.get_value() == 0 {
let event = Event::new(
id,
EventType::ButtonReleased(crate::EvCode(EvCode { page, usage })),
);
let _ = tx.send((event, None));
} else {
let event = Event::new(
id,
EventType::ButtonPressed(crate::EvCode(EvCode { page, usage })),
);
let _ = tx.send((event, None));
}
} else if IOHIDElement::is_hat(type_, page, usage) {
// Hat switch values are reported with a range of usually 8 numbers (sometimes 4). The logic
// below uses the reported min/max values of that range to map that onto a range of 0-7 for
// the directions (and any other value indicates the center position). Lucky for us, they
// always start with "up" as the lowest number and proceed clockwise. See similar handling
// here https://github.com/spurious/SDL-mirror/blob/094b2f68dd7fc9af167f905e10625e103a131459/src/joystick/darwin/SDL_sysjoystick.c#L976-L1028
//
// up
// 7 0 1
// \ | /
// left 6 - ? - 2 right (After mapping)
// / | \
// 5 4 3
// down
let range = element.get_logical_max() - element.get_logical_min() + 1;
let shifted_value = value.get_value() - element.get_logical_min();
let dpad_value = match range {
4 => shifted_value * 2, // 4-position hat switch - scale it up to 8
8 => shifted_value, // 8-position hat switch - no adjustment necessary
_ => -1, // Neither 4 nor 8 positions, we don't know what to do - default to centered
};
// At this point, the value should be normalized to the 0-7 directional values (or center
// for any other value). The dpad is a hat switch on macOS, but on other platforms dpads are
// either buttons or a pair of axes that get converted to button events by the
// `axis_dpad_to_button` filter. We will emulate axes here and let that filter do the
// button conversion, because it is safer and easier than making separate logic for button
// conversion that may diverge in subtle ways from the axis conversion logic. The most
// practical outcome of this conversion is that there are extra "released" axis events for
// the unused axis. For example, pressing just "up" will also give you a "released" event
// for either the left or right button, even if it wasn't pressed before pressing "up".
let x_axis_value = match dpad_value {
5..=7 => -1, // left
1..=3 => 1, // right
_ => 0,
};
// Since we're emulating an inverted macOS gamepad axis, down is positive and up is negative
let y_axis_value = match dpad_value {
3..=5 => 1, // down
0 | 1 | 7 => -1, // up
_ => 0,
};
let x_axis_event = Event::new(
id,
EventType::AxisValueChanged(
x_axis_value,
crate::EvCode(EvCode {
page,
usage: USAGE_AXIS_DPADX,
}),
),
);
let y_axis_event = Event::new(
id,
EventType::AxisValueChanged(
y_axis_value,
crate::EvCode(EvCode {
page,
usage: USAGE_AXIS_DPADY,
}),
),
);
let _ = tx.send((x_axis_event, None));
let _ = tx.send((y_axis_event, None));
}
}

View File

@@ -0,0 +1,612 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
use core_foundation::array::{
kCFTypeArrayCallBacks, CFArray, CFArrayCallBacks, CFArrayGetCount, CFArrayGetValueAtIndex,
__CFArray,
};
use core_foundation::base::{
kCFAllocatorDefault, CFAllocatorRef, CFIndex, CFRelease, CFType, TCFType,
};
use core_foundation::dictionary::CFDictionary;
use core_foundation::impl_TCFType;
use core_foundation::number::CFNumber;
use core_foundation::runloop::{CFRunLoop, CFRunLoopMode};
use core_foundation::set::CFSetApplyFunction;
use core_foundation::string::{kCFStringEncodingUTF8, CFString, CFStringCreateWithCString};
use io_kit_sys::hid::base::{
IOHIDDeviceCallback, IOHIDDeviceRef, IOHIDElementRef, IOHIDValueCallback, IOHIDValueRef,
};
use io_kit_sys::hid::device::*;
use io_kit_sys::hid::element::*;
use io_kit_sys::hid::keys::*;
use io_kit_sys::hid::manager::*;
use io_kit_sys::hid::usage_tables::*;
use io_kit_sys::hid::value::{
IOHIDValueGetElement, IOHIDValueGetIntegerValue, IOHIDValueGetTypeID,
};
use io_kit_sys::ret::{kIOReturnSuccess, IOReturn};
use io_kit_sys::types::{io_service_t, IO_OBJECT_NULL};
use io_kit_sys::{IOObjectRelease, IOObjectRetain, IORegistryEntryGetRegistryEntryID};
use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
use std::ptr;
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDManager(IOHIDManagerRef);
pub type CFMutableArrayRef = *mut __CFArray;
extern "C" {
pub fn CFArrayCreateMutable(
allocator: CFAllocatorRef,
capacity: CFIndex,
callBacks: *const CFArrayCallBacks,
) -> CFMutableArrayRef;
pub fn CFArrayAppendValue(theArray: CFMutableArrayRef, value: *const c_void);
}
impl_TCFType!(IOHIDManager, IOHIDManagerRef, IOHIDManagerGetTypeID);
impl IOHIDManager {
pub fn new() -> Option<Self> {
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone) };
if manager.is_null() {
return None;
}
let matchers = CFArray::from_CFTypes(&[
create_hid_device_matcher(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
create_hid_device_matcher(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
create_hid_device_matcher(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
]);
unsafe {
IOHIDManagerSetDeviceMatchingMultiple(manager, matchers.as_concrete_TypeRef());
};
let ret = unsafe { IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) };
if ret == kIOReturnSuccess {
Some(IOHIDManager(manager))
} else {
unsafe { CFRelease(manager as _) };
None
}
}
pub fn open(&mut self) -> IOReturn {
unsafe { IOHIDManagerOpen(self.0, kIOHIDOptionsTypeNone) }
}
pub fn close(&mut self) -> IOReturn {
unsafe { IOHIDManagerClose(self.0, kIOHIDOptionsTypeNone) }
}
pub fn schedule_with_run_loop(&mut self, run_loop: CFRunLoop, run_loop_mode: CFRunLoopMode) {
unsafe {
IOHIDManagerScheduleWithRunLoop(self.0, run_loop.as_concrete_TypeRef(), run_loop_mode)
}
}
pub fn unschedule_from_run_loop(&mut self, run_loop: CFRunLoop, run_loop_mode: CFRunLoopMode) {
unsafe {
IOHIDManagerUnscheduleFromRunLoop(self.0, run_loop.as_concrete_TypeRef(), run_loop_mode)
}
}
pub fn register_device_matching_callback(
&mut self,
callback: IOHIDDeviceCallback,
context: *mut c_void,
) {
unsafe { IOHIDManagerRegisterDeviceMatchingCallback(self.0, callback, context) }
}
pub fn register_device_removal_callback(
&mut self,
callback: IOHIDDeviceCallback,
context: *mut c_void,
) {
unsafe { IOHIDManagerRegisterDeviceRemovalCallback(self.0, callback, context) }
}
pub fn register_input_value_callback(
&mut self,
callback: IOHIDValueCallback,
context: *mut c_void,
) {
unsafe { IOHIDManagerRegisterInputValueCallback(self.0, callback, context) }
}
pub fn get_devices(&mut self) -> Vec<IOHIDDevice> {
let copied = unsafe { IOHIDManagerCopyDevices(self.0) };
if copied.is_null() {
return vec![];
}
let devices =
unsafe { CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks) };
if devices.is_null() {
unsafe { CFRelease(copied as _) };
return vec![];
}
unsafe { CFSetApplyFunction(copied, cf_set_applier, devices as _) };
unsafe { CFRelease(copied as _) };
let device_count = unsafe { CFArrayGetCount(devices) };
let mut vec = Vec::with_capacity(device_count as _);
for i in 0..device_count {
let device = unsafe { CFArrayGetValueAtIndex(devices, i) };
if device.is_null() {
continue;
}
if let Some(device) = IOHIDDevice::new(device as _) {
vec.push(device);
}
}
unsafe { CFRelease(devices as _) };
vec
}
}
impl Drop for IOHIDManager {
fn drop(&mut self) {
unsafe { CFRelease(self.as_CFTypeRef()) }
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDDevice(IOHIDDeviceRef);
impl_TCFType!(IOHIDDevice, IOHIDDeviceRef, IOHIDDeviceGetTypeID);
impl IOHIDDevice {
pub fn new(device: IOHIDDeviceRef) -> Option<IOHIDDevice> {
if device.is_null() {
None
} else {
Some(IOHIDDevice(device))
}
}
pub fn get_name(&self) -> Option<String> {
self.get_string_property(kIOHIDProductKey)
.map(|name| name.to_string())
}
pub fn get_location_id(&self) -> Option<u32> {
self.get_number_property(kIOHIDLocationIDKey)
.and_then(|location_id| location_id.to_i32().map(|location_id| location_id as u32))
}
pub fn get_bustype(&self) -> Option<u16> {
match self.get_transport_key() {
Some(transport_key) => {
if transport_key == "USB" {
Some(0x03)
} else if transport_key == "Bluetooth" {
Some(0x05)
} else {
None
}
}
None => None,
}
}
pub fn get_transport_key(&self) -> Option<String> {
self.get_string_property(kIOHIDTransportKey)
.map(|transport_key| transport_key.to_string())
}
pub fn get_vendor_id(&self) -> Option<u16> {
self.get_number_property(kIOHIDVendorIDKey)
.and_then(|vendor_id| vendor_id.to_i32().map(|vendor_id| vendor_id as u16))
}
pub fn get_product_id(&self) -> Option<u16> {
self.get_number_property(kIOHIDProductIDKey)
.and_then(|product_id| product_id.to_i32().map(|product_id| product_id as u16))
}
pub fn get_version(&self) -> Option<u16> {
self.get_number_property(kIOHIDVersionNumberKey)
.and_then(|version| version.to_i32().map(|version| version as u16))
}
pub fn get_page(&self) -> Option<u32> {
self.get_number_property(kIOHIDPrimaryUsagePageKey)
.and_then(|page| page.to_i32().map(|page| page as u32))
}
pub fn get_usage(&self) -> Option<u32> {
self.get_number_property(kIOHIDPrimaryUsageKey)
.and_then(|usage| usage.to_i32().map(|usage| usage as u32))
}
pub fn get_service(&self) -> Option<IOService> {
unsafe { IOService::new(IOHIDDeviceGetService(self.0)) }
}
pub fn get_elements(&self) -> Vec<IOHIDElement> {
let elements =
unsafe { IOHIDDeviceCopyMatchingElements(self.0, ptr::null(), kIOHIDOptionsTypeNone) };
if elements.is_null() {
return vec![];
}
let element_count = unsafe { CFArrayGetCount(elements) };
let mut vec = Vec::with_capacity(element_count as _);
for i in 0..element_count {
let element = unsafe { CFArrayGetValueAtIndex(elements, i) };
if element.is_null() {
continue;
}
vec.push(IOHIDElement(element as _));
}
vec
}
}
impl Properties for IOHIDDevice {
fn get_property(&self, key: *const c_char) -> Option<CFType> {
let key =
unsafe { CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8) };
let value = unsafe { IOHIDDeviceGetProperty(self.0, key) };
if value.is_null() {
None
} else {
Some(unsafe { TCFType::wrap_under_get_rule(value) })
}
}
}
unsafe impl Send for IOHIDDevice {}
unsafe impl Sync for IOHIDDevice {}
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDElement(IOHIDElementRef);
impl_TCFType!(IOHIDElement, IOHIDElementRef, IOHIDElementGetTypeID);
impl IOHIDElement {
pub fn is_collection_type(type_: u32) -> bool {
type_ == kIOHIDElementTypeCollection
}
pub fn is_axis(type_: u32, page: u32, usage: u32) -> bool {
match type_ {
kIOHIDElementTypeInput_Misc
| kIOHIDElementTypeInput_Button
| kIOHIDElementTypeInput_Axis => match page {
kHIDPage_GenericDesktop => {
matches!(
usage,
kHIDUsage_GD_X
| kHIDUsage_GD_Y
| kHIDUsage_GD_Z
| kHIDUsage_GD_Rx
| kHIDUsage_GD_Ry
| kHIDUsage_GD_Rz
| kHIDUsage_GD_Slider
| kHIDUsage_GD_Dial
| kHIDUsage_GD_Wheel
)
}
kHIDPage_Simulation => matches!(
usage,
kHIDUsage_Sim_Rudder
| kHIDUsage_Sim_Throttle
| kHIDUsage_Sim_Accelerator
| kHIDUsage_Sim_Brake
),
_ => false,
},
_ => false,
}
}
pub fn is_button(type_: u32, page: u32, usage: u32) -> bool {
match type_ {
kIOHIDElementTypeInput_Misc
| kIOHIDElementTypeInput_Button
| kIOHIDElementTypeInput_Axis => match page {
kHIDPage_GenericDesktop => matches!(
usage,
kHIDUsage_GD_DPadUp
| kHIDUsage_GD_DPadDown
| kHIDUsage_GD_DPadRight
| kHIDUsage_GD_DPadLeft
| kHIDUsage_GD_Start
| kHIDUsage_GD_Select
| kHIDUsage_GD_SystemMainMenu
),
kHIDPage_Button | kHIDPage_Consumer => true,
_ => false,
},
_ => false,
}
}
pub fn is_hat(type_: u32, page: u32, usage: u32) -> bool {
match type_ {
kIOHIDElementTypeInput_Misc
| kIOHIDElementTypeInput_Button
| kIOHIDElementTypeInput_Axis => match page {
kHIDPage_GenericDesktop => matches!(usage, USAGE_AXIS_DPADX | USAGE_AXIS_DPADY),
_ => false,
},
_ => false,
}
}
pub fn get_cookie(&self) -> u32 {
unsafe { IOHIDElementGetCookie(self.0) }
}
pub fn get_type(&self) -> u32 {
unsafe { IOHIDElementGetType(self.0) }
}
pub fn get_page(&self) -> u32 {
unsafe { IOHIDElementGetUsagePage(self.0) }
}
pub fn get_usage(&self) -> u32 {
unsafe { IOHIDElementGetUsage(self.0) }
}
pub fn get_logical_min(&self) -> i64 {
unsafe { IOHIDElementGetLogicalMin(self.0).try_into().unwrap() }
}
pub fn get_logical_max(&self) -> i64 {
unsafe { IOHIDElementGetLogicalMax(self.0).try_into().unwrap() }
}
pub fn get_calibration_dead_zone_min(&self) -> Option<i64> {
match self.get_number_property(kIOHIDElementCalibrationDeadZoneMinKey) {
Some(calibration_dead_zone_min) => calibration_dead_zone_min.to_i64(),
None => None,
}
}
pub fn get_calibration_dead_zone_max(&self) -> Option<i64> {
match self.get_number_property(kIOHIDElementCalibrationDeadZoneMaxKey) {
Some(calibration_dead_zone_max) => calibration_dead_zone_max.to_i64(),
None => None,
}
}
pub fn get_children(&self) -> Vec<IOHIDElement> {
let elements = unsafe { IOHIDElementGetChildren(self.0) };
if elements.is_null() {
return vec![];
}
let element_count = unsafe { CFArrayGetCount(elements) };
let mut vec = Vec::with_capacity(element_count as _);
for i in 0..element_count {
let element = unsafe { CFArrayGetValueAtIndex(elements, i) };
if element.is_null() {
continue;
}
vec.push(IOHIDElement(element as _));
}
vec
}
}
impl Properties for IOHIDElement {
fn get_property(&self, key: *const c_char) -> Option<CFType> {
let key =
unsafe { CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8) };
let value = unsafe { IOHIDElementGetProperty(self.0, key) };
if value.is_null() {
None
} else {
Some(unsafe { TCFType::wrap_under_get_rule(value) })
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDValue(IOHIDValueRef);
impl_TCFType!(IOHIDValue, IOHIDValueRef, IOHIDValueGetTypeID);
impl IOHIDValue {
pub fn new(value: IOHIDValueRef) -> Option<IOHIDValue> {
if value.is_null() {
None
} else {
Some(IOHIDValue(value))
}
}
pub fn get_value(&self) -> i64 {
unsafe { IOHIDValueGetIntegerValue(self.0).try_into().unwrap() }
}
pub fn get_element(&self) -> Option<IOHIDElement> {
let element = unsafe { IOHIDValueGetElement(self.0) };
if element.is_null() {
None
} else {
Some(IOHIDElement(element))
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IOService(io_service_t);
impl IOService {
pub fn new(io_service: io_service_t) -> Option<IOService> {
if io_service == IO_OBJECT_NULL {
return None;
}
let result = unsafe { IOObjectRetain(io_service) };
if result == kIOReturnSuccess {
Some(IOService(io_service))
} else {
None
}
}
pub fn get_registry_entry_id(&self) -> Option<u64> {
unsafe {
IOObjectRetain(self.0);
let mut entry_id = 0;
let result = IORegistryEntryGetRegistryEntryID(self.0, &mut entry_id);
IOObjectRelease(self.0);
if result == kIOReturnSuccess {
Some(entry_id)
} else {
None
}
}
}
}
impl Drop for IOService {
fn drop(&mut self) {
unsafe {
IOObjectRelease(self.0 as _);
}
}
}
trait Properties {
fn get_number_property(&self, key: *const c_char) -> Option<CFNumber> {
match self.get_property(key) {
Some(value) => {
if value.instance_of::<CFNumber>() {
Some(unsafe { CFNumber::wrap_under_get_rule(value.as_CFTypeRef() as _) })
} else {
None
}
}
None => None,
}
}
fn get_string_property(&self, key: *const c_char) -> Option<CFString> {
match self.get_property(key) {
Some(value) => {
if value.instance_of::<CFString>() {
Some(unsafe { CFString::wrap_under_get_rule(value.as_CFTypeRef() as _) })
} else {
None
}
}
None => None,
}
}
fn get_property(&self, key: *const c_char) -> Option<CFType>;
}
fn create_hid_device_matcher(page: u32, usage: u32) -> CFDictionary<CFString, CFNumber> {
let page_key = unsafe { CStr::from_ptr(kIOHIDDeviceUsagePageKey as _) };
let page_key = CFString::from(page_key.to_str().unwrap());
let page_value = CFNumber::from(page as i32);
let usage_key = unsafe { CStr::from_ptr(kIOHIDDeviceUsageKey as _) };
let usage_key = CFString::from(usage_key.to_str().unwrap());
let usage_value = CFNumber::from(usage as i32);
CFDictionary::from_CFType_pairs(&[(page_key, page_value), (usage_key, usage_value)])
}
extern "C" fn cf_set_applier(value: *const c_void, context: *const c_void) {
unsafe { CFArrayAppendValue(context as _, value) };
}
// Usage Pages
pub const PAGE_GENERIC_DESKTOP: u32 = kHIDPage_GenericDesktop;
pub const PAGE_BUTTON: u32 = kHIDPage_Button;
// GenericDesktop Page (0x01)
pub const USAGE_AXIS_LSTICKX: u32 = kHIDUsage_GD_X;
pub const USAGE_AXIS_LSTICKY: u32 = kHIDUsage_GD_Y;
#[allow(dead_code)]
pub const USAGE_AXIS_LEFTZ: u32 = 0;
pub const USAGE_AXIS_RSTICKX: u32 = kHIDUsage_GD_Rx;
pub const USAGE_AXIS_RSTICKY: u32 = kHIDUsage_GD_Ry;
#[allow(dead_code)]
pub const USAGE_AXIS_RIGHTZ: u32 = 0;
pub const USAGE_AXIS_DPADX: u32 = kHIDUsage_GD_Hatswitch;
pub const USAGE_AXIS_DPADY: u32 = kHIDUsage_GD_Hatswitch + 1; // This "+ 1" is assumed and hard-coded elsewhere
#[allow(dead_code)]
pub const USAGE_AXIS_RT: u32 = 0;
#[allow(dead_code)]
pub const USAGE_AXIS_LT: u32 = 0;
pub const USAGE_AXIS_RT2: u32 = kHIDUsage_GD_Z;
pub const USAGE_AXIS_LT2: u32 = kHIDUsage_GD_Rz;
// Button Page (0x09)
pub const USAGE_BTN_SOUTH: u32 = kHIDUsage_Button_1;
pub const USAGE_BTN_EAST: u32 = kHIDUsage_Button_1 + 1;
pub const USAGE_BTN_NORTH: u32 = kHIDUsage_Button_1 + 2;
pub const USAGE_BTN_WEST: u32 = kHIDUsage_Button_1 + 3;
pub const USAGE_BTN_LT: u32 = kHIDUsage_Button_1 + 4;
pub const USAGE_BTN_RT: u32 = kHIDUsage_Button_1 + 5;
pub const USAGE_BTN_LT2: u32 = kHIDUsage_Button_1 + 6;
pub const USAGE_BTN_RT2: u32 = kHIDUsage_Button_1 + 7;
pub const USAGE_BTN_START: u32 = kHIDUsage_Button_1 + 8;
pub const USAGE_BTN_SELECT: u32 = kHIDUsage_Button_1 + 9;
pub const USAGE_BTN_MODE: u32 = kHIDUsage_Button_1 + 10;
pub const USAGE_BTN_DPAD_UP: u32 = kHIDUsage_Button_1 + 11;
pub const USAGE_BTN_DPAD_DOWN: u32 = kHIDUsage_Button_1 + 12;
pub const USAGE_BTN_DPAD_LEFT: u32 = kHIDUsage_Button_1 + 13;
pub const USAGE_BTN_DPAD_RIGHT: u32 = kHIDUsage_Button_1 + 14;
#[allow(dead_code)]
pub const USAGE_BTN_C: u32 = kHIDUsage_Button_1 + 15;
#[allow(dead_code)]
pub const USAGE_BTN_Z: u32 = kHIDUsage_Button_1 + 16;
#[allow(dead_code)]
pub const USAGE_BTN_LTHUMB: u32 = kHIDUsage_Button_1 + 17;
#[allow(dead_code)]
pub const USAGE_BTN_RTHUMB: u32 = kHIDUsage_Button_1 + 18;

View File

@@ -0,0 +1,15 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
mod io_kit;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
// True, if Y axis of sticks points downwards.
pub const IS_Y_AXIS_REVERSED: bool = true;

60
vendor/gilrs-core/src/platform/mod.rs vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Module which exports the platform-specific types.
//!
//! Each backend has to provide:
//!
//! * A `FfDevice` (a struct which handles force feedback)
//! * A `Gilrs` context
//! * A `Gamepad` struct
//! * A static `str` which specifies the name of the SDL input mapping
//! * A constant which define whether Y axis of sticks points upwards or downwards
//! * A module with the platform-specific constants for common gamepad buttons
//! called `native_ev_codes`
#![allow(clippy::module_inception)]
pub use self::platform::*;
#[cfg(target_os = "linux")]
#[path = "linux/mod.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "macos/mod.rs"]
mod platform;
#[cfg(all(not(feature = "xinput"), not(feature = "wgi")))]
compile_error!(
"Windows needs one of the features `gilrs/xinput` or `gilrs/wgi` enabled. \nEither don't use \
'default-features = false' or add one of the features back."
);
#[cfg(all(feature = "wgi", feature = "xinput"))]
compile_error!("features `gilrs/xinput` and `gilrs/wgi` are mutually exclusive");
#[cfg(all(target_os = "windows", feature = "xinput", not(feature = "wgi")))]
#[path = "windows_xinput/mod.rs"]
mod platform;
#[cfg(all(target_os = "windows", feature = "wgi"))]
#[path = "windows_wgi/mod.rs"]
mod platform;
#[cfg(target_arch = "wasm32")]
#[path = "wasm/mod.rs"]
mod platform;
#[cfg(all(
not(any(target_os = "linux")),
not(target_os = "macos"),
not(target_os = "windows"),
not(target_arch = "wasm32")
))]
#[path = "default/mod.rs"]
mod platform;

View File

@@ -0,0 +1,15 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
#[derive(Debug)]
pub struct Device;
impl Device {
pub fn set_ff_state(&mut self, _strong: u16, _weak: u16, _min_duration: Duration) {}
}

View File

@@ -0,0 +1,486 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::collections::VecDeque;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::time::Duration;
use js_sys::RegExp;
use uuid::Uuid;
use wasm_bindgen::JsCast;
use web_sys::{DomException, Gamepad as WebGamepad, GamepadButton, GamepadMappingType};
use super::FfDevice;
use crate::platform::native_ev_codes::{BTN_LT2, BTN_RT2};
use crate::{AxisInfo, Event, EventType, PlatformError, PowerInfo};
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct Gilrs {
event_cache: VecDeque<Event>,
gamepads: Vec<Gamepad>,
new_web_gamepads: Vec<WebGamepad>,
next_event_error_logged: bool,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let window =
web_sys::window().ok_or_else(|| PlatformError::Other(Box::new(Error::NoWindow)))?;
if !window.is_secure_context() {
warn!("Context is not secure, gamepad API may not be available.")
}
Ok({
Gilrs {
event_cache: VecDeque::new(),
gamepads: Vec::new(),
new_web_gamepads: Vec::new(),
next_event_error_logged: false,
}
})
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
// Don't duplicate the work of checking the diff between the old and new gamepads if
// there are still events to return
if !self.event_cache.is_empty() {
return self.event_cache.pop_front();
}
let gamepads = match web_sys::window()
.expect("no window")
.navigator()
.get_gamepads()
{
Ok(x) => {
self.next_event_error_logged = false;
x
}
Err(js) => {
if !self.next_event_error_logged {
self.next_event_error_logged = true;
let exception: DomException = match js.dyn_into() {
Ok(x) => x,
Err(e) => {
error!("getGamepads() failed with unknown error: {:?}", e);
return None;
}
};
error!("getGamepads(): {}", exception.message());
}
return None;
}
};
// Gather all non-null gamepads
for maybe_js_gamepad in gamepads {
if !maybe_js_gamepad.is_null() {
self.new_web_gamepads
.push(WebGamepad::from(maybe_js_gamepad));
}
}
// Update existing gamepads
for (id, gamepad) in self.gamepads.iter_mut().enumerate() {
let maybe_js_gamepad_index = self
.new_web_gamepads
.iter()
.position(|x| gamepad.gamepad.index() == x.index());
if let Some(js_gamepad_index) = maybe_js_gamepad_index {
gamepad.gamepad = self.new_web_gamepads.swap_remove(js_gamepad_index);
if !gamepad.connected {
self.event_cache
.push_back(Event::new(id, EventType::Connected));
gamepad.connected = true;
}
let buttons = gamepad.gamepad.buttons();
for btn_index in 0..gamepad
.mapping
.buttons()
.len()
.min(buttons.length() as usize)
{
let (old_pressed, old_value) = gamepad.mapping.buttons()[btn_index];
let ev_code = crate::EvCode(gamepad.button_code(btn_index));
let button_object = GamepadButton::from(buttons.get(btn_index as u32));
let new_pressed = button_object.pressed();
let new_value = button_object.value();
if [BTN_LT2, BTN_RT2].contains(&ev_code.0) && old_value != new_value {
// Treat left and right triggers as axes so we get non-binary values.
// Button Pressed/Changed events are generated from the axis changed
// events later.
let value = (new_value * i32::MAX as f64) as i32;
self.event_cache
.push_back(Event::new(id, EventType::AxisValueChanged(value, ev_code)));
} else {
match (old_pressed, new_pressed) {
(false, true) => self
.event_cache
.push_back(Event::new(id, EventType::ButtonPressed(ev_code))),
(true, false) => self
.event_cache
.push_back(Event::new(id, EventType::ButtonReleased(ev_code))),
_ => (),
}
}
gamepad.mapping.buttons_mut()[btn_index] = (new_pressed, new_value);
}
let axes = gamepad.gamepad.axes();
for axis_index in 0..gamepad.mapping.axes().len().min(axes.length() as usize) {
let old_value = gamepad.mapping.axes()[axis_index];
let new_value = axes
.get(axis_index as u32)
.as_f64()
.expect("axes() should be an array of f64");
if old_value != new_value {
let ev_code = crate::EvCode(gamepad.axis_code(axis_index));
let value = (new_value * i32::MAX as f64) as i32;
self.event_cache
.push_back(Event::new(id, EventType::AxisValueChanged(value, ev_code)));
}
gamepad.mapping.axes_mut()[axis_index] = new_value;
}
} else {
// Create a disconnect event
if gamepad.connected {
self.event_cache
.push_back(Event::new(id, EventType::Disconnected));
gamepad.connected = false;
}
}
}
// Add new gamepads
for js_gamepad in self.new_web_gamepads.drain(..) {
let id = self.gamepads.len();
self.gamepads.push(Gamepad::new(js_gamepad));
// Create a connected event
let event = Event::new(id, EventType::Connected);
self.event_cache.push_back(event);
}
self.event_cache.pop_front()
}
pub(crate) fn next_event_blocking(&mut self, _timeout: Option<Duration>) -> Option<Event> {
unimplemented!("next_event_blocking is not supported on web. Use next_event.")
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum Mapping {
Standard {
buttons: [(bool, f64); 17],
axes: [f64; 4],
},
NoMapping {
buttons: Vec<(bool, f64)>,
axes: Vec<f64>,
},
}
impl Mapping {
fn buttons(&self) -> &[(bool, f64)] {
match self {
Mapping::Standard { buttons, .. } => buttons,
Mapping::NoMapping { buttons, .. } => buttons,
}
}
fn buttons_mut(&mut self) -> &mut [(bool, f64)] {
match self {
Mapping::Standard { buttons, .. } => &mut *buttons,
Mapping::NoMapping { buttons, .. } => &mut *buttons,
}
}
fn axes(&self) -> &[f64] {
match self {
Mapping::Standard { axes, .. } => axes,
Mapping::NoMapping { axes, .. } => axes,
}
}
fn axes_mut(&mut self) -> &mut [f64] {
match self {
Mapping::Standard { axes, .. } => &mut *axes,
Mapping::NoMapping { axes, .. } => &mut *axes,
}
}
}
#[derive(Debug)]
pub struct Gamepad {
uuid: Uuid,
gamepad: WebGamepad,
name: String,
vendor: Option<u16>,
product: Option<u16>,
mapping: Mapping,
connected: bool,
}
impl Gamepad {
fn new(gamepad: WebGamepad) -> Gamepad {
let name = gamepad.id();
// This regular expression extracts the vendor and product ID from the gamepad "id".
// Firefox:
// 054c-05c4-Sony Computer Entertainment Wireless Controller
// Chrome:
// Sony Computer Entertainment Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 05c4)
let regexp = RegExp::new(
r"(?:^([a-f0-9]{4})-([a-f0-9]{4})-)|(?:Vendor: ([a-f0-9]{4}) Product: ([a-f0-9]{4})\)$)",
"",
);
let (vendor, product) = if let Some(matches) = regexp.exec(&name) {
let parse_hex = |index| {
matches
.get(index)
.as_string()
.and_then(|id| u16::from_str_radix(&id, 16).ok())
};
(
parse_hex(1).or_else(|| parse_hex(3)),
parse_hex(2).or_else(|| parse_hex(4)),
)
} else {
(None, None)
};
let buttons = gamepad.buttons();
let button_iter = {
{
buttons.iter().map(GamepadButton::from)
}
};
let axes = gamepad.axes();
let axis_iter = {
{
axes.iter()
.map(|val| val.as_f64().expect("axes() should be an array of f64"))
}
};
let mapping = match gamepad.mapping() {
GamepadMappingType::Standard => {
let mut buttons = [(false, 0.0); 17];
let mut axes = [0.0; 4];
for (index, button) in button_iter.enumerate().take(buttons.len()) {
buttons[index] = (button.pressed(), button.value());
}
for (index, axis) in axis_iter.enumerate().take(axes.len()) {
axes[index] = axis;
}
Mapping::Standard { buttons, axes }
}
_ => {
let buttons = button_iter
.map(|button| (button.pressed(), button.value()))
.collect();
let axes = axis_iter.collect();
Mapping::NoMapping { buttons, axes }
}
};
Gamepad {
uuid: Uuid::nil(),
gamepad,
name,
vendor,
product,
mapping,
connected: true,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn vendor_id(&self) -> Option<u16> {
self.vendor
}
pub fn product_id(&self) -> Option<u16> {
self.product
}
pub fn is_connected(&self) -> bool {
self.gamepad.connected()
}
pub fn power_info(&self) -> PowerInfo {
PowerInfo::Unknown
}
pub fn is_ff_supported(&self) -> bool {
false
}
pub fn ff_device(&self) -> Option<FfDevice> {
None
}
pub fn buttons(&self) -> &[EvCode] {
&native_ev_codes::BUTTONS
}
pub fn axes(&self) -> &[EvCode] {
&native_ev_codes::AXES
}
fn button_code(&self, index: usize) -> EvCode {
self.buttons()
.get(index)
.copied()
.unwrap_or(EvCode(index as u8 + 31))
}
fn axis_code(&self, index: usize) -> EvCode {
self.axes()
.get(index)
.copied()
.unwrap_or_else(|| EvCode((index + self.mapping.buttons().len()) as u8 + 31))
}
pub(crate) fn axis_info(&self, _nec: EvCode) -> Option<&AxisInfo> {
if self.buttons().contains(&_nec) {
return Some(&AxisInfo {
min: 0,
max: i32::MAX,
deadzone: None,
});
}
Some(&AxisInfo {
min: i32::MIN,
max: i32::MAX,
deadzone: None,
})
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode(u8);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0 as u32
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.0.fmt(f)
}
}
#[derive(Debug, Copy, Clone)]
enum Error {
NoWindow,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
Error::NoWindow => f.write_str("window is not available"),
}
}
}
impl std::error::Error for Error {}
pub mod native_ev_codes {
use super::EvCode;
pub const AXIS_LSTICKX: EvCode = EvCode(0);
pub const AXIS_LSTICKY: EvCode = EvCode(1);
pub const AXIS_LEFTZ: EvCode = EvCode(2);
pub const AXIS_RSTICKX: EvCode = EvCode(3);
pub const AXIS_RSTICKY: EvCode = EvCode(4);
pub const AXIS_RIGHTZ: EvCode = EvCode(5);
pub const AXIS_DPADX: EvCode = EvCode(6);
pub const AXIS_DPADY: EvCode = EvCode(7);
pub const AXIS_RT: EvCode = EvCode(8);
pub const AXIS_LT: EvCode = EvCode(9);
pub const AXIS_RT2: EvCode = EvCode(10);
pub const AXIS_LT2: EvCode = EvCode(11);
pub const BTN_SOUTH: EvCode = EvCode(12);
pub const BTN_EAST: EvCode = EvCode(13);
pub const BTN_C: EvCode = EvCode(14);
pub const BTN_NORTH: EvCode = EvCode(15);
pub const BTN_WEST: EvCode = EvCode(16);
pub const BTN_Z: EvCode = EvCode(17);
pub const BTN_LT: EvCode = EvCode(18);
pub const BTN_RT: EvCode = EvCode(19);
pub const BTN_LT2: EvCode = EvCode(20);
pub const BTN_RT2: EvCode = EvCode(21);
pub const BTN_SELECT: EvCode = EvCode(22);
pub const BTN_START: EvCode = EvCode(23);
pub const BTN_MODE: EvCode = EvCode(24);
pub const BTN_LTHUMB: EvCode = EvCode(25);
pub const BTN_RTHUMB: EvCode = EvCode(26);
pub const BTN_DPAD_UP: EvCode = EvCode(27);
pub const BTN_DPAD_DOWN: EvCode = EvCode(28);
pub const BTN_DPAD_LEFT: EvCode = EvCode(29);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(30);
pub(super) static BUTTONS: [EvCode; 17] = [
BTN_SOUTH,
BTN_EAST,
BTN_WEST,
BTN_NORTH,
BTN_LT,
BTN_RT,
BTN_LT2,
BTN_RT2,
BTN_SELECT,
BTN_START,
BTN_LTHUMB,
BTN_RTHUMB,
BTN_DPAD_UP,
BTN_DPAD_DOWN,
BTN_DPAD_LEFT,
BTN_DPAD_RIGHT,
BTN_MODE,
];
pub(super) static AXES: [EvCode; 4] = [AXIS_LSTICKX, AXIS_LSTICKY, AXIS_RSTICKX, AXIS_RSTICKY];
}

View File

@@ -0,0 +1,7 @@
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = true;

View File

@@ -0,0 +1,37 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
use windows::Gaming::Input::Gamepad as WgiGamepad;
use windows::Gaming::Input::GamepadVibration;
#[derive(Debug)]
pub struct Device {
id: u32,
wgi_gamepad: Option<WgiGamepad>,
}
impl Device {
pub(crate) fn new(id: u32, wgi_gamepad: Option<WgiGamepad>) -> Self {
Device { id, wgi_gamepad }
}
pub fn set_ff_state(&mut self, strong: u16, weak: u16, _min_duration: Duration) {
if let Some(wgi_gamepad) = &self.wgi_gamepad {
if let Err(err) = wgi_gamepad.SetVibration(GamepadVibration {
LeftMotor: (strong as f64) / (u16::MAX as f64),
RightMotor: (weak as f64) / (u16::MAX as f64),
LeftTrigger: 0.0,
RightTrigger: 0.0,
}) {
error!(
"Failed to change FF state unknown error. ID = {}, error = {:?}.",
self.id, err
);
}
}
}
}

View File

@@ -0,0 +1,957 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::FfDevice;
use crate::native_ev_codes as nec;
use crate::{utils, AxisInfo, Event, EventType, PlatformError, PowerInfo};
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::thread;
use std::thread::JoinHandle;
use std::time::{Duration, Instant, SystemTime};
use uuid::Uuid;
use windows::core::HSTRING;
use windows::Devices::Power::BatteryReport;
use windows::Foundation::EventHandler;
use windows::Gaming::Input::{
GameControllerSwitchPosition, Gamepad as WgiGamepad, GamepadButtons, GamepadReading,
RawGameController,
};
use windows::System::Power::BatteryStatus;
const SDL_HARDWARE_BUS_USB: u32 = 0x03;
// const SDL_HARDWARE_BUS_BLUETOOTH: u32 = 0x05;
// The general consensus is that standard xbox controllers poll at ~125 hz which
// means 8 ms between updates.
// Seems like a good target for how often we update the background thread.
const EVENT_THREAD_SLEEP_TIME: u64 = 8;
const WGI_TO_GILRS_BUTTON_MAP: [(GamepadButtons, crate::EvCode); 14] = [
(GamepadButtons::DPadUp, nec::BTN_DPAD_UP),
(GamepadButtons::DPadDown, nec::BTN_DPAD_DOWN),
(GamepadButtons::DPadLeft, nec::BTN_DPAD_LEFT),
(GamepadButtons::DPadRight, nec::BTN_DPAD_RIGHT),
(GamepadButtons::Menu, nec::BTN_START),
(GamepadButtons::View, nec::BTN_SELECT),
(GamepadButtons::LeftThumbstick, nec::BTN_LTHUMB),
(GamepadButtons::RightThumbstick, nec::BTN_RTHUMB),
(GamepadButtons::LeftShoulder, nec::BTN_LT),
(GamepadButtons::RightShoulder, nec::BTN_RT),
(GamepadButtons::A, nec::BTN_SOUTH),
(GamepadButtons::B, nec::BTN_EAST),
(GamepadButtons::X, nec::BTN_WEST),
(GamepadButtons::Y, nec::BTN_NORTH),
];
/// This is similar to `gilrs_core::Event` but has a raw_game_controller that still needs to be
/// converted to a gilrs gamepad id.
#[derive(Debug)]
struct WgiEvent {
raw_game_controller: RawGameController,
event: EventType,
pub time: SystemTime,
}
impl WgiEvent {
fn new(raw_game_controller: RawGameController, event: EventType) -> Self {
let time = utils::time_now();
WgiEvent {
raw_game_controller,
event,
time,
}
}
}
#[derive(Debug)]
pub struct Gilrs {
gamepads: Vec<Gamepad>,
rx: Receiver<WgiEvent>,
join_handle: Option<JoinHandle<()>>,
stop_tx: Sender<()>,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let raw_game_controllers = RawGameController::RawGameControllers()
.map_err(|e| PlatformError::Other(Box::new(e)))?;
let count = raw_game_controllers
.Size()
.map_err(|e| PlatformError::Other(Box::new(e)))?;
// Intentionally avoiding using RawGameControllers.into_iter() as it triggers a crash when
// the app is run through steam.
// https://gitlab.com/gilrs-project/gilrs/-/issues/132
let gamepads = (0..count)
.map(|i| {
let controller = raw_game_controllers
.GetAt(i)
.map_err(|e| PlatformError::Other(Box::new(e)))?;
Ok(Gamepad::new(i, controller))
})
.collect::<Result<Vec<_>, _>>()?;
let (tx, rx) = mpsc::channel();
let (stop_tx, stop_rx) = mpsc::channel();
let join_handle = Some(Self::spawn_thread(tx, stop_rx));
Ok(Gilrs {
gamepads,
rx,
join_handle,
stop_tx,
})
}
fn spawn_thread(tx: Sender<WgiEvent>, stop_rx: Receiver<()>) -> JoinHandle<()> {
let added_tx = tx.clone();
let added_handler = EventHandler::<RawGameController>::new(move |_, g| {
if let Some(g) = g.as_ref() {
added_tx
.send(WgiEvent::new(g.clone(), EventType::Connected))
.expect("should be able to send to main thread");
}
Ok(())
});
let controller_added_token =
RawGameController::RawGameControllerAdded(&added_handler).unwrap();
let removed_tx = tx.clone();
let removed_handler = EventHandler::<RawGameController>::new(move |_, g| {
if let Some(g) = g.as_ref() {
removed_tx
.send(WgiEvent::new(g.clone(), EventType::Disconnected))
.expect("should be able to send to main thread");
}
Ok(())
});
let controller_removed_token =
RawGameController::RawGameControllerRemoved(&removed_handler).unwrap();
std::thread::Builder::new()
.name("gilrs".to_owned())
.spawn(move || {
let mut controllers: Vec<RawGameController> = Vec::new();
// To avoid allocating every update, store old and new readings for every controller
// and swap their memory
let mut readings: Vec<(HSTRING, Reading, Reading)> = Vec::new();
let mut last_failed_get_id: Option<Instant> = None;
loop {
match stop_rx.try_recv() {
Ok(_) => break,
Err(TryRecvError::Disconnected) => {
warn!("stop_rx channel disconnected prematurely");
break;
}
Err(TryRecvError::Empty) => {}
}
controllers.clear();
// Avoiding using RawGameControllers().into_iter() here due to it causing an
// unhandled exception when the app is running through steam.
// https://gitlab.com/gilrs-project/gilrs/-/issues/132
if let Ok(raw_game_controllers) = RawGameController::RawGameControllers() {
let count = raw_game_controllers.Size().unwrap_or_default();
for index in 0..count {
if let Ok(controller) = raw_game_controllers.GetAt(index) {
controllers.push(controller);
}
}
}
for controller in controllers.iter() {
let id: HSTRING = match controller.NonRoamableId() {
Ok(id) => id,
Err(e) => {
if last_failed_get_id.map_or(true, |x| x.elapsed().as_secs() > 59) {
error!(
"Failed to get gamepad id: {e}! Skipping reading events \
for this gamepad."
);
last_failed_get_id = Some(Instant::now());
}
continue;
}
};
// Find readings for this controller or insert new ones.
let index = match readings.iter().position(|(other_id, ..)| id == *other_id)
{
None => {
let reading = match WgiGamepad::FromGameController(controller) {
Ok(wgi_gamepad) => {
Reading::Gamepad(wgi_gamepad.GetCurrentReading().unwrap())
}
_ => Reading::Raw(RawGamepadReading::new(controller).unwrap()),
};
readings.push((id, reading.clone(), reading));
readings.len() - 1
}
Some(i) => i,
};
let (_, old_reading, new_reading) = &mut readings[index];
// Make last update's reading the old reading and get a new one.
std::mem::swap(old_reading, new_reading);
if let Err(e) = new_reading.update(controller) {
if e.code().is_err() {
error!("Reading::update() function failed with {e}");
}
}
// Skip if this is the same reading as the last one.
if old_reading.time() == new_reading.time() {
continue;
}
Reading::send_events_for_differences(
old_reading,
new_reading,
controller,
&tx,
);
}
thread::sleep(Duration::from_millis(EVENT_THREAD_SLEEP_TIME));
}
if let Err(e) =
RawGameController::RemoveRawGameControllerAdded(controller_added_token)
{
error!("Failed to remove RawGameControllerAdded event handler: {e}");
}
if let Err(e) =
RawGameController::RemoveRawGameControllerRemoved(controller_removed_token)
{
error!("Failed to remove RawGameControllerRemoved event handler: {e}");
}
})
.expect("failed to spawn thread")
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
self.rx
.try_recv()
.ok()
.map(|wgi_event: WgiEvent| self.handle_event(wgi_event))
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
if let Some(timeout) = timeout {
self.rx
.recv_timeout(timeout)
.ok()
.map(|wgi_event: WgiEvent| self.handle_event(wgi_event))
} else {
self.rx
.recv()
.ok()
.map(|wgi_event: WgiEvent| self.handle_event(wgi_event))
}
}
fn handle_event(&mut self, wgi_event: WgiEvent) -> Event {
// Find the index of the gamepad in our vec or insert it
let id = self
.gamepads
.iter()
.position(
|gamepad| match wgi_event.raw_game_controller.NonRoamableId() {
Ok(id) => id == gamepad.non_roamable_id,
_ => false,
},
)
.unwrap_or_else(|| {
self.gamepads.push(Gamepad::new(
self.gamepads.len() as u32,
wgi_event.raw_game_controller,
));
self.gamepads.len() - 1
});
match wgi_event.event {
EventType::Connected => self.gamepads[id].is_connected = true,
EventType::Disconnected => self.gamepads[id].is_connected = false,
_ => (),
}
Event {
id,
event: wgi_event.event,
time: wgi_event.time,
}
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
}
impl Drop for Gilrs {
fn drop(&mut self) {
if let Err(e) = self.stop_tx.send(()) {
warn!("Failed to send stop signal to thread: {e:?}");
}
if let Err(e) = self.join_handle.take().unwrap().join() {
warn!("Failed to join thread: {e:?}");
}
}
}
#[derive(Debug, Clone)]
struct RawGamepadReading {
axes: Vec<f64>,
buttons: Vec<bool>,
switches: Vec<GameControllerSwitchPosition>,
time: u64,
}
impl RawGamepadReading {
fn new(raw_game_controller: &RawGameController) -> windows::core::Result<Self> {
let axis_count = raw_game_controller.AxisCount()? as usize;
let button_count = raw_game_controller.ButtonCount()? as usize;
let switch_count = raw_game_controller.SwitchCount()? as usize;
let mut new = Self {
axes: vec![0.0; axis_count],
buttons: vec![false; button_count],
switches: vec![GameControllerSwitchPosition::default(); switch_count],
time: 0,
};
new.time = raw_game_controller.GetCurrentReading(
&mut new.buttons,
&mut new.switches,
&mut new.axes,
)?;
Ok(new)
}
fn update(&mut self, raw_game_controller: &RawGameController) -> windows::core::Result<()> {
self.time = raw_game_controller.GetCurrentReading(
&mut self.buttons,
&mut self.switches,
&mut self.axes,
)?;
Ok(())
}
}
/// Treats switches like a two axes similar to a Directional pad.
/// Returns a tuple containing the values of the x and y axis.
/// Value's range is -1 to 1.
fn direction_from_switch(switch: GameControllerSwitchPosition) -> (i32, i32) {
match switch {
GameControllerSwitchPosition::Up => (0, 1),
GameControllerSwitchPosition::Down => (0, -1),
GameControllerSwitchPosition::Right => (1, 0),
GameControllerSwitchPosition::Left => (-1, 0),
GameControllerSwitchPosition::UpLeft => (-1, 1),
GameControllerSwitchPosition::UpRight => (1, 1),
GameControllerSwitchPosition::DownLeft => (-1, -1),
GameControllerSwitchPosition::DownRight => (1, -1),
_ => (0, 0),
}
}
#[derive(Clone)]
enum Reading {
Raw(RawGamepadReading),
Gamepad(GamepadReading),
}
impl Reading {
fn time(&self) -> u64 {
match self {
Reading::Raw(r) => r.time,
Reading::Gamepad(r) => r.Timestamp,
}
}
fn update(&mut self, controller: &RawGameController) -> windows::core::Result<()> {
match self {
Reading::Raw(raw_reading) => {
raw_reading.update(controller)?;
}
Reading::Gamepad(gamepad_reading) => {
let gamepad: WgiGamepad = WgiGamepad::FromGameController(controller)?;
*gamepad_reading = gamepad.GetCurrentReading()?;
}
}
Ok(())
}
fn send_events_for_differences(
old: &Self,
new: &Self,
controller: &RawGameController,
tx: &Sender<WgiEvent>,
) {
match (old, new) {
// WGI RawGameController
(Reading::Raw(old), Reading::Raw(new)) => {
// Axis changes
for index in 0..new.axes.len() {
if old.axes.get(index) != new.axes.get(index) {
// https://github.com/libsdl-org/SDL/blob/6af17369ca773155bd7f39b8801725c4a6d52e4f/src/joystick/windows/SDL_windows_gaming_input.c#L863
let value = ((new.axes[index] * 65535.0) - 32768.0) as i32;
let event_type = EventType::AxisValueChanged(
value,
crate::EvCode(EvCode {
kind: EvCodeKind::Axis,
index: index as u32,
}),
);
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
}
for index in 0..new.buttons.len() {
if old.buttons.get(index) != new.buttons.get(index) {
let event_type = match new.buttons[index] {
true => EventType::ButtonPressed(crate::EvCode(EvCode {
kind: EvCodeKind::Button,
index: index as u32,
})),
false => EventType::ButtonReleased(crate::EvCode(EvCode {
kind: EvCodeKind::Button,
index: index as u32,
})),
};
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
}
for index in 0..old.switches.len() {
let (old_x, old_y) = direction_from_switch(old.switches[index]);
let (new_x, new_y) = direction_from_switch(new.switches[index]);
if old_x != new_x {
let event_type = EventType::AxisValueChanged(
new_x,
crate::EvCode(EvCode {
kind: EvCodeKind::Switch,
index: (index * 2) as u32,
}),
);
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
if old_y != new_y {
let event_type = EventType::AxisValueChanged(
-new_y,
crate::EvCode(EvCode {
kind: EvCodeKind::Switch,
index: (index * 2) as u32 + 1,
}),
);
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
}
}
// WGI Gamepad
(Reading::Gamepad(old), Reading::Gamepad(new)) => {
#[rustfmt::skip]
let axes = [
(new.LeftTrigger, old.LeftTrigger, nec::AXIS_LT2, 1.0),
(new.RightTrigger, old.RightTrigger, nec::AXIS_RT2, 1.0),
(new.LeftThumbstickX, old.LeftThumbstickX, nec::AXIS_LSTICKX, 1.0),
(new.LeftThumbstickY, old.LeftThumbstickY, nec::AXIS_LSTICKY, -1.0),
(new.RightThumbstickX, old.RightThumbstickX, nec::AXIS_RSTICKX, 1.0),
(new.RightThumbstickY, old.RightThumbstickY, nec::AXIS_RSTICKY, -1.0),
];
for (new, old, code, multiplier) in axes {
if new != old {
let _ = tx.send(WgiEvent::new(
controller.clone(),
EventType::AxisValueChanged(
(multiplier * new * i32::MAX as f64) as i32,
code,
),
));
}
}
for (current_button, ev_code) in WGI_TO_GILRS_BUTTON_MAP {
if (new.Buttons & current_button) != (old.Buttons & current_button) {
let _ = match new.Buttons & current_button != GamepadButtons::None {
true => tx.send(WgiEvent::new(
controller.clone(),
EventType::ButtonPressed(ev_code),
)),
false => tx.send(WgiEvent::new(
controller.clone(),
EventType::ButtonReleased(ev_code),
)),
};
}
}
}
(a, b) => {
warn!(
"WGI Controller changed from gamepad: {} to gamepad: {}. Could not compare \
last update.",
a.is_gamepad(),
b.is_gamepad()
);
#[cfg(debug_assertions)]
panic!(
"Controllers shouldn't change type between updates, likely programmer error"
);
}
}
}
fn is_gamepad(&self) -> bool {
matches!(self, Reading::Gamepad(_))
}
}
#[derive(Debug)]
pub struct Gamepad {
id: u32,
name: String,
uuid: Uuid,
is_connected: bool,
/// This is the generic controller handle without any mappings
/// https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.rawgamecontroller
raw_game_controller: RawGameController,
/// An ID for this device that will survive disconnects and restarts.
/// [NonRoamableIds](https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.rawgamecontroller.nonroamableid)
///
/// Changes if plugged into a different port and is not the same between different applications
/// or PCs.
non_roamable_id: HSTRING,
/// If the controller has a [Gamepad](https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.gamepad?view=winrt-22621)
/// mapping, this is used to access the mapped values.
wgi_gamepad: Option<WgiGamepad>,
axes: Option<Vec<EvCode>>,
buttons: Option<Vec<EvCode>>,
}
impl Gamepad {
fn new(id: u32, raw_game_controller: RawGameController) -> Gamepad {
let is_connected = true;
let non_roamable_id = raw_game_controller.NonRoamableId().unwrap();
// See if we can cast this to a windows definition of a gamepad
let wgi_gamepad = WgiGamepad::FromGameController(&raw_game_controller).ok();
let name = match raw_game_controller.DisplayName() {
Ok(hstring) => hstring.to_string_lossy(),
Err(_) => "unknown".to_string(),
};
let uuid = match wgi_gamepad.is_some() {
true => Uuid::nil(),
false => {
let vendor_id = raw_game_controller.HardwareVendorId().unwrap_or(0).to_be();
let product_id = raw_game_controller.HardwareProductId().unwrap_or(0).to_be();
let version = 0;
// SDL uses the SDL_HARDWARE_BUS_BLUETOOTH bustype for IsWireless devices:
// https://github.com/libsdl-org/SDL/blob/294ccba0a23b37fffef62189423444f93732e565/src/joystick/windows/SDL_windows_gaming_input.c#L335-L338
// In my testing though, it caused my controllers to not find mappings.
// SDL only uses their WGI implementation for UWP apps so I guess it hasn't been
// used enough for people to submit mappings with the different bustype.
let bustype = SDL_HARDWARE_BUS_USB.to_be();
Uuid::from_fields(
bustype,
vendor_id,
0,
&[
(product_id >> 8) as u8,
product_id as u8,
0,
0,
(version >> 8) as u8,
version as u8,
0,
0,
],
)
}
};
let mut gamepad = Gamepad {
id,
name,
uuid,
is_connected,
raw_game_controller,
non_roamable_id,
wgi_gamepad,
axes: None,
buttons: None,
};
if gamepad.wgi_gamepad.is_none() {
gamepad.collect_axes_and_buttons();
}
gamepad
}
pub fn name(&self) -> &str {
&self.name
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn vendor_id(&self) -> Option<u16> {
self.raw_game_controller.HardwareVendorId().ok()
}
pub fn product_id(&self) -> Option<u16> {
self.raw_game_controller.HardwareProductId().ok()
}
pub fn is_connected(&self) -> bool {
self.is_connected
}
pub fn power_info(&self) -> PowerInfo {
self.power_info_err().unwrap_or(PowerInfo::Unknown)
}
/// Using this function so we can easily map errors to unknown
fn power_info_err(&self) -> windows::core::Result<PowerInfo> {
if !self.raw_game_controller.IsWireless()? {
return Ok(PowerInfo::Wired);
}
let report: BatteryReport = self.raw_game_controller.TryGetBatteryReport()?;
let status: BatteryStatus = report.Status()?;
let power_info = match status {
BatteryStatus::Discharging | BatteryStatus::Charging => {
let full = report.FullChargeCapacityInMilliwattHours()?.GetInt32()? as f32;
let remaining = report.RemainingCapacityInMilliwattHours()?.GetInt32()? as f32;
let percent: u8 = ((remaining / full) * 100.0) as u8;
match status {
_ if percent == 100 => PowerInfo::Charged,
BatteryStatus::Discharging => PowerInfo::Discharging(percent),
BatteryStatus::Charging => PowerInfo::Charging(percent),
_ => unreachable!(),
}
}
BatteryStatus::NotPresent => PowerInfo::Wired,
BatteryStatus::Idle => PowerInfo::Charged,
BatteryStatus(_) => PowerInfo::Unknown,
};
Ok(power_info)
}
pub fn is_ff_supported(&self) -> bool {
self.wgi_gamepad.is_some()
&& self
.raw_game_controller
.ForceFeedbackMotors()
.ok()
.map(|motors| motors.First())
.is_some()
}
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice::new(self.id, self.wgi_gamepad.clone()))
}
pub fn buttons(&self) -> &[EvCode] {
match &self.buttons {
None => &native_ev_codes::BUTTONS,
Some(buttons) => buttons,
}
}
pub fn axes(&self) -> &[EvCode] {
match &self.axes {
None => &native_ev_codes::AXES,
Some(axes) => axes,
}
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
// If it isn't a Windows "Gamepad" then return what we want SDL mappings to be able to use
if self.wgi_gamepad.is_none() {
return match nec.kind {
EvCodeKind::Button => None,
EvCodeKind::Axis => Some(&AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: None,
}),
EvCodeKind::Switch => Some(&AxisInfo {
min: -1,
max: 1,
deadzone: None,
}),
};
}
// For Windows Gamepads, the triggers are 0.0 to 1.0 and the thumbsticks are -1.0 to 1.0
// https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.gamepadreading#fields
// Since Gilrs processes axis data as integers, the input has already been multiplied by
// i32::MAX in the joy_value method.
match nec {
native_ev_codes::AXIS_LT2 | native_ev_codes::AXIS_RT2 => Some(&AxisInfo {
min: 0,
max: i32::MAX,
deadzone: None,
}),
_ => Some(&AxisInfo {
min: i32::MIN,
max: i32::MAX,
deadzone: None,
}),
}
}
fn collect_axes_and_buttons(&mut self) {
let axis_count = self.raw_game_controller.AxisCount().unwrap() as u32;
let button_count = self.raw_game_controller.ButtonCount().unwrap() as u32;
let switch_count = self.raw_game_controller.SwitchCount().unwrap() as u32;
self.buttons = Some(
(0..button_count)
.map(|index| EvCode {
kind: EvCodeKind::Button,
index,
})
.collect(),
);
self.axes = Some(
(0..axis_count)
.map(|index| EvCode {
kind: EvCodeKind::Axis,
index,
})
.chain(
// Treat switches as two axes
(0..switch_count).flat_map(|index| {
[
EvCode {
kind: EvCodeKind::Switch,
index: index * 2,
},
EvCode {
kind: EvCodeKind::Switch,
index: (index * 2) + 1,
},
]
}),
)
.collect(),
);
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum EvCodeKind {
Button = 0,
Axis,
Switch,
}
impl Display for EvCodeKind {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
EvCodeKind::Button => "Button",
EvCodeKind::Axis => "Axis",
EvCodeKind::Switch => "Switch",
}
.fmt(f)
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode {
kind: EvCodeKind,
index: u32,
}
impl EvCode {
pub fn into_u32(self) -> u32 {
((self.kind as u32) << 16) | self.index
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}({})", self.kind, self.index)
}
}
pub mod native_ev_codes {
use super::{EvCode, EvCodeKind};
pub const AXIS_LSTICKX: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 0,
};
pub const AXIS_LSTICKY: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 1,
};
pub const AXIS_RSTICKX: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 2,
};
pub const AXIS_LT2: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 3,
};
pub const AXIS_RT2: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 4,
};
pub const AXIS_RSTICKY: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 5,
};
pub const AXIS_RT: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 6,
};
pub const AXIS_LT: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 7,
};
pub const AXIS_LEFTZ: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 8,
};
pub const AXIS_RIGHTZ: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 9,
};
pub const AXIS_DPADX: EvCode = EvCode {
kind: EvCodeKind::Switch,
index: 0,
};
pub const AXIS_DPADY: EvCode = EvCode {
kind: EvCodeKind::Switch,
index: 1,
};
pub const BTN_WEST: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 0,
};
pub const BTN_SOUTH: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 1,
};
pub const BTN_EAST: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 2,
};
pub const BTN_NORTH: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 3,
};
pub const BTN_LT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 4,
};
pub const BTN_RT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 5,
};
pub const BTN_LT2: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 6,
};
pub const BTN_RT2: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 7,
};
pub const BTN_SELECT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 8,
};
pub const BTN_START: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 9,
};
pub const BTN_LTHUMB: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 10,
};
pub const BTN_RTHUMB: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 11,
};
pub const BTN_MODE: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 12,
};
pub const BTN_C: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 13,
};
pub const BTN_Z: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 14,
};
// The DPad for DS4 controllers is a hat/switch that gets mapped to the DPad native event
// code buttons. These "buttons" don't exist on the DS4 controller, so it doesn't matter
// what the index is, but if it overlaps with an existing button it will send the event
// for the overlapping button as a dpad button instead of unknown.
// By using a large index it should avoid this.
pub const BTN_DPAD_UP: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX - 3,
};
pub const BTN_DPAD_RIGHT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX - 2,
};
pub const BTN_DPAD_DOWN: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX - 1,
};
pub const BTN_DPAD_LEFT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX,
};
pub(super) static BUTTONS: [EvCode; 14] = [
BTN_WEST,
BTN_SOUTH,
BTN_EAST,
BTN_NORTH,
BTN_LT,
BTN_RT,
BTN_SELECT,
BTN_START,
BTN_LTHUMB,
BTN_RTHUMB,
BTN_DPAD_UP,
BTN_DPAD_RIGHT,
BTN_DPAD_DOWN,
BTN_DPAD_LEFT,
];
pub(super) static AXES: [EvCode; 6] = [
AXIS_LSTICKX,
AXIS_LSTICKY,
AXIS_RSTICKX,
AXIS_LT2,
AXIS_RT2,
AXIS_RSTICKY,
];
}

View File

@@ -0,0 +1,13 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = true;

View File

@@ -0,0 +1,40 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use rusty_xinput::{self, XInputHandle, XInputUsageError};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug)]
pub struct Device {
id: u32,
xinput_handle: Arc<XInputHandle>,
}
impl Device {
pub(crate) fn new(id: u32, xinput_handle: Arc<XInputHandle>) -> Self {
Device { id, xinput_handle }
}
pub fn set_ff_state(&mut self, strong: u16, weak: u16, _min_duration: Duration) {
match self.xinput_handle.set_state(self.id, strong, weak) {
Ok(()) => (),
Err(XInputUsageError::DeviceNotConnected) => {
error!(
"Failed to change FF state gamepad with id {} is no longer connected.",
self.id
);
}
Err(err) => {
error!(
"Failed to change FF state unknown error. ID = {}, error = {:?}.",
self.id, err
);
}
}
}
}

View File

@@ -0,0 +1,641 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::FfDevice;
use crate::{AxisInfo, Event, EventType, PlatformError, PowerInfo};
use std::error::Error as StdError;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::sync::{
mpsc::{self, Receiver, Sender},
Arc,
};
use std::time::Duration;
use std::{mem, thread};
use rusty_xinput::{
BatteryLevel, BatteryType, XInputHandle, XInputLoadingFailure, XInputState, XInputUsageError,
};
use uuid::Uuid;
use winapi::um::xinput::{
XINPUT_GAMEPAD as XGamepad, XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_BACK,
XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT,
XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_LEFT_THUMB,
XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_RIGHT_THUMB, XINPUT_GAMEPAD_START,
XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, XINPUT_STATE as XState,
};
// Chosen by dice roll ;)
const EVENT_THREAD_SLEEP_TIME: u64 = 10;
const ITERATIONS_TO_CHECK_IF_CONNECTED: u64 = 100;
const MAX_XINPUT_CONTROLLERS: usize = 4;
#[derive(Debug)]
pub struct Gilrs {
gamepads: [Gamepad; MAX_XINPUT_CONTROLLERS],
rx: Receiver<Event>,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let xinput_handle = XInputHandle::load_default()
.map_err(|e| PlatformError::Other(Box::new(Error::FailedToLoadDll(e))))?;
let xinput_handle = Arc::new(xinput_handle);
let gamepad_ids: [usize; MAX_XINPUT_CONTROLLERS] = std::array::from_fn(|idx| idx);
// Map controller IDs to Gamepads
let gamepads = gamepad_ids.map(|id| Gamepad::new(id as u32, xinput_handle.clone()));
let mut connected: [bool; MAX_XINPUT_CONTROLLERS] = Default::default();
// Iterate through each controller ID and set connected state
for id in 0..MAX_XINPUT_CONTROLLERS {
connected[id] = gamepads[id].is_connected;
}
let (tx, rx) = mpsc::channel();
Self::spawn_thread(tx, connected, xinput_handle.clone());
// Coerce gamepads vector to slice
Ok(Gilrs { gamepads, rx })
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
let ev = self.rx.try_recv().ok();
self.handle_evevnt(ev);
ev
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
let ev = if let Some(tiemout) = timeout {
self.rx.recv_timeout(tiemout).ok()
} else {
self.rx.recv().ok()
};
self.handle_evevnt(ev);
ev
}
fn handle_evevnt(&mut self, ev: Option<Event>) {
if let Some(ev) = ev {
match ev.event {
EventType::Connected => self.gamepads[ev.id].is_connected = true,
EventType::Disconnected => self.gamepads[ev.id].is_connected = false,
_ => (),
}
}
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
fn spawn_thread(
tx: Sender<Event>,
connected: [bool; MAX_XINPUT_CONTROLLERS],
xinput_handle: Arc<XInputHandle>,
) {
std::thread::Builder::new()
.name("gilrs".to_owned())
.spawn(move || unsafe {
// Issue #70 fix - Maintain a prev_state per controller id. Otherwise the loop will compare the prev_state of a different controller.
let mut prev_states: [XState; MAX_XINPUT_CONTROLLERS] =
[mem::zeroed::<XState>(); MAX_XINPUT_CONTROLLERS];
let mut connected = connected;
let mut counter = 0;
loop {
for id in 0..MAX_XINPUT_CONTROLLERS {
if *connected.get_unchecked(id)
|| counter % ITERATIONS_TO_CHECK_IF_CONNECTED == 0
{
match xinput_handle.get_state(id as u32) {
Ok(XInputState { raw: state }) => {
if !connected[id] {
connected[id] = true;
let _ = tx.send(Event::new(id, EventType::Connected));
}
if state.dwPacketNumber != prev_states[id].dwPacketNumber {
Self::compare_state(
id,
&state.Gamepad,
&prev_states[id].Gamepad,
&tx,
);
prev_states[id] = state;
}
}
Err(XInputUsageError::DeviceNotConnected) if connected[id] => {
connected[id] = false;
let _ = tx.send(Event::new(id, EventType::Disconnected));
}
Err(XInputUsageError::DeviceNotConnected) => (),
Err(e) => error!("Failed to get gamepad state: {:?}", e),
}
}
}
counter = counter.wrapping_add(1);
thread::sleep(Duration::from_millis(EVENT_THREAD_SLEEP_TIME));
}
})
.expect("failed to spawn thread");
}
fn compare_state(id: usize, g: &XGamepad, pg: &XGamepad, tx: &Sender<Event>) {
if g.bLeftTrigger != pg.bLeftTrigger {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.bLeftTrigger as i32,
crate::native_ev_codes::AXIS_LT2,
),
));
}
if g.bRightTrigger != pg.bRightTrigger {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.bRightTrigger as i32,
crate::native_ev_codes::AXIS_RT2,
),
));
}
if g.sThumbLX != pg.sThumbLX {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbLX as i32,
crate::native_ev_codes::AXIS_LSTICKX,
),
));
}
if g.sThumbLY != pg.sThumbLY {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbLY as i32,
crate::native_ev_codes::AXIS_LSTICKY,
),
));
}
if g.sThumbRX != pg.sThumbRX {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbRX as i32,
crate::native_ev_codes::AXIS_RSTICKX,
),
));
}
if g.sThumbRY != pg.sThumbRY {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbRY as i32,
crate::native_ev_codes::AXIS_RSTICKY,
),
));
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_UP) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_UP != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_UP),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_UP),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_DOWN) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_DOWN != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_DOWN),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_DOWN),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_LEFT) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_LEFT != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_LEFT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_LEFT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_RIGHT) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_RIGHT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_RIGHT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_START) {
let _ = match g.wButtons & XINPUT_GAMEPAD_START != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_START),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_START),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_BACK) {
let _ = match g.wButtons & XINPUT_GAMEPAD_BACK != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_SELECT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_SELECT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_LEFT_THUMB) {
let _ = match g.wButtons & XINPUT_GAMEPAD_LEFT_THUMB != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_LTHUMB),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_LTHUMB),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_RIGHT_THUMB) {
let _ = match g.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_RTHUMB),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_RTHUMB),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_LEFT_SHOULDER) {
let _ = match g.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_LT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_LT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_RIGHT_SHOULDER) {
let _ = match g.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_RT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_RT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_A) {
let _ = match g.wButtons & XINPUT_GAMEPAD_A != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_SOUTH),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_SOUTH),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_B) {
let _ = match g.wButtons & XINPUT_GAMEPAD_B != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_EAST),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_EAST),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_X) {
let _ = match g.wButtons & XINPUT_GAMEPAD_X != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_WEST),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_WEST),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_Y) {
let _ = match g.wButtons & XINPUT_GAMEPAD_Y != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_NORTH),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_NORTH),
)),
};
}
}
}
#[derive(Debug)]
pub struct Gamepad {
uuid: Uuid,
id: u32,
is_connected: bool,
xinput_handle: Arc<XInputHandle>,
}
impl Gamepad {
fn new(id: u32, xinput_handle: Arc<XInputHandle>) -> Gamepad {
let is_connected = xinput_handle.get_state(id).is_ok();
Gamepad {
uuid: Uuid::nil(),
id,
is_connected,
xinput_handle,
}
}
pub fn name(&self) -> &str {
"Xbox Controller"
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn vendor_id(&self) -> Option<u16> {
None
}
pub fn product_id(&self) -> Option<u16> {
None
}
pub fn is_connected(&self) -> bool {
self.is_connected
}
pub fn power_info(&self) -> PowerInfo {
match self.xinput_handle.get_gamepad_battery_information(self.id) {
Ok(binfo) => match binfo.battery_type {
BatteryType::WIRED => PowerInfo::Wired,
BatteryType::ALKALINE | BatteryType::NIMH => {
let lvl = match binfo.battery_level {
BatteryLevel::EMPTY => 0,
BatteryLevel::LOW => 33,
BatteryLevel::MEDIUM => 67,
BatteryLevel::FULL => 100,
lvl => {
trace!("Unexpected battery level: {}", lvl.0);
100
}
};
if lvl == 100 {
PowerInfo::Charged
} else {
PowerInfo::Discharging(lvl)
}
}
_ => PowerInfo::Unknown,
},
Err(e) => {
debug!("Failed to get battery info: {:?}", e);
PowerInfo::Unknown
}
}
}
pub fn is_ff_supported(&self) -> bool {
true
}
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice::new(self.id, self.xinput_handle.clone()))
}
pub fn buttons(&self) -> &[EvCode] {
&native_ev_codes::BUTTONS
}
pub fn axes(&self) -> &[EvCode] {
&native_ev_codes::AXES
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
native_ev_codes::AXES_INFO
.get(nec.0 as usize)
.and_then(|o| o.as_ref())
}
}
#[inline(always)]
fn is_mask_eq(l: u16, r: u16, mask: u16) -> bool {
(l & mask != 0) == (r & mask != 0)
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode(u8);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0 as u32
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.0.fmt(f)
}
}
#[derive(Debug)]
enum Error {
FailedToLoadDll(XInputLoadingFailure),
}
impl StdError for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Error::FailedToLoadDll(e) => {
f.write_fmt(format_args!("Failed to load XInput DLL {:?}", e))
}
}
}
}
pub mod native_ev_codes {
use winapi::um::xinput::{
XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE,
XINPUT_GAMEPAD_TRIGGER_THRESHOLD,
};
use super::EvCode;
use crate::AxisInfo;
pub const AXIS_LSTICKX: EvCode = EvCode(0);
pub const AXIS_LSTICKY: EvCode = EvCode(1);
pub const AXIS_LEFTZ: EvCode = EvCode(2);
pub const AXIS_RSTICKX: EvCode = EvCode(3);
pub const AXIS_RSTICKY: EvCode = EvCode(4);
pub const AXIS_RIGHTZ: EvCode = EvCode(5);
pub const AXIS_DPADX: EvCode = EvCode(6);
pub const AXIS_DPADY: EvCode = EvCode(7);
pub const AXIS_RT: EvCode = EvCode(8);
pub const AXIS_LT: EvCode = EvCode(9);
pub const AXIS_RT2: EvCode = EvCode(10);
pub const AXIS_LT2: EvCode = EvCode(11);
pub const BTN_SOUTH: EvCode = EvCode(12);
pub const BTN_EAST: EvCode = EvCode(13);
pub const BTN_C: EvCode = EvCode(14);
pub const BTN_NORTH: EvCode = EvCode(15);
pub const BTN_WEST: EvCode = EvCode(16);
pub const BTN_Z: EvCode = EvCode(17);
pub const BTN_LT: EvCode = EvCode(18);
pub const BTN_RT: EvCode = EvCode(19);
pub const BTN_LT2: EvCode = EvCode(20);
pub const BTN_RT2: EvCode = EvCode(21);
pub const BTN_SELECT: EvCode = EvCode(22);
pub const BTN_START: EvCode = EvCode(23);
pub const BTN_MODE: EvCode = EvCode(24);
pub const BTN_LTHUMB: EvCode = EvCode(25);
pub const BTN_RTHUMB: EvCode = EvCode(26);
pub const BTN_DPAD_UP: EvCode = EvCode(27);
pub const BTN_DPAD_DOWN: EvCode = EvCode(28);
pub const BTN_DPAD_LEFT: EvCode = EvCode(29);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(30);
pub(super) static BUTTONS: [EvCode; 15] = [
BTN_SOUTH,
BTN_EAST,
BTN_NORTH,
BTN_WEST,
BTN_LT,
BTN_RT,
BTN_SELECT,
BTN_START,
BTN_MODE,
BTN_LTHUMB,
BTN_RTHUMB,
BTN_DPAD_UP,
BTN_DPAD_DOWN,
BTN_DPAD_LEFT,
BTN_DPAD_RIGHT,
];
pub(super) static AXES: [EvCode; 6] = [
AXIS_LSTICKX,
AXIS_LSTICKY,
AXIS_RSTICKX,
AXIS_RSTICKY,
AXIS_RT2,
AXIS_LT2,
];
pub(super) static AXES_INFO: [Option<AxisInfo>; 12] = [
// LeftStickX
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE as u32),
}),
// LeftStickY
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE as u32),
}),
// LeftZ
None,
// RightStickX
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE as u32),
}),
// RightStickY
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE as u32),
}),
// RightZ
None,
// DPadX
None,
// DPadY
None,
// RightTrigger
None,
// LeftTrigger
None,
// RightTrigger2
Some(AxisInfo {
min: u8::MIN as i32,
max: u8::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_TRIGGER_THRESHOLD as u32),
}),
// LeftTrigger2
Some(AxisInfo {
min: u8::MIN as i32,
max: u8::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_TRIGGER_THRESHOLD as u32),
}),
];
}

View File

@@ -0,0 +1,13 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = false;

21
vendor/gilrs-core/src/utils.rs vendored Normal file
View File

@@ -0,0 +1,21 @@
use std::time::SystemTime;
/// Returns true if nth bit in array is 1.
#[allow(dead_code)]
pub(crate) fn test_bit(n: u16, array: &[u8]) -> bool {
(array[(n / 8) as usize] >> (n % 8)) & 1 != 0
}
#[cfg(not(target_arch = "wasm32"))]
pub fn time_now() -> SystemTime {
SystemTime::now()
}
#[cfg(target_arch = "wasm32")]
pub fn time_now() -> SystemTime {
use js_sys::Date;
use std::time::Duration;
let offset = Duration::from_millis(Date::now() as u64);
SystemTime::UNIX_EPOCH + offset
}