x360_w_raw_gadget/lib.rs
1//! # x360-w-raw-gadget
2//!
3//! Emulates an **Xbox 360 wireless receiver** (up to 4 controllers) on Linux
4//! via the [`raw-gadget`](https://docs.kernel.org/usb/raw-gadget.html) kernel
5//! interface.
6//!
7//! The crate is split into two layers:
8//!
9//! - **Portable core** — pure Rust, no kernel dependency, fully unit-testable
10//! on any Linux machine without USB hardware.
11//! - **Hardware transport** (`transport_hw`, Linux only) — drives the actual
12//! `/dev/raw-gadget` device on a USB OTG capable board (e.g. Raspberry Pi
13//! Zero 2W).
14//!
15//! A **C-callable shared library** (`capi`, Linux only) is also built
16//! (`libx360_w_raw_gadget.so`) for use from Python/ctypes or any other
17//! language with a C FFI.
18//!
19//! ## Quick start — Rust
20//!
21//! ```rust,no_run
22//! use x360_w_raw_gadget::{Button, ConfigDescriptorSet, InputState, WirelessReceiver};
23//! # #[cfg(target_os = "linux")]
24//! use x360_w_raw_gadget::RawGadgetTransport;
25//!
26//! # #[cfg(target_os = "linux")]
27//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
28//! let config = ConfigDescriptorSet::new(1)?;
29//! let transport = RawGadgetTransport::new(&config, "3f980000.usb", "3f980000.usb")?;
30//! let mut receiver = WirelessReceiver::new(config, Box::new(transport));
31//!
32//! let state = InputState::default()
33//! .with_button(Button::A, true)
34//! .with_left_stick(-16000, 8000)
35//! .with_left_trigger(128);
36//!
37//! receiver.slot_mut(0).unwrap().set_state(state);
38//! receiver.send_input(0)?;
39//!
40//! if let Some(r) = receiver.poll_rumble(0)? {
41//! println!("rumble left={} right={}", r.left_motor, r.right_motor);
42//! }
43//! if let Some(l) = receiver.poll_led(0)? {
44//! println!("led {:?}", l.animation);
45//! }
46//! # Ok(())
47//! # }
48//! # #[cfg(not(target_os = "linux"))]
49//! # fn main() {}
50//! ```
51//!
52//! ## Testing without hardware
53//!
54//! The core is fully testable using [`MockTransport`](session::MockTransport):
55//!
56//! ```rust
57//! use x360_w_raw_gadget::{
58//! Button, ConfigDescriptorSet, InputState, WirelessReceiver,
59//! session::MockTransport,
60//! protocol::{OutputReport, RumbleReport},
61//! };
62//!
63//! let config = ConfigDescriptorSet::new(1).unwrap();
64//! let mut transport = MockTransport::new();
65//! transport.queue_output(0, OutputReport::Rumble(RumbleReport { left_motor: 128, right_motor: 64 }));
66//!
67//! let mut receiver = WirelessReceiver::new(config, Box::new(transport));
68//! receiver.slot_mut(0).unwrap().set_state(
69//! InputState::default().with_button(Button::A, true)
70//! );
71//! receiver.send_input(0).unwrap();
72//!
73//! let rumble = receiver.poll_rumble(0).unwrap();
74//! assert!(rumble.is_some());
75//! ```
76//!
77//! ## Modules
78//!
79//! | Module | Contents |
80//! |--------|----------|
81//! | [`controller`] | [`InputState`], [`Button`], [`ControllerSlot`] |
82//! | [`protocol`] | [`InputReport`], [`RumbleReport`], [`LedReport`], [`LedAnimation`], [`OutputReport`] |
83//! | [`descriptors`] | [`ConfigDescriptorSet`], [`ReceiverIdentity`] |
84//! | [`session`] | [`WirelessReceiver`], [`Transport`], [`MockTransport`](session::MockTransport) |
85//! | `transport_hw` *(Linux only)* | [`RawGadgetTransport`] |
86//! | `capi` *(Linux only)* | C FFI symbols (`x360_open`, `x360_send`, …) |
87
88use std::sync::atomic::{AtomicBool, Ordering};
89
90pub mod controller;
91pub mod descriptors;
92pub mod ffi;
93pub mod protocol;
94pub mod session;
95#[cfg(target_os = "linux")]
96pub mod transport_hw;
97#[cfg(target_os = "linux")]
98pub mod capi;
99#[cfg(target_os = "linux")]
100pub mod udc;
101
102pub use controller::{Button, ControllerSlot, InputState};
103pub use descriptors::{ConfigDescriptorSet, DescriptorError, ReceiverIdentity, StringDescriptorMap};
104pub use protocol::{InputReport, LedAnimation, LedReport, OutputReport, RumbleReport};
105pub use session::{Transport, WirelessReceiver};
106#[cfg(target_os = "linux")]
107pub use transport_hw::RawGadgetTransport;
108#[cfg(target_os = "linux")]
109pub use udc::detect_udc;
110
111/// Global debug flag. When `true`, the hardware transport prints verbose logs
112/// to stderr. Set via [`set_debug`], the `--debug` CLI flag, or the
113/// `X360_DEBUG` environment variable.
114pub static DEBUG: AtomicBool = AtomicBool::new(false);
115
116/// Enable or disable verbose debug logging to stderr.
117///
118/// Equivalent to setting the `X360_DEBUG` environment variable or passing
119/// `--debug` to the `x360-w-gadget` binary.
120pub fn set_debug(enable: bool) {
121 DEBUG.store(enable, Ordering::Relaxed);
122}
123
124/// Internal logging macro: prints only when [`DEBUG`] is set.
125#[macro_export]
126macro_rules! dbg_log {
127 ($($arg:tt)*) => {
128 if $crate::DEBUG.load(std::sync::atomic::Ordering::Relaxed) {
129 eprintln!($($arg)*);
130 }
131 };
132}