From 85110e1d8e392da29323da43fe581d0dfba3cbb5 Mon Sep 17 00:00:00 2001 From: Osspial Date: Thu, 20 Jun 2019 15:13:07 -0400 Subject: [PATCH] Overhaul device events API and add gamepad support on Windows (#804) * Initial implementation * Corrected RAWINPUT buffer sizing * Mostly complete XInput implementation * XInput triggers * Add preliminary CHANGELOG entry. * match unix common API to evl 2.0 * wayland: eventloop2.0 * make EventLoopProxy require T: 'static * Revamp device event API, as well as several misc. fixes on Windows: * When you have multiple windows, you no longer receive duplicate device events * Mouse Device Events now send X-button input * Mouse Device Events now send horizontal scroll wheel input * Add MouseEvent documentation and Device ID debug passthrough * Improve type safety on get_raw_input_data * Remove button_id field from MouseEvent::Button in favor of utton * Remove regex dependency on Windows * Remove axis filtering in XInput * Make gamepads not use lazy_static * Publicly expose gamepad rumble * Unstack DeviceEvent and fix examples/tests * Add HANDLE retrieval method to DeviceExtWindows * Add distinction between non-joystick axes and joystick axes. This helps with properly calculating the deadzone for controller joysticks. One potential issue is that the `Stick` variant isn't used for *all* joysticks, which could be potentially confusing - for example, raw input joysticks will never use the `Stick` variant because we don't understand the semantic meaning of raw input joystick axes. * Add ability to get gamepad port * Fix xinput controller hot swapping * Add functions for enumerating attached devices * Clamp input to [0.0, 1.0] on gamepad rumble * Expose gamepad rumble errors * Add method to check if device is still connected * Add docs * Rename AxisHint and ButtonHint to GamepadAxis and GamepadButton * Add CHANGELOG entry * Update CHANGELOG.md * Add HidId and MovedAbsolute * Fix xinput deprecation warnings * Add ability to retrieve gamepad battery level * Fix weird imports in gamepad example * Update CHANGELOG.md * Resolve francesca64 comments --- CHANGELOG.md | 14 + Cargo.toml | 3 + examples/cursor.rs | 10 +- examples/cursor_grab.rs | 14 +- examples/fullscreen.rs | 12 +- examples/gamepad.rs | 52 +++ examples/gamepad_rumble.rs | 60 +++ examples/handling_close.rs | 12 +- examples/multithreaded.rs | 24 +- examples/multiwindow.rs | 10 +- examples/resizable.rs | 12 +- src/event.rs | 162 ++----- src/event/device.rs | 362 +++++++++++++++ src/lib.rs | 1 + src/platform/windows.rs | 40 +- src/platform_impl/windows/event_loop.rs | 583 +++++++++++++++--------- src/platform_impl/windows/gamepad.rs | 89 ++++ src/platform_impl/windows/mod.rs | 181 ++++++-- src/platform_impl/windows/raw_input.rs | 517 +++++++++++++++++++-- src/platform_impl/windows/util.rs | 11 +- src/platform_impl/windows/window.rs | 13 +- src/platform_impl/windows/xinput.rs | 334 ++++++++++++++ src/util.rs | 29 ++ tests/send_objects.rs | 4 +- 24 files changed, 2074 insertions(+), 475 deletions(-) create mode 100644 examples/gamepad.rs create mode 100644 examples/gamepad_rumble.rs create mode 100644 src/event/device.rs create mode 100644 src/platform_impl/windows/gamepad.rs create mode 100644 src/platform_impl/windows/xinput.rs create mode 100644 src/util.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ddc6816546..29bcb9b376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,20 @@ - On Windows, fix initial dimensions of a fullscreen window. - On Windows, Fix transparent borderless windows rendering wrong. +- Improve event API documentation. +- Overhaul device event API: + - **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`. + - **Breaking**: Remove `DeviceEvent::Text` variant. + - **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`. + - **Breaking**: Removed device IDs from `WindowEvent` variants. + - Add `enumerate` function on device ID types to list all attached devices of that type. + - Add `is_connected` function on device ID types check if the specified device is still available. + - **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`. + - Add `handle` function to retrieve the underlying `HANDLE`. +- On Windows, fix duplicate device events getting sent if Winit managed multiple windows. +- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases. +- Added gamepad support on Windows via raw input and XInput. + # Version 0.19.1 (2019-04-08) - On Wayland, added a `get_wayland_display` function to `EventsLoopExt`. diff --git a/Cargo.toml b/Cargo.toml index 18b7e954b8..8afe0350bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ objc = "0.2.3" [target.'cfg(target_os = "windows")'.dependencies] bitflags = "1" +rusty-xinput = "1.0" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" @@ -49,6 +50,7 @@ features = [ "commctrl", "dwmapi", "errhandlingapi", + "hidpi", "hidusage", "libloaderapi", "objbase", @@ -64,6 +66,7 @@ features = [ "wingdi", "winnt", "winuser", + "xinput", ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] diff --git a/examples/cursor.rs b/examples/cursor.rs index d5bd34a980..b8b6dec176 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -15,14 +15,10 @@ fn main() { event_loop.run(move |event, _, control_flow| match event { Event::WindowEvent { event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Pressed, .. - }, + }), .. } => { println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 40613d7068..5c7da5f70d 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -17,16 +17,12 @@ fn main() { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(key), - modifiers, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + modifiers, .. - } => { + }) => { use winit::event::VirtualKeyCode::*; match key { Escape => *control_flow = ControlFlow::Exit, diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 27df22767b..7a8b278223 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -56,15 +56,11 @@ fn main() { match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + virtual_keycode: Some(virtual_code), + state, .. - } => match (virtual_code, state) { + }) => match (virtual_code, state) { (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, (VirtualKeyCode::F, ElementState::Pressed) => { #[cfg(target_os = "macos")] diff --git a/examples/gamepad.rs b/examples/gamepad.rs new file mode 100644 index 0000000000..42023a81f9 --- /dev/null +++ b/examples/gamepad.rs @@ -0,0 +1,52 @@ +use winit::{ + event::{ + device::{GamepadEvent, GamepadHandle}, + Event, WindowEvent, + }, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +fn main() { + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("The world's worst video game") + .build(&event_loop) + .unwrap(); + + println!("enumerating gamepads:"); + for gamepad in GamepadHandle::enumerate(&event_loop) { + println!( + " gamepad={:?}\tport={:?}\tbattery level={:?}", + gamepad, + gamepad.port(), + gamepad.battery_level() + ); + } + + let deadzone = 0.12; + + event_loop.run(move |event, _, control_flow| { + match event { + Event::GamepadEvent(gamepad_handle, event) => { + match event { + // Discard any Axis events that has a corresponding Stick event. + GamepadEvent::Axis { stick: true, .. } => (), + + // Discard any Stick event that falls inside the stick's deadzone. + GamepadEvent::Stick { + x_value, y_value, .. + } if (x_value.powi(2) + y_value.powi(2)).sqrt() < deadzone => (), + + _ => println!("[{:?}] {:#?}", gamepad_handle, event), + } + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } + }); +} diff --git a/examples/gamepad_rumble.rs b/examples/gamepad_rumble.rs new file mode 100644 index 0000000000..8ae6ccc8c9 --- /dev/null +++ b/examples/gamepad_rumble.rs @@ -0,0 +1,60 @@ +use std::time::Instant; +use winit::event_loop::EventLoop; + +#[derive(Debug, Clone)] +enum Rumble { + None, + Left, + Right, +} + +fn main() { + let event_loop = EventLoop::new(); + + // You should generally use `GamepadEvent::Added/Removed` to detect gamepads, as doing that will + // allow you to more easily support gamepad hotswapping. However, we're using `enumerate` here + // because it makes this example more concise. + let gamepads = winit::event::device::GamepadHandle::enumerate(&event_loop).collect::>(); + + let rumble_patterns = &[ + (0.5, Rumble::None), + (2.0, Rumble::Left), + (0.5, Rumble::None), + (2.0, Rumble::Right), + ]; + let mut rumble_iter = rumble_patterns.iter().cloned().cycle(); + + let mut active_pattern = rumble_iter.next().unwrap(); + let mut timeout = active_pattern.0; + let mut timeout_start = Instant::now(); + + event_loop.run(move |_, _, _| { + if timeout <= active_pattern.0 { + let t = (timeout / active_pattern.0) * std::f64::consts::PI; + let intensity = t.sin(); + + for g in &gamepads { + let result = match active_pattern.1 { + Rumble::Left => g.rumble(intensity, 0.0), + Rumble::Right => g.rumble(0.0, intensity), + Rumble::None => Ok(()), + }; + + if let Err(e) = result { + println!("Rumble failed: {:?}", e); + } + } + + timeout = (Instant::now() - timeout_start).as_millis() as f64 / 1000.0; + } else { + active_pattern = rumble_iter.next().unwrap(); + println!( + "Rumbling {:?} for {:?} seconds", + active_pattern.1, active_pattern.0 + ); + + timeout = 0.0; + timeout_start = Instant::now(); + } + }); +} diff --git a/examples/handling_close.rs b/examples/handling_close.rs index d67c0046a7..639e2b7482 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -41,15 +41,11 @@ fn main() { // closing the window. How to close the window is detailed in the handler for // the Y key. } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: Released, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + virtual_keycode: Some(virtual_code), + state: Released, .. - } => { + }) => { match virtual_code { Y => { if close_requested { diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 39f7af3df8..bc961cc47f 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -24,16 +24,12 @@ fn main() { thread::spawn(move || { while let Ok(event) = rx.recv() { match event { - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(key), - modifiers, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + modifiers, .. - } => { + }) => { window.set_title(&format!("{:?}", key)); let state = !modifiers.shift; use self::VirtualKeyCode::*; @@ -105,14 +101,10 @@ fn main() { Event::WindowEvent { event, window_id } => match event { WindowEvent::CloseRequested | WindowEvent::Destroyed - | WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, + | WindowEvent::KeyboardInput(KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), .. - } => { + }) => { window_senders.remove(&window_id); } _ => { diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 61dc1fd7ec..de00a49a87 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -29,14 +29,10 @@ fn main() { *control_flow = ControlFlow::Exit; } } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Pressed, .. - } => { + }) => { let window = Window::new(&event_loop).unwrap(); windows.insert(window.id(), window); } diff --git a/examples/resizable.rs b/examples/resizable.rs index 8aa6f70631..f17d177df2 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -21,15 +21,11 @@ fn main() { match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Space), - state: ElementState::Released, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Space), + state: ElementState::Released, .. - } => { + }) => { resizable = !resizable; println!("Resizable: {}", resizable); window.set_resizable(resizable); diff --git a/src/event.rs b/src/event.rs index 26356e1008..2499ef64e7 100644 --- a/src/event.rs +++ b/src/event.rs @@ -8,11 +8,12 @@ use std::{path::PathBuf, time::Instant}; use crate::{ dpi::{LogicalPosition, LogicalSize}, - platform_impl, window::WindowId, }; -/// Describes a generic event. +pub mod device; + +/// A generic event. #[derive(Clone, Debug, PartialEq)] pub enum Event { /// Emitted when the OS sends an event to a winit window. @@ -20,11 +21,15 @@ pub enum Event { window_id: WindowId, event: WindowEvent, }, - /// Emitted when the OS sends an event to a device. - DeviceEvent { - device_id: DeviceId, - event: DeviceEvent, - }, + + /// Emitted when a mouse device has generated input. + MouseEvent(device::MouseId, device::MouseEvent), + /// Emitted when a keyboard device has generated input. + KeyboardEvent(device::KeyboardId, device::KeyboardEvent), + HidEvent(device::HidId, device::HidEvent), + /// Emitted when a gamepad/joystick device has generated input. + GamepadEvent(device::GamepadHandle, device::GamepadEvent), + /// Emitted when an event is sent from [`EventLoopProxy::send_event`](../event_loop/struct.EventLoopProxy.html#method.send_event) UserEvent(T), /// Emitted when new events arrive from the OS to be processed. @@ -50,7 +55,10 @@ impl Event { match self { UserEvent(_) => Err(self), WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }), - DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }), + MouseEvent(id, event) => Ok(MouseEvent(id, event)), + KeyboardEvent(id, event) => Ok(KeyboardEvent(id, event)), + HidEvent(id, event) => Ok(HidEvent(id, event)), + GamepadEvent(id, event) => Ok(GamepadEvent(id, event)), NewEvents(cause) => Ok(NewEvents(cause)), EventsCleared => Ok(EventsCleared), LoopDestroyed => Ok(LoopDestroyed), @@ -60,7 +68,7 @@ impl Event { } } -/// Describes the reason the event loop is resuming. +/// The reason the event loop is resuming. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum StartCause { /// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the @@ -86,7 +94,7 @@ pub enum StartCause { Init, } -/// Describes an event from a `Window`. +/// An event from a `Window`. #[derive(Clone, Debug, PartialEq)] pub enum WindowEvent { /// The size of the window has changed. Contains the client area's new dimensions. @@ -128,15 +136,10 @@ pub enum WindowEvent { Focused(bool), /// An event from the keyboard has been received. - KeyboardInput { - device_id: DeviceId, - input: KeyboardInput, - }, + KeyboardInput(KeyboardInput), /// The cursor has moved on the window. CursorMoved { - device_id: DeviceId, - /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. @@ -145,14 +148,13 @@ pub enum WindowEvent { }, /// The cursor has entered the window. - CursorEntered { device_id: DeviceId }, + CursorEntered, /// The cursor has left the window. - CursorLeft { device_id: DeviceId }, + CursorLeft, /// A mouse wheel movement or touchpad scroll occurred. MouseWheel { - device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState, @@ -160,7 +162,6 @@ pub enum WindowEvent { /// An mouse button press has been received. MouseInput { - device_id: DeviceId, state: ElementState, button: MouseButton, modifiers: ModifiersState, @@ -171,18 +172,7 @@ pub enum WindowEvent { /// At the moment, only supported on Apple forcetouch-capable macbooks. /// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad /// is being pressed) and stage (integer representing the click level). - TouchpadPressure { - device_id: DeviceId, - pressure: f32, - stage: i64, - }, - - /// Motion on some analog axis. May report data redundant to other, more specific events. - AxisMotion { - device_id: DeviceId, - axis: AxisId, - value: f64, - }, + TouchpadPressure { pressure: f32, stage: i64 }, /// The OS or application has requested that the window be redrawn. RedrawRequested, @@ -202,72 +192,7 @@ pub enum WindowEvent { HiDpiFactorChanged(f64), } -/// Identifier of an input device. -/// -/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which -/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or -/// physical. Virtual devices typically aggregate inputs from multiple physical devices. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(pub(crate) platform_impl::DeviceId); - -impl DeviceId { - /// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return - /// value of this function is that it will always be equal to itself and to future values returned - /// by this function. No other guarantees are made. This may be equal to a real `DeviceId`. - /// - /// **Passing this into a winit function will result in undefined behavior.** - pub unsafe fn dummy() -> Self { - DeviceId(platform_impl::DeviceId::dummy()) - } -} - -/// Represents raw hardware events that are not associated with any particular window. -/// -/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person -/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because -/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs -/// may not match. -/// -/// Note that these events are delivered regardless of input focus. -#[derive(Clone, Debug, PartialEq)] -pub enum DeviceEvent { - Added, - Removed, - - /// Change in physical position of a pointing device. - /// - /// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`. - MouseMotion { - /// (x, y) change in position in unspecified units. - /// - /// Different devices may use different units. - delta: (f64, f64), - }, - - /// Physical scroll event - MouseWheel { - delta: MouseScrollDelta, - }, - - /// Motion on some analog axis. This event will be reported for all arbitrary input devices - /// that winit supports on this platform, including mouse devices. If the device is a mouse - /// device then this will be reported alongside the MouseMotion event. - Motion { - axis: AxisId, - value: f64, - }, - - Button { - button: ButtonId, - state: ElementState, - }, - Key(KeyboardInput), - Text { - codepoint: char, - }, -} - -/// Describes a keyboard input event. +/// A keyboard input event. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct KeyboardInput { @@ -293,37 +218,30 @@ pub struct KeyboardInput { pub modifiers: ModifiersState, } -/// Describes touch-screen input state. +/// Touch input state. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TouchPhase { Started, Moved, Ended, + /// The touch has been cancelled by the OS. + /// + /// This can occur in a variety of situations, such as the window losing focus. Cancelled, } -/// Represents touch event -/// -/// Every time user touches screen new Start event with some finger id is generated. -/// When the finger is removed from the screen End event with same id is generated. -/// -/// For every id there will be at least 2 events with phases Start and End (or Cancelled). -/// There may be 0 or more Move events. -/// +/// A touch event. /// -/// Depending on platform implementation id may or may not be reused by system after End event. -/// -/// Gesture regonizer using this event should assume that Start event received with same id -/// as previously received End event is a new finger and has nothing to do with an old one. -/// -/// Touch may be cancelled if for example window lost focus. +/// Every event is guaranteed to start with a `Start` event, and may end with either an `End` or +/// `Cancelled` event. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Touch { - pub device_id: DeviceId, pub phase: TouchPhase, pub location: LogicalPosition, - /// unique identifier of a finger. + /// Unique identifier of a finger. + /// + /// This may get reused by the system after the touch ends. pub id: u64, } @@ -336,16 +254,16 @@ pub type AxisId = u32; /// Identifier for a specific button on some device. pub type ButtonId = u32; -/// Describes the input state of a key. -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +/// The input state of a key or button. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ElementState { Pressed, Released, } -/// Describes a button of a mouse controller. -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +/// A button on a mouse. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum MouseButton { Left, @@ -354,7 +272,7 @@ pub enum MouseButton { Other(u8), } -/// Describes a difference in the mouse scroll wheel state. +/// A difference in the mouse scroll wheel state. #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum MouseScrollDelta { @@ -373,7 +291,7 @@ pub enum MouseScrollDelta { PixelDelta(LogicalPosition), } -/// Symbolic name for a keyboard key. +/// Symbolic name of a keyboard key. #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] #[repr(u32)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -572,7 +490,7 @@ pub enum VirtualKeyCode { Cut, } -/// Represents the current state of the keyboard modifiers +/// The current state of the keyboard modifiers /// /// Each field of this struct represents a modifier and is `true` if this modifier is active. #[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)] diff --git a/src/event/device.rs b/src/event/device.rs new file mode 100644 index 0000000000..c42562e9fe --- /dev/null +++ b/src/event/device.rs @@ -0,0 +1,362 @@ +//! Raw hardware events that are not associated with any particular window. +//! +//! Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person +//! game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because +//! window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs +//! may not match. +//! +//! All attached devices are guaranteed to emit an `Added` event upon the initialization of the event loop. +//! +//! Note that device events are always delivered regardless of window focus. + +use crate::{ + dpi::PhysicalPosition, + event::{AxisId, ButtonId, ElementState, KeyboardInput, MouseButton}, + event_loop::EventLoop, + platform_impl, +}; +use std::{fmt, io}; + +/// A hint suggesting the type of button that was pressed. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GamepadButton { + Start, + Select, + + /// The north face button. + /// + /// * Nintendo: X + /// * Playstation: Triangle + /// * XBox: Y + North, + /// The south face button. + /// + /// * Nintendo: B + /// * Playstation: X + /// * XBox: A + South, + /// The east face button. + /// + /// * Nintendo: A + /// * Playstation: Circle + /// * XBox: B + East, + /// The west face button. + /// + /// * Nintendo: Y + /// * Playstation: Square + /// * XBox: X + West, + + LeftStick, + RightStick, + + LeftTrigger, + RightTrigger, + + LeftShoulder, + RightShoulder, + + DPadUp, + DPadDown, + DPadLeft, + DPadRight, +} + +/// A hint suggesting the type of axis that moved. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GamepadAxis { + LeftStickX, + LeftStickY, + + RightStickX, + RightStickY, + + LeftTrigger, + RightTrigger, +} + +/// A given joystick's side on the gamepad. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Side { + Left, + Right, +} + +/// Raw mouse events. +/// +/// See the module-level docs for more information. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum MouseEvent { + /// A mouse device has been added. + Added, + /// A mouse device has been removed. + Removed, + /// A mouse button has been pressed or released. + Button { + state: ElementState, + button: MouseButton, + }, + /// Relative change in physical position of a pointing device. + /// + /// This represents raw, unfiltered physical motion, NOT the position of the mouse. Accordingly, + /// the values provided here are the change in position of the mouse since the previous + /// `MovedRelative` event. + MovedRelative(f64, f64), + /// Change in absolute position of a pointing device. + /// + /// The `PhysicalPosition` value is the new position of the cursor relative to the desktop. This + /// generally doesn't get output by standard mouse devices, but can get output from tablet devices. + MovedAbsolute(PhysicalPosition), + /// Change in rotation of mouse wheel. + Wheel(f64, f64), +} + +/// Raw keyboard events. +/// +/// See the module-level docs for more information. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum KeyboardEvent { + /// A keyboard device has been added. + Added, + /// A keyboard device has been removed. + Removed, + /// A key has been pressed or released. + Input(KeyboardInput), +} + +/// Raw HID event. +/// +/// See the module-level docs for more information. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum HidEvent { + /// A Human Interface Device device has been added. + Added, + /// A Human Interface Device device has been removed. + Removed, + /// A raw data packet has been received from the Human Interface Device. + Data(Box<[u8]>), +} + +/// Gamepad/joystick events. +/// +/// These can be generated by any of a variety of game controllers, including (but not limited to) +/// gamepads, joysicks, and HOTAS devices. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +pub enum GamepadEvent { + /// A gamepad/joystick device has been added. + Added, + /// A gamepad/joystick device has been removed. + Removed, + /// An analog axis value on the gamepad/joystick has changed. + /// + /// Winit does NOT provide [deadzone filtering](https://www.quora.com/What-does-the-term-joystick-deadzone-mean), + /// and such filtering may have to be provided by API users for joystick axes. + Axis { + axis_id: AxisId, + /// A hint regarding the physical axis that moved. + /// + /// On traditional gamepads (such as an X360 controller) this can be assumed to have a + /// non-`None` value; however, other joystick devices with more varied layouts generally won't + /// provide a value here. + /// + /// TODO: DISCUSS CONTROLLER MAPPING ONCE WE FIGURE OUT WHAT WE'RE DOING THERE. + axis: Option, + value: f64, + /// Whether or not this axis has also produced a [`GamepadEvent::Stick`] event. + stick: bool, + }, + /// A two-axis joystick's value has changed. + /// + /// This is mainly provided to assist with deadzone calculation, as proper deadzones should be + /// calculated via the combined distance of each joystick axis from the center of the joystick, + /// rather than per-axis. + /// + /// Note that this is only guaranteed to be emitted for traditionally laid out gamepads. More + /// complex joysticks generally don't report specifics of their layout to the operating system, + /// preventing Winit from automatically aggregating their axis input into two-axis stick events. + Stick { + /// The X axis' ID. + x_id: AxisId, + /// The Y axis' ID. + y_id: AxisId, + x_value: f64, + y_value: f64, + /// Which joystick side produced this event. + side: Side, + }, + Button { + button_id: ButtonId, + /// A hint regarding the location of the button. + /// + /// The caveats on the `Axis.hint` field also apply here. + button: Option, + state: ElementState, + }, +} + +/// Error reported if a rumble attempt unexpectedly failed. +#[derive(Debug)] +pub enum RumbleError { + /// The device is no longer connected. + DeviceNotConnected, + /// An unknown OS error has occured. + OsError(io::Error), +} + +/// A typed identifier for a mouse device. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MouseId(pub(crate) platform_impl::MouseId); +/// A typed identifier for a keyboard device. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct KeyboardId(pub(crate) platform_impl::KeyboardId); +/// A typed if for a Human Interface Device. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct HidId(pub(crate) platform_impl::HidId); +/// A handle to a gamepad/joystick device. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GamepadHandle(pub(crate) platform_impl::GamepadHandle); + +impl MouseId { + /// Returns a dummy `MouseId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `MouseId`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + MouseId(platform_impl::MouseId::dummy()) + } + + /// Enumerate all attached mouse devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::MouseId::enumerate(&event_loop.event_loop) + } + + /// Check to see if this mouse device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } +} + +impl KeyboardId { + /// Returns a dummy `KeyboardId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `KeyboardId`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + KeyboardId(platform_impl::KeyboardId::dummy()) + } + + /// Enumerate all attached keyboard devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::KeyboardId::enumerate(&event_loop.event_loop) + } + + /// Check to see if this keyboard device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } +} + +impl HidId { + /// Returns a dummy `HidId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `HidId`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + HidId(platform_impl::HidId::dummy()) + } + + /// Enumerate all attached keyboard devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::HidId::enumerate(&event_loop.event_loop) + } + + /// Check to see if this keyboard device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } +} + +impl GamepadHandle { + /// Returns a dummy `GamepadHandle`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `GamepadHandle`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + GamepadHandle(platform_impl::GamepadHandle::dummy()) + } + + /// Enumerate all attached gamepad/joystick devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::GamepadHandle::enumerate(&event_loop.event_loop) + } + + /// Check to see if this gamepad/joystick device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } + + /// Attempts to set the rumble values for an attached controller. Input values are automatically + /// bound to a [`0.0`, `1.0`] range. + /// + /// Certain gamepads assign different usages to the left and right motors - for example, X360 + /// controllers treat the left motor as a low-frequency rumble and the right motor as a + /// high-frequency rumble. However, this cannot necessarily be assumed for all gamepad devices. + /// + /// Note that, if the given gamepad does not support rumble, no error value gets thrown. + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + self.0.rumble(left_speed, right_speed) + } + + /// Gets the port number assigned to the gamepad. + pub fn port(&self) -> Option { + self.0.port() + } + + /// Gets the controller's battery level. + /// + /// If the controller doesn't report a battery level, this returns `None`. + pub fn battery_level(&self) -> Option { + self.0.battery_level() + } +} + +/// TODO: IS THIS THE RIGHT ABSTRACTION FOR ALL PLATFORMS? +/// This is exposed in its current form because it's what Microsoft does for XInput, and that's my +/// (@Osspial's) main point of reference. If you're implementing this on a different platform and +/// that platform exposes battery level differently, please bring it up in the tracking issue! +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BatteryLevel { + Empty, + Low, + Medium, + Full, +} + +impl fmt::Debug for MouseId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} + +impl fmt::Debug for KeyboardId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} + +impl fmt::Debug for HidId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} + +impl fmt::Debug for GamepadHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2695db6b20..26ebd91f27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,6 +132,7 @@ pub mod event_loop; mod icon; pub mod monitor; mod platform_impl; +mod util; pub mod window; pub mod platform; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 43133e04b2..02a1955acd 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -6,7 +6,7 @@ use libc; use winapi::shared::windef::HWND; use crate::{ - event::DeviceId, + event::device::{GamepadHandle, KeyboardId, MouseId}, event_loop::EventLoop, monitor::MonitorHandle, platform_impl::EventLoop as WindowsEventLoop, @@ -108,17 +108,49 @@ impl MonitorHandleExtWindows for MonitorHandle { } } -/// Additional methods on `DeviceId` that are specific to Windows. -pub trait DeviceIdExtWindows { +/// Additional methods on device types that are specific to Windows. +pub trait DeviceExtWindows { /// Returns an identifier that persistently refers to this specific device. /// /// Will return `None` if the device is no longer available. fn persistent_identifier(&self) -> Option; + + /// Returns the handle of the device - `HANDLE`. + fn handle(&self) -> *mut c_void; +} + +impl DeviceExtWindows for MouseId { + #[inline] + fn persistent_identifier(&self) -> Option { + self.0.persistent_identifier() + } + + #[inline] + fn handle(&self) -> *mut c_void { + self.0.handle() as _ + } +} + +impl DeviceExtWindows for KeyboardId { + #[inline] + fn persistent_identifier(&self) -> Option { + self.0.persistent_identifier() + } + + #[inline] + fn handle(&self) -> *mut c_void { + self.0.handle() as _ + } } -impl DeviceIdExtWindows for DeviceId { +impl DeviceExtWindows for GamepadHandle { #[inline] fn persistent_identifier(&self) -> Option { self.0.persistent_identifier() } + + #[inline] + fn handle(&self) -> *mut c_void { + self.0.handle() as _ + } } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 4520e34b42..2da248e80d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -16,7 +16,7 @@ use parking_lot::Mutex; use std::{ any::Any, cell::RefCell, - collections::VecDeque, + collections::{HashMap, VecDeque}, marker::PhantomData, mem, panic, ptr, rc::Rc, @@ -38,14 +38,17 @@ use winapi::{ }, um::{ commctrl, libloaderapi, ole2, processthreadsapi, winbase, - winnt::{LONG, LPCSTR, SHORT}, + winnt::{HANDLE, LONG, LPCSTR, SHORT}, winuser, }, }; use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalSize}, - event::{DeviceEvent, Event, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent}, + dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, + event::{ + device::{GamepadEvent, HidEvent, KeyboardEvent, MouseEvent}, + Event, KeyboardInput, MouseButton, StartCause, Touch, TouchPhase, WindowEvent, + }, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform_impl::platform::{ dpi::{ @@ -53,35 +56,44 @@ use crate::{ }, drop_handler::FileDropHandler, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, - raw_input::{get_raw_input_data, get_raw_mouse_button_state}, + gamepad::Gamepad, + raw_input::{self, get_raw_input_data, get_raw_mouse_button_state, RawInputData}, util, window::adjust_size, window_state::{CursorFlags, WindowFlags, WindowState}, - wrap_device_id, WindowId, DEVICE_ID, + GamepadHandle, HidId, KeyboardId, MouseId, WindowId, }, window::WindowId as RootWindowId, }; pub(crate) struct SubclassInput { pub window_state: Arc>, - pub event_loop_runner: EventLoopRunnerShared, + pub shared_data: Rc>, pub file_drop_handler: FileDropHandler, } impl SubclassInput { unsafe fn send_event(&self, event: Event) { - self.event_loop_runner.send_event(event); + self.shared_data.runner_shared.send_event(event); } } struct ThreadMsgTargetSubclassInput { - event_loop_runner: EventLoopRunnerShared, + shared_data: Rc>, user_event_receiver: Receiver, } +#[derive(Debug)] +pub(crate) enum DeviceId { + Mouse(MouseId), + Keyboard(KeyboardId), + Hid(HidId), + Gamepad(GamepadHandle, Gamepad), +} + impl ThreadMsgTargetSubclassInput { unsafe fn send_event(&self, event: Event) { - self.event_loop_runner.send_event(event); + self.shared_data.runner_shared.send_event(event); } } @@ -94,7 +106,7 @@ pub struct EventLoopWindowTarget { thread_id: DWORD, trigger_newevents_on_redraw: Arc, thread_msg_target: HWND, - pub(crate) runner_shared: EventLoopRunnerShared, + pub(crate) shared_data: Rc>, } impl EventLoop { @@ -110,12 +122,15 @@ impl EventLoop { become_dpi_aware(dpi_aware); let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; - let runner_shared = Rc::new(ELRShared { - runner: RefCell::new(None), - buffer: RefCell::new(VecDeque::new()), + let shared_data = Rc::new(SubclassSharedData { + runner_shared: ELRShared { + runner: RefCell::new(None), + buffer: RefCell::new(VecDeque::new()), + }, + active_device_ids: RefCell::new(HashMap::default()), }); let (thread_msg_target, thread_msg_sender) = - thread_event_target_window(runner_shared.clone()); + thread_event_target_window(shared_data.clone()); EventLoop { thread_msg_sender, @@ -124,7 +139,7 @@ impl EventLoop { thread_id, trigger_newevents_on_redraw: Arc::new(AtomicBool::new(true)), thread_msg_target, - runner_shared, + shared_data, }, _marker: PhantomData, }, @@ -155,10 +170,10 @@ impl EventLoop { }) }; { - let runner_shared = self.window_target.p.runner_shared.clone(); - let mut runner_ref = runner_shared.runner.borrow_mut(); + let shared_data = self.window_target.p.shared_data.clone(); + let mut runner_ref = shared_data.runner_shared.runner.borrow_mut(); loop { - let event = runner_shared.buffer.borrow_mut().pop_front(); + let event = shared_data.runner_shared.buffer.borrow_mut().pop_front(); match event { Some(e) => { runner.process_event(e); @@ -173,6 +188,7 @@ impl EventLoop { () => { self.window_target .p + .shared_data .runner_shared .runner .borrow_mut() @@ -220,7 +236,13 @@ impl EventLoop { } runner!().call_event_handler(Event::LoopDestroyed); - *self.window_target.p.runner_shared.runner.borrow_mut() = None; + *self + .window_target + .p + .shared_data + .runner_shared + .runner + .borrow_mut() = None; } pub fn create_proxy(&self) -> EventLoopProxy { @@ -229,6 +251,70 @@ impl EventLoop { event_send: self.thread_msg_sender.clone(), } } + + fn devices( + &self, + f: impl FnMut(&DeviceId) -> Option, + ) -> impl '_ + Iterator { + // Flush WM_INPUT and WM_INPUT_DEVICE_CHANGE events so that the active_device_ids list is + // accurate. This is essential to make this function work if called before calling `run` or + // `run_return`. + unsafe { + let mut msg = mem::uninitialized(); + loop { + let result = winuser::PeekMessageW( + &mut msg, + self.window_target.p.thread_msg_target, + winuser::WM_INPUT_DEVICE_CHANGE, + winuser::WM_INPUT, + 1, + ); + if 0 == result { + break; + } + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + } + } + + self.window_target + .p + .shared_data + .active_device_ids + .borrow() + .values() + .filter_map(f) + .collect::>() + .into_iter() + } + + pub fn mouses(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Mouse(id) => Some(id.clone().into()), + _ => None, + }) + } + + pub fn keyboards(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Keyboard(id) => Some(id.clone().into()), + _ => None, + }) + } + + pub fn hids(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Hid(id) => Some(id.clone().into()), + _ => None, + }) + } + + pub fn gamepads(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Gamepad(handle, _) => Some(handle.clone().into()), + _ => None, + }) + } } impl EventLoopWindowTarget { @@ -242,7 +328,11 @@ impl EventLoopWindowTarget { } } -pub(crate) type EventLoopRunnerShared = Rc>; +pub(crate) struct SubclassSharedData { + pub runner_shared: ELRShared, + pub active_device_ids: RefCell>, +} + pub(crate) struct ELRShared { runner: RefCell>>, buffer: RefCell>>, @@ -720,7 +810,7 @@ lazy_static! { }; } -fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> (HWND, Sender) { +fn thread_event_target_window(shared_data: Rc>) -> (HWND, Sender) { unsafe { let window = winuser::CreateWindowExW( winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED, @@ -748,7 +838,7 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> let (tx, rx) = mpsc::channel(); let subclass_input = ThreadMsgTargetSubclassInput { - event_loop_runner, + shared_data, user_event_receiver: rx, }; let input_ptr = Box::into_raw(Box::new(subclass_input)); @@ -760,6 +850,9 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> ); assert_eq!(subclass_result, 1); + // Set up raw input + raw_input::register_for_raw_input(window); + (window, tx) } } @@ -814,14 +907,14 @@ unsafe extern "system" fn public_window_callback( match msg { winuser::WM_ENTERSIZEMOVE => { - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); + let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut(); if let Some(ref mut runner) = *runner { runner.in_modal_loop = true; } 0 } winuser::WM_EXITSIZEMOVE => { - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); + let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut(); if let Some(ref mut runner) = *runner { runner.in_modal_loop = false; } @@ -870,7 +963,7 @@ unsafe extern "system" fn public_window_callback( _ if msg == *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID => { use crate::event::WindowEvent::RedrawRequested; - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); + let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut(); if let Some(ref mut runner) = *runner { // This check makes sure that calls to `request_redraw()` during `EventsCleared` // handling dispatch `RedrawRequested` immediately after `EventsCleared`, without @@ -987,9 +1080,7 @@ unsafe extern "system" fn public_window_callback( if mouse_was_outside_window { subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: CursorEntered { - device_id: DEVICE_ID, - }, + event: CursorEntered, }); // Calling TrackMouseEvent in order to receive mouse leave events. @@ -1009,7 +1100,6 @@ unsafe extern "system" fn public_window_callback( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { - device_id: DEVICE_ID, position, modifiers: event::get_key_mods(), }, @@ -1029,9 +1119,7 @@ unsafe extern "system" fn public_window_callback( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: CursorLeft { - device_id: DEVICE_ID, - }, + event: CursorLeft, }); 0 @@ -1047,7 +1135,6 @@ unsafe extern "system" fn public_window_callback( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, modifiers: event::get_key_mods(), @@ -1067,7 +1154,6 @@ unsafe extern "system" fn public_window_callback( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, modifiers: event::get_key_mods(), @@ -1085,15 +1171,12 @@ unsafe extern "system" fn public_window_callback( if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Pressed, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + state: Pressed, + scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + }), }); // Windows doesn't emit a delete character by default, but in order to make it // consistent with the other platforms we'll emit a delete character here. @@ -1113,31 +1196,27 @@ unsafe extern "system" fn public_window_callback( if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Released, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + state: Released, + scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + }), }); } 0 } winuser::WM_LBUTTONDOWN => { - use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; capture_mouse(window, &mut *subclass_input.window_state.lock()); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Left, + button: MouseButton::Left, modifiers: event::get_key_mods(), }, }); @@ -1145,18 +1224,15 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_LBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Left, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; release_mouse(&mut *subclass_input.window_state.lock()); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Left, + button: MouseButton::Left, modifiers: event::get_key_mods(), }, }); @@ -1164,18 +1240,15 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_RBUTTONDOWN => { - use crate::event::{ - ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; capture_mouse(window, &mut *subclass_input.window_state.lock()); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Right, + button: MouseButton::Right, modifiers: event::get_key_mods(), }, }); @@ -1183,18 +1256,15 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_RBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Right, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; release_mouse(&mut *subclass_input.window_state.lock()); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Right, + button: MouseButton::Right, modifiers: event::get_key_mods(), }, }); @@ -1202,18 +1272,15 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_MBUTTONDOWN => { - use crate::event::{ - ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; capture_mouse(window, &mut *subclass_input.window_state.lock()); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Middle, + button: MouseButton::Middle, modifiers: event::get_key_mods(), }, }); @@ -1221,18 +1288,15 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_MBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; release_mouse(&mut *subclass_input.window_state.lock()); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Middle, + button: MouseButton::Middle, modifiers: event::get_key_mods(), }, }); @@ -1240,9 +1304,7 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_XBUTTONDOWN => { - use crate::event::{ - ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); capture_mouse(window, &mut *subclass_input.window_state.lock()); @@ -1250,9 +1312,8 @@ unsafe extern "system" fn public_window_callback( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Other(xbutton as u8), + button: MouseButton::Other(xbutton as u8), modifiers: event::get_key_mods(), }, }); @@ -1260,9 +1321,7 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_XBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Other, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); release_mouse(&mut *subclass_input.window_state.lock()); @@ -1270,129 +1329,14 @@ unsafe extern "system" fn public_window_callback( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Other(xbutton as u8), + button: MouseButton::Other(xbutton as u8), modifiers: event::get_key_mods(), }, }); 0 } - winuser::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam as _ { - winuser::GIDC_ARRIVAL => DeviceEvent::Added, - winuser::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; - - subclass_input.send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam as _), - event, - }); - - 0 - } - - winuser::WM_INPUT => { - use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, - ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, - }; - - if let Some(data) = get_raw_input_data(lparam as _) { - let device_id = wrap_device_id(data.header.hDevice as _); - - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); - - if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; - - if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } - - if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); - } - - if x != 0.0 || y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) }, - }); - } - } - - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { - let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta as f32), - }, - }); - } - - let button_state = get_raw_mouse_button_state(mouse.usButtonFlags); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as _; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Button { button, state }, - }); - } - } - } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); - - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; - - if pressed || released { - let state = if pressed { Pressed } else { Released }; - - let scancode = keyboard.MakeCode as _; - let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) - | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); - if let Some((vkey, scancode)) = - handle_extended_keys(keyboard.VKey as _, scancode, extended) - { - let virtual_keycode = vkey_to_winit_vkey(vkey); - - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers: event::get_key_mods(), - }), - }); - } - } - } - } - - commctrl::DefSubclassProc(window, msg, wparam, lparam) - } - winuser::WM_TOUCH => { let pcount = LOWORD(wparam as DWORD) as usize; let mut inputs = Vec::with_capacity(pcount); @@ -1424,7 +1368,6 @@ unsafe extern "system" fn public_window_callback( }, location, id: input.dwID as u64, - device_id: DEVICE_ID, }), }); } @@ -1637,7 +1580,7 @@ unsafe extern "system" fn thread_event_target_callback( ); }; let in_modal_loop = { - let runner = subclass_input.event_loop_runner.runner.borrow_mut(); + let runner = subclass_input.shared_data.runner_shared.runner.borrow_mut(); if let Some(ref runner) = *runner { runner.in_modal_loop } else { @@ -1671,7 +1614,7 @@ unsafe extern "system" fn thread_event_target_callback( } } - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); + let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut(); if let Some(ref mut runner) = *runner { runner.events_cleared(); match runner.control_flow { @@ -1691,6 +1634,230 @@ unsafe extern "system" fn thread_event_target_callback( } 0 } + winuser::WM_INPUT_DEVICE_CHANGE => { + use super::raw_input::RawDeviceInfo; + + let handle = lparam as HANDLE; + + match wparam as _ { + winuser::GIDC_ARRIVAL => { + if let Some(handle_info) = raw_input::get_raw_input_device_info(handle) { + let device: DeviceId; + let event: Event; + + match handle_info { + RawDeviceInfo::Mouse(_) => { + let mouse_id = MouseId(handle); + device = DeviceId::Mouse(mouse_id); + event = Event::MouseEvent(mouse_id.into(), MouseEvent::Added); + } + RawDeviceInfo::Keyboard(_) => { + let keyboard_id = KeyboardId(handle); + device = DeviceId::Keyboard(keyboard_id); + event = + Event::KeyboardEvent(keyboard_id.into(), KeyboardEvent::Added); + } + RawDeviceInfo::Hid(_) => match Gamepad::new(handle) { + Some(gamepad) => { + let gamepad_handle = GamepadHandle { + handle, + shared_data: gamepad.shared_data(), + }; + + device = DeviceId::Gamepad(gamepad_handle.clone(), gamepad); + event = Event::GamepadEvent( + gamepad_handle.into(), + GamepadEvent::Added, + ); + } + None => { + let hid_id = HidId(handle); + device = DeviceId::Hid(hid_id.into()); + event = Event::HidEvent(hid_id.into(), HidEvent::Added); + } + }, + } + + subclass_input + .shared_data + .active_device_ids + .borrow_mut() + .insert(handle, device); + subclass_input.send_event(event); + } + } + winuser::GIDC_REMOVAL => { + let removed_device = subclass_input + .shared_data + .active_device_ids + .borrow_mut() + .remove(&handle); + if let Some(device_id) = removed_device { + let event = match device_id { + DeviceId::Mouse(mouse_id) => { + Event::MouseEvent(mouse_id.into(), MouseEvent::Removed) + } + DeviceId::Keyboard(keyboard_id) => { + Event::KeyboardEvent(keyboard_id.into(), KeyboardEvent::Removed) + } + DeviceId::Hid(hid_id) => { + Event::HidEvent(hid_id.into(), HidEvent::Removed) + } + DeviceId::Gamepad(gamepad_handle, _) => { + Event::GamepadEvent(gamepad_handle.into(), GamepadEvent::Removed) + } + }; + subclass_input.send_event(event); + } + } + _ => unreachable!(), + } + + 0 + } + + winuser::WM_INPUT => { + use crate::event::ElementState::{Pressed, Released}; + + match get_raw_input_data(lparam as _) { + Some(RawInputData::Mouse { + device_handle, + raw_mouse, + }) => { + let mouse_handle = MouseId(device_handle).into(); + + if util::has_flag(raw_mouse.usFlags, winuser::MOUSE_MOVE_ABSOLUTE) { + let x = raw_mouse.lLastX as f64; + let y = raw_mouse.lLastY as f64; + + if x != 0.0 || y != 0.0 { + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::MovedAbsolute(PhysicalPosition { x, y }), + )); + } + } else if util::has_flag(raw_mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { + let x = raw_mouse.lLastX as f64; + let y = raw_mouse.lLastY as f64; + + if x != 0.0 || y != 0.0 { + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::MovedRelative(x, y), + )); + } + } + + if util::has_flag(raw_mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { + // TODO: HOW IS RAW WHEEL DELTA HANDLED ON OTHER PLATFORMS? + let delta = raw_mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Wheel(0.0, delta as f64), + )); + } + // Check if there's horizontal wheel movement. + if util::has_flag(raw_mouse.usButtonFlags, 0x0800) { + // TODO: HOW IS RAW WHEEL DELTA HANDLED ON OTHER PLATFORMS? + let delta = raw_mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Wheel(delta as f64, 0.0), + )); + } + + let button_state = get_raw_mouse_button_state(raw_mouse.usButtonFlags); + for (index, state) in button_state + .iter() + .cloned() + .enumerate() + .filter_map(|(i, state)| state.map(|s| (i, s))) + { + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Button { + state, + button: match index { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + _ => MouseButton::Other(index as u8 - 2), + }, + }, + )); + } + } + Some(RawInputData::Keyboard { + device_handle, + raw_keyboard, + }) => { + let keyboard_id = KeyboardId(device_handle).into(); + + let pressed = raw_keyboard.Message == winuser::WM_KEYDOWN + || raw_keyboard.Message == winuser::WM_SYSKEYDOWN; + let released = raw_keyboard.Message == winuser::WM_KEYUP + || raw_keyboard.Message == winuser::WM_SYSKEYUP; + + if pressed || released { + let state = if pressed { Pressed } else { Released }; + + let scancode = raw_keyboard.MakeCode as _; + let extended = util::has_flag(raw_keyboard.Flags, winuser::RI_KEY_E0 as _) + | util::has_flag(raw_keyboard.Flags, winuser::RI_KEY_E1 as _); + if let Some((vkey, scancode)) = + handle_extended_keys(raw_keyboard.VKey as _, scancode, extended) + { + let virtual_keycode = vkey_to_winit_vkey(vkey); + + subclass_input.send_event(Event::KeyboardEvent( + keyboard_id, + KeyboardEvent::Input(KeyboardInput { + scancode, + state, + virtual_keycode, + modifiers: event::get_key_mods(), + }), + )); + } + } + } + Some(RawInputData::Hid { + device_handle, + mut raw_hid, + }) => { + let mut gamepad_handle_opt: Option = None; + let mut gamepad_events = vec![]; + + { + let mut devices = subclass_input.shared_data.active_device_ids.borrow_mut(); + let device_id = devices.get_mut(&device_handle); + if let Some(DeviceId::Gamepad(gamepad_handle, ref mut gamepad)) = device_id + { + gamepad.update_state(&mut raw_hid.raw_input); + gamepad_events = gamepad.get_gamepad_events(); + gamepad_handle_opt = Some(gamepad_handle.clone().into()); + } + } + + if let Some(gamepad_handle) = gamepad_handle_opt { + for gamepad_event in gamepad_events { + subclass_input.send_event(Event::GamepadEvent( + gamepad_handle.clone(), + gamepad_event, + )); + } + } else { + subclass_input.send_event(Event::HidEvent( + HidId(device_handle).into(), + HidEvent::Data(raw_hid.raw_input), + )); + } + } + None => (), + } + + commctrl::DefSubclassProc(window, msg, wparam, lparam) + } _ if msg == *USER_EVENT_MSG_ID => { if let Ok(event) = subclass_input.user_event_receiver.recv() { subclass_input.send_event(Event::UserEvent(event)); diff --git a/src/platform_impl/windows/gamepad.rs b/src/platform_impl/windows/gamepad.rs new file mode 100644 index 0000000000..4867105099 --- /dev/null +++ b/src/platform_impl/windows/gamepad.rs @@ -0,0 +1,89 @@ +use std::sync::Weak; + +use winapi::um::winnt::HANDLE; + +use crate::{ + event::device::{BatteryLevel, GamepadEvent, RumbleError}, + platform_impl::platform::{ + raw_input::{get_raw_input_device_name, RawGamepad}, + xinput::{self, XInputGamepad, XInputGamepadShared}, + }, +}; + +#[derive(Debug)] +pub enum GamepadType { + Raw(RawGamepad), + XInput(XInputGamepad), +} + +#[derive(Clone)] +pub enum GamepadShared { + Raw(()), + XInput(Weak), + Dummy, +} + +#[derive(Debug)] +pub struct Gamepad { + handle: HANDLE, + backend: GamepadType, +} + +impl Gamepad { + pub fn new(handle: HANDLE) -> Option { + // TODO: Verify that this is an HID device + let name = get_raw_input_device_name(handle)?; + xinput::id_from_name(&name) + .and_then(XInputGamepad::new) + .map(GamepadType::XInput) + .or_else(|| RawGamepad::new(handle).map(GamepadType::Raw)) + .map(|backend| Gamepad { handle, backend }) + } + + pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + match self.backend { + GamepadType::Raw(ref mut gamepad) => gamepad.update_state(raw_input_report), + GamepadType::XInput(ref mut gamepad) => gamepad.update_state(), + } + } + + pub fn get_gamepad_events(&self) -> Vec { + match self.backend { + GamepadType::Raw(ref gamepad) => gamepad.get_gamepad_events(), + GamepadType::XInput(ref gamepad) => gamepad.get_gamepad_events(), + } + } + + pub fn shared_data(&self) -> GamepadShared { + match self.backend { + GamepadType::Raw(_) => GamepadShared::Raw(()), + GamepadType::XInput(ref gamepad) => GamepadShared::XInput(gamepad.shared_data()), + } + } +} + +impl GamepadShared { + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => Ok(()), + GamepadShared::XInput(ref data) => data + .upgrade() + .map(|r| r.rumble(left_speed, right_speed)) + .unwrap_or(Err(RumbleError::DeviceNotConnected)), + } + } + + pub fn port(&self) -> Option { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => None, + GamepadShared::XInput(ref data) => data.upgrade().map(|r| r.port()), + } + } + + pub fn battery_level(&self) -> Option { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => None, + GamepadShared::XInput(ref data) => data.upgrade().and_then(|r| r.battery_level()), + } + } +} diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 58d86faf00..3041d7019a 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -1,14 +1,34 @@ #![cfg(target_os = "windows")] -use winapi::{self, shared::windef::HWND}; +mod dpi; +mod drop_handler; +mod event; +mod event_loop; +mod gamepad; +mod icon; +mod monitor; +mod raw_input; +mod util; +mod window; +mod window_state; +mod xinput; + +use std::{ + cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, + fmt, + hash::{Hash, Hasher}, + ptr, +}; +use winapi::{self, shared::windef::HWND, um::winnt::HANDLE}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, + gamepad::GamepadShared, monitor::MonitorHandle, window::Window, }; -use crate::{event::DeviceId as RootDeviceId, window::Icon}; +use crate::{event::device, window::Icon}; #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -27,53 +47,148 @@ unsafe impl Send for Cursor {} unsafe impl Sync for Cursor {} #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(u32); +pub struct WindowId(HWND); +unsafe impl Send for WindowId {} +unsafe impl Sync for WindowId {} -impl DeviceId { +impl WindowId { pub unsafe fn dummy() -> Self { - DeviceId(0) + WindowId(ptr::null_mut()) } } -impl DeviceId { - pub fn persistent_identifier(&self) -> Option { - if self.0 != 0 { - raw_input::get_raw_input_device_name(self.0 as _) - } else { - None +macro_rules! device_id { + ($name:ident, $enumerate:ident) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub(crate) struct $name(HANDLE); + + unsafe impl Send for $name {} + unsafe impl Sync for $name {} + + impl $name { + pub unsafe fn dummy() -> Self { + Self(ptr::null_mut()) + } + + pub fn persistent_identifier(&self) -> Option { + raw_input::get_raw_input_device_name(self.0) + } + + pub fn is_connected(&self) -> bool { + raw_input::get_raw_input_device_info(self.0).is_some() + } + + #[inline(always)] + pub fn handle(&self) -> HANDLE { + self.0 + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.$enumerate() + } } - } + + impl From<$name> for device::$name { + fn from(platform_id: $name) -> Self { + Self(platform_id) + } + } + }; } -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); +device_id!(MouseId, mouses); +device_id!(KeyboardId, keyboards); +device_id!(HidId, hids); -fn wrap_device_id(id: u32) -> RootDeviceId { - RootDeviceId(DeviceId(id)) +#[derive(Clone)] +pub(crate) struct GamepadHandle { + handle: HANDLE, + shared_data: GamepadShared, } pub type OsError = std::io::Error; -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(HWND); -unsafe impl Send for WindowId {} -unsafe impl Sync for WindowId {} +unsafe impl Send for GamepadHandle where GamepadShared: Send {} +unsafe impl Sync for GamepadHandle where GamepadShared: Sync {} -impl WindowId { +impl GamepadHandle { pub unsafe fn dummy() -> Self { - use std::ptr::null_mut; + Self { + handle: ptr::null_mut(), + shared_data: GamepadShared::Dummy, + } + } + + pub fn persistent_identifier(&self) -> Option { + raw_input::get_raw_input_device_name(self.handle) + } + + pub fn is_connected(&self) -> bool { + raw_input::get_raw_input_device_info(self.handle).is_some() + } + + #[inline(always)] + pub fn handle(&self) -> HANDLE { + self.handle + } + + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), device::RumbleError> { + self.shared_data.rumble(left_speed, right_speed) + } + + pub fn port(&self) -> Option { + self.shared_data.port() + } - WindowId(null_mut()) + pub fn battery_level(&self) -> Option { + self.shared_data.battery_level() + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.gamepads() } } -mod dpi; -mod drop_handler; -mod event; -mod event_loop; -mod icon; -mod monitor; -mod raw_input; -mod util; -mod window; -mod window_state; +impl From for device::GamepadHandle { + fn from(platform_id: GamepadHandle) -> Self { + Self(platform_id) + } +} + +impl fmt::Debug for GamepadHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_tuple("GamepadHandle").field(&self.handle).finish() + } +} + +impl Eq for GamepadHandle {} +impl PartialEq for GamepadHandle { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.handle == other.handle + } +} + +impl Ord for GamepadHandle { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.handle.cmp(&other.handle) + } +} +impl PartialOrd for GamepadHandle { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + self.handle.partial_cmp(&other.handle) + } +} + +impl Hash for GamepadHandle { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.handle.hash(state); + } +} diff --git a/src/platform_impl/windows/raw_input.rs b/src/platform_impl/windows/raw_input.rs index 5e0656061d..501df79458 100644 --- a/src/platform_impl/windows/raw_input.rs +++ b/src/platform_impl/windows/raw_input.rs @@ -1,27 +1,43 @@ use std::{ + cmp::max, + fmt, mem::{self, size_of}, - ptr, + ptr, slice, }; use winapi::{ ctypes::wchar_t, shared::{ - hidusage::{HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC}, - minwindef::{TRUE, UINT, USHORT}, + hidpi::{ + HidP_GetButtonCaps, HidP_GetCaps, HidP_GetScaledUsageValue, HidP_GetUsageValue, + HidP_GetUsagesEx, HidP_GetValueCaps, HidP_Input, HIDP_STATUS_SUCCESS, HIDP_VALUE_CAPS, + PHIDP_PREPARSED_DATA, + }, + hidusage::{ + HID_USAGE_GENERIC_GAMEPAD, HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_GENERIC_KEYBOARD, + HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC, + }, + minwindef::{INT, TRUE, UINT, USHORT}, windef::HWND, }, um::{ - winnt::HANDLE, + winnt::{HANDLE, PCHAR}, winuser::{ self, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER, - RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, - RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, - RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, + RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RIDI_PREPARSEDDATA, + RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, + RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }, }, }; -use crate::{event::ElementState, platform_impl::platform::util}; +use crate::{ + event::{ + device::{GamepadAxis, GamepadEvent}, + ElementState, + }, + platform_impl::platform::util, +}; #[allow(dead_code)] pub fn get_raw_input_device_list() -> Option> { @@ -52,7 +68,6 @@ pub fn get_raw_input_device_list() -> Option> { Some(buffer) } -#[allow(dead_code)] pub enum RawDeviceInfo { Mouse(RID_DEVICE_INFO_MOUSE), Keyboard(RID_DEVICE_INFO_KEYBOARD), @@ -72,28 +87,27 @@ impl From for RawDeviceInfo { } } -#[allow(dead_code)] pub fn get_raw_input_device_info(handle: HANDLE) -> Option { let mut info: RID_DEVICE_INFO = unsafe { mem::uninitialized() }; let info_size = size_of::() as UINT; info.cbSize = info_size; - let mut minimum_size = 0; + let mut data_size = info_size; let status = unsafe { winuser::GetRawInputDeviceInfoW( handle, RIDI_DEVICEINFO, &mut info as *mut _ as _, - &mut minimum_size, + &mut data_size, ) - }; + } as INT; - if status == UINT::max_value() || status == 0 { + if status <= 0 { return None; } - debug_assert_eq!(info_size, status); + debug_assert_eq!(info_size, status as _); Some(info.into()) } @@ -130,6 +144,43 @@ pub fn get_raw_input_device_name(handle: HANDLE) -> Option { Some(util::wchar_to_string(&name)) } +pub fn get_raw_input_pre_parse_info(handle: HANDLE) -> Option> { + let mut minimum_size = 0; + let status = unsafe { + winuser::GetRawInputDeviceInfoW( + handle, + RIDI_PREPARSEDDATA, + ptr::null_mut(), + &mut minimum_size, + ) + }; + + if status != 0 { + return None; + } + + let mut buf: Vec = Vec::with_capacity(minimum_size as _); + + let status = unsafe { + winuser::GetRawInputDeviceInfoW( + handle, + RIDI_PREPARSEDDATA, + buf.as_ptr() as _, + &mut minimum_size, + ) + }; + + if status == UINT::max_value() || status == 0 { + return None; + } + + debug_assert_eq!(minimum_size, status); + + unsafe { buf.set_len(minimum_size as _) }; + + Some(buf) +} + pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { let device_size = size_of::() as UINT; @@ -140,12 +191,12 @@ pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { success == TRUE } -pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool { - // RIDEV_DEVNOTIFY: receive hotplug events - // RIDEV_INPUTSINK: receive events even if we're not in the foreground +pub fn register_for_raw_input(window_handle: HWND) -> bool { + // `RIDEV_DEVNOTIFY`: receive hotplug events + // `RIDEV_INPUTSINK`: receive events even if we're not in the foreground let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; - let devices: [RAWINPUTDEVICE; 2] = [ + let devices: [RAWINPUTDEVICE; 5] = [ RAWINPUTDEVICE { usUsagePage: HID_USAGE_PAGE_GENERIC, usUsage: HID_USAGE_GENERIC_MOUSE, @@ -158,27 +209,182 @@ pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> boo dwFlags: flags, hwndTarget: window_handle, }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_JOYSTICK, + dwFlags: flags, + hwndTarget: window_handle, + }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_GAMEPAD, + dwFlags: flags, + hwndTarget: window_handle, + }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: 0x08, // multi-axis + dwFlags: flags, + hwndTarget: window_handle, + }, ]; register_raw_input_devices(&devices) } -pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { - let mut data: RAWINPUT = unsafe { mem::uninitialized() }; - let mut data_size = size_of::() as UINT; +pub enum RawInputData { + Mouse { + device_handle: HANDLE, + raw_mouse: winuser::RAWMOUSE, + }, + Keyboard { + device_handle: HANDLE, + raw_keyboard: winuser::RAWKEYBOARD, + }, + Hid { + device_handle: HANDLE, + raw_hid: RawHidData, + }, +} + +pub struct RawHidData { + pub hid_input_size: u32, + pub hid_input_count: u32, + pub raw_input: Box<[u8]>, +} + +pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { + let mut data_size = 0; let header_size = size_of::() as UINT; - let status = unsafe { + // There are two classes of data this function can output: + // - Raw mouse and keyboard data + // - Raw HID data + // The first class (mouse and keyboard) is always going to write data formatted like the + // `RAWINPUT` struct, with no other data, and can be placed on the stack into `RAWINPUT`. + // The second class (raw HID data) writes the struct, and then a buffer of data appended to + // the end. That data needs to be heap-allocated so we can store all of it. + unsafe { winuser::GetRawInputData( handle, RID_INPUT, - &mut data as *mut _ as _, + ptr::null_mut(), &mut data_size, header_size, ) }; - if status == UINT::max_value() || status == 0 { + let (status, data): (INT, RawInputData); + + if data_size <= size_of::() as UINT { + // Since GetRawInputData is going to write... well, a buffer that's `RAWINPUT` bytes long + // and structured like `RAWINPUT`, we're just going to cut to the chase and write directly into + // a `RAWINPUT` struct. + let mut rawinput_data: RAWINPUT = unsafe { mem::uninitialized() }; + + status = unsafe { + winuser::GetRawInputData( + handle, + RID_INPUT, + &mut rawinput_data as *mut RAWINPUT as *mut _, + &mut data_size, + header_size, + ) + } as INT; + + assert_ne!(-1, status); + + let device_handle = rawinput_data.header.hDevice; + + data = match rawinput_data.header.dwType { + winuser::RIM_TYPEMOUSE => { + let raw_mouse = unsafe { rawinput_data.data.mouse().clone() }; + RawInputData::Mouse { + device_handle, + raw_mouse, + } + } + winuser::RIM_TYPEKEYBOARD => { + let raw_keyboard = unsafe { rawinput_data.data.keyboard().clone() }; + RawInputData::Keyboard { + device_handle, + raw_keyboard, + } + } + winuser::RIM_TYPEHID => { + let hid_data = unsafe { rawinput_data.data.hid() }; + let buf_len = hid_data.dwSizeHid as usize * hid_data.dwCount as usize; + let data = unsafe { slice::from_raw_parts(hid_data.bRawData.as_ptr(), buf_len) }; + RawInputData::Hid { + device_handle, + raw_hid: RawHidData { + hid_input_size: hid_data.dwSizeHid, + hid_input_count: hid_data.dwCount, + raw_input: Box::from(data), + }, + } + } + _ => unreachable!(), + }; + } else { + let mut buf = vec![0u8; data_size as usize]; + + status = unsafe { + winuser::GetRawInputData( + handle, + RID_INPUT, + buf.as_mut_ptr() as *mut _, + &mut data_size, + header_size, + ) + } as INT; + + let rawinput_data = buf.as_ptr() as *const RAWINPUT; + + let device_handle = unsafe { (&*rawinput_data).header.hDevice }; + + data = match unsafe { *rawinput_data }.header.dwType { + winuser::RIM_TYPEMOUSE => { + let raw_mouse = unsafe { (&*rawinput_data).data.mouse().clone() }; + RawInputData::Mouse { + device_handle, + raw_mouse, + } + } + winuser::RIM_TYPEKEYBOARD => { + let raw_keyboard = unsafe { (&*rawinput_data).data.keyboard().clone() }; + RawInputData::Keyboard { + device_handle, + raw_keyboard, + } + } + winuser::RIM_TYPEHID => { + let hid_data: winuser::RAWHID = unsafe { (&*rawinput_data).data.hid().clone() }; + + let hid_data_index = { + let hid_data_start = + unsafe { &((&*rawinput_data).data.hid().bRawData) } as *const _; + hid_data_start as usize - buf.as_ptr() as usize + }; + + buf.drain(..hid_data_index); + + RawInputData::Hid { + device_handle, + raw_hid: RawHidData { + hid_input_size: hid_data.dwSizeHid, + hid_input_count: hid_data.dwCount, + raw_input: buf.into_boxed_slice(), + }, + } + } + _ => unreachable!(), + }; + + assert_ne!(-1, status); + } + + if status == 0 { return None; } @@ -200,7 +406,7 @@ fn button_flags_to_element_state( } } -pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option; 3] { +pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option; 5] { [ button_flags_to_element_state( button_flags, @@ -217,5 +423,264 @@ pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option winuser::RI_MOUSE_RIGHT_BUTTON_DOWN, winuser::RI_MOUSE_RIGHT_BUTTON_UP, ), + button_flags_to_element_state( + button_flags, + winuser::RI_MOUSE_BUTTON_4_DOWN, + winuser::RI_MOUSE_BUTTON_4_UP, + ), + button_flags_to_element_state( + button_flags, + winuser::RI_MOUSE_BUTTON_5_DOWN, + winuser::RI_MOUSE_BUTTON_5_UP, + ), ] } + +pub struct Axis { + caps: HIDP_VALUE_CAPS, + value: f64, + prev_value: f64, + axis: Option, +} + +impl fmt::Debug for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[derive(Debug)] + struct Axis { + value: f64, + prev_value: f64, + axis: Option, + } + + let axis_proxy = Axis { + value: self.value, + prev_value: self.prev_value, + axis: self.axis, + }; + + axis_proxy.fmt(f) + } +} + +#[derive(Debug)] +pub struct RawGamepad { + handle: HANDLE, + pre_parsed_data: Vec, + button_count: usize, + pub button_state: Vec, + pub prev_button_state: Vec, + axis_count: usize, + pub axis_state: Vec, +} + +// Reference: https://chromium.googlesource.com/chromium/chromium/+/trunk/content/browser/gamepad/raw_input_data_fetcher_win.cc +impl RawGamepad { + pub fn new(handle: HANDLE) -> Option { + let pre_parsed_data = get_raw_input_pre_parse_info(handle)?; + let data_ptr = pre_parsed_data.as_ptr() as PHIDP_PREPARSED_DATA; + let mut caps = unsafe { mem::uninitialized() }; + let status = unsafe { HidP_GetCaps(data_ptr, &mut caps) }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + let mut button_caps_len = caps.NumberInputButtonCaps; + let mut button_caps = Vec::with_capacity(button_caps_len as _); + let status = unsafe { + HidP_GetButtonCaps( + HidP_Input, + button_caps.as_mut_ptr(), + &mut button_caps_len, + data_ptr, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { button_caps.set_len(button_caps_len as _) }; + let mut button_count = 0; + for button_cap in button_caps { + let range = unsafe { button_cap.u.Range() }; + button_count = max(button_count, range.UsageMax); + } + let button_state = vec![false; button_count as usize]; + let mut axis_caps_len = caps.NumberInputValueCaps; + let mut axis_caps = Vec::with_capacity(axis_caps_len as _); + let status = unsafe { + HidP_GetValueCaps( + HidP_Input, + axis_caps.as_mut_ptr(), + &mut axis_caps_len, + data_ptr, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { axis_caps.set_len(axis_caps_len as _) }; + let mut axis_state = Vec::with_capacity(axis_caps_len as _); + let mut axis_count = 0; + for (axis_index, axis_cap) in axis_caps.drain(0..).enumerate() { + axis_state.push(Axis { + caps: axis_cap, + value: 0.0, + prev_value: 0.0, + axis: None, + }); + axis_count = max(axis_count, axis_index + 1); + } + Some(RawGamepad { + handle, + pre_parsed_data, + button_count: button_count as usize, + button_state: button_state.clone(), + prev_button_state: button_state, + axis_count, + axis_state, + }) + } + + fn pre_parsed_data_ptr(&mut self) -> PHIDP_PREPARSED_DATA { + self.pre_parsed_data.as_mut_ptr() as PHIDP_PREPARSED_DATA + } + + fn update_button_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + let pre_parsed_data_ptr = self.pre_parsed_data_ptr(); + self.prev_button_state = + mem::replace(&mut self.button_state, vec![false; self.button_count]); + let mut usages_len = 0; + // This is the officially documented way to get the required length, but it nonetheless returns + // `HIDP_STATUS_BUFFER_TOO_SMALL`... + unsafe { + HidP_GetUsagesEx( + HidP_Input, + 0, + ptr::null_mut(), + &mut usages_len, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + let mut usages = Vec::with_capacity(usages_len as _); + let status = unsafe { + HidP_GetUsagesEx( + HidP_Input, + 0, + usages.as_mut_ptr(), + &mut usages_len, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { usages.set_len(usages_len as _) }; + for usage in usages { + if usage.UsagePage != 0xFF << 8 { + let button_index = (usage.Usage - 1) as usize; + self.button_state[button_index] = true; + } + } + Some(()) + } + + fn update_axis_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + let pre_parsed_data_ptr = self.pre_parsed_data_ptr(); + for axis in &mut self.axis_state { + let (status, axis_value) = if axis.caps.LogicalMin < 0 { + let mut scaled_axis_value = 0; + let status = unsafe { + HidP_GetScaledUsageValue( + HidP_Input, + axis.caps.UsagePage, + 0, + axis.caps.u.Range().UsageMin, + &mut scaled_axis_value, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + (status, scaled_axis_value as f64) + } else { + let mut axis_value = 0; + let status = unsafe { + HidP_GetUsageValue( + HidP_Input, + axis.caps.UsagePage, + 0, + axis.caps.u.Range().UsageMin, + &mut axis_value, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + (status, axis_value as f64) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + axis.prev_value = axis.value; + axis.value = util::normalize_symmetric( + axis_value, + axis.caps.LogicalMin as f64, + axis.caps.LogicalMax as f64, + ); + } + Some(()) + } + + pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + self.update_button_state(raw_input_report)?; + self.update_axis_state(raw_input_report)?; + Some(()) + } + + pub fn get_changed_buttons(&self) -> impl '_ + Iterator { + self.button_state + .iter() + .zip(self.prev_button_state.iter()) + .enumerate() + .filter(|&(_, (button, prev_button))| button != prev_button) + .map(|(index, (button, _))| { + let state = if *button { + ElementState::Pressed + } else { + ElementState::Released + }; + GamepadEvent::Button { + button_id: index as _, + button: None, + state, + } + }) + } + + pub fn get_changed_axes(&self) -> impl '_ + Iterator { + self.axis_state + .iter() + .enumerate() + .filter(|&(_, axis)| axis.value != axis.prev_value) + .map(|(index, axis)| GamepadEvent::Axis { + axis_id: index as _, + axis: axis.axis, + value: axis.value, + stick: false, + }) + } + + pub fn get_gamepad_events(&self) -> Vec { + self.get_changed_axes() + .chain(self.get_changed_buttons()) + .collect() + } + + // pub fn rumble(&mut self, _left_speed: u16, _right_speed: u16) { + // // Even though I can't read German, this is still the most useful resource I found: + // // https://zfx.info/viewtopic.php?t=3574&f=7 + // // I'm not optimistic about it being possible to implement this. + // } +} diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index edb5769e5c..d87ba1b6a5 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -1,7 +1,5 @@ use std::{ - io, mem, - ops::BitAnd, - ptr, slice, + io, mem, ptr, slice, sync::atomic::{AtomicBool, Ordering}, }; @@ -15,12 +13,7 @@ use winapi::{ um::{winbase::lstrlenW, winuser}, }; -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag -} +pub use crate::util::*; pub fn wchar_to_string(wchar: &[wchar_t]) -> String { String::from_utf16_lossy(wchar).to_string() diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 64419204fe..a1757a8d42 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -40,9 +40,7 @@ use crate::{ REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, }, icon::{self, IconType, WinIcon}, - monitor, - raw_input::register_all_mice_and_keyboards_for_raw_input, - util, + monitor, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, @@ -85,12 +83,12 @@ impl Window { panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`"); } - let file_drop_runner = event_loop.runner_shared.clone(); + let shared_data = event_loop.shared_data.clone(); let file_drop_handler = FileDropHandler::new( win.window.0, Box::new(move |event| { if let Ok(e) = event.map_nonuser_event() { - file_drop_runner.send_event(e) + shared_data.runner_shared.send_event(e) } }), ); @@ -106,7 +104,7 @@ impl Window { let subclass_input = event_loop::SubclassInput { window_state: win.window_state.clone(), - event_loop_runner: event_loop.runner_shared.clone(), + shared_data: event_loop.shared_data.clone(), file_drop_handler, }; @@ -698,9 +696,6 @@ unsafe fn init( WindowWrapper(handle) }; - // Set up raw input - register_all_mice_and_keyboards_for_raw_input(real_window.0); - // Register for touch events if applicable { let digitizer = winuser::GetSystemMetrics(winuser::SM_DIGITIZER) as u32; diff --git a/src/platform_impl/windows/xinput.rs b/src/platform_impl/windows/xinput.rs new file mode 100644 index 0000000000..9984c8a512 --- /dev/null +++ b/src/platform_impl/windows/xinput.rs @@ -0,0 +1,334 @@ +use std::{ + io, mem, + sync::{Arc, Weak}, +}; + +use rusty_xinput::*; +use winapi::{ + shared::minwindef::{DWORD, WORD}, + um::xinput::*, +}; + +use crate::{ + event::{ + device::{BatteryLevel, GamepadAxis, GamepadButton, GamepadEvent, RumbleError, Side}, + ElementState, + }, + platform_impl::platform::util, +}; + +lazy_static! { + static ref XINPUT_HANDLE: Option = XInputHandle::load_default().ok(); +} + +static BUTTONS: &[(WORD, u32, GamepadButton)] = &[ + (XINPUT_GAMEPAD_DPAD_UP, 12, GamepadButton::DPadUp), + (XINPUT_GAMEPAD_DPAD_DOWN, 13, GamepadButton::DPadDown), + (XINPUT_GAMEPAD_DPAD_LEFT, 14, GamepadButton::DPadLeft), + (XINPUT_GAMEPAD_DPAD_RIGHT, 15, GamepadButton::DPadRight), + (XINPUT_GAMEPAD_START, 9, GamepadButton::Start), + (XINPUT_GAMEPAD_BACK, 8, GamepadButton::Select), + (XINPUT_GAMEPAD_LEFT_THUMB, 10, GamepadButton::LeftStick), + (XINPUT_GAMEPAD_RIGHT_THUMB, 11, GamepadButton::RightStick), + (XINPUT_GAMEPAD_LEFT_SHOULDER, 4, GamepadButton::LeftShoulder), + ( + XINPUT_GAMEPAD_RIGHT_SHOULDER, + 5, + GamepadButton::RightShoulder, + ), + (XINPUT_GAMEPAD_A, 0, GamepadButton::South), + (XINPUT_GAMEPAD_B, 1, GamepadButton::East), + (XINPUT_GAMEPAD_X, 2, GamepadButton::West), + (XINPUT_GAMEPAD_Y, 3, GamepadButton::North), +]; + +pub fn id_from_name(name: &str) -> Option { + // A device name looks like \\?\HID#VID_046D&PID_C21D&IG_00#8&6daf3b6&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} + // The IG_00 substring indicates that this is an XInput gamepad, and that the ID is 00 + let pat = "IG_0"; + name.find(pat) + .and_then(|i| name[i + pat.len()..].chars().next()) + .and_then(|c| match c { + '0' => Some(0), + '1' => Some(1), + '2' => Some(2), + '3' => Some(3), + _ => None, + }) +} + +#[derive(Debug)] +pub struct XInputGamepad { + shared: Arc, + prev_state: Option, + state: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct XInputGamepadShared { + port: DWORD, +} + +impl XInputGamepad { + pub fn new(port: DWORD) -> Option { + XINPUT_HANDLE.as_ref().map(|_| XInputGamepad { + shared: Arc::new(XInputGamepadShared { port }), + prev_state: None, + state: None, + }) + } + + pub fn update_state(&mut self) -> Option<()> { + let state = XINPUT_HANDLE + .as_ref() + .and_then(|h| h.get_state(self.shared.port).ok()); + if state.is_some() { + self.prev_state = mem::replace(&mut self.state, state); + Some(()) + } else { + None + } + } + + fn check_trigger_digital( + events: &mut Vec, + value: bool, + prev_value: Option, + side: Side, + ) { + const LEFT_TRIGGER_ID: u32 = /*BUTTONS.len() as _*/ 16; + const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1; + if Some(value) != prev_value { + let state = if value { + ElementState::Pressed + } else { + ElementState::Released + }; + let (button_id, button) = match side { + Side::Left => (LEFT_TRIGGER_ID, Some(GamepadButton::LeftTrigger)), + Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadButton::RightTrigger)), + }; + events.push(GamepadEvent::Button { + button_id, + button, + state, + }); + } + } + + pub fn get_changed_buttons(&self, events: &mut Vec) { + let (buttons, left_trigger, right_trigger) = match self.state.as_ref() { + Some(state) => ( + state.raw.Gamepad.wButtons, + state.left_trigger_bool(), + state.right_trigger_bool(), + ), + None => return, + }; + let (prev_buttons, prev_left, prev_right) = self + .prev_state + .as_ref() + .map(|state| { + ( + state.raw.Gamepad.wButtons, + Some(state.left_trigger_bool()), + Some(state.right_trigger_bool()), + ) + }) + .unwrap_or_else(|| (0, None, None)); + /* + A = buttons + B = prev_buttons + C = changed + P = pressed + R = released + A B C C A P C B R + (0 0) 0 (0 0) 0 (0 0) 0 + (0 1) 1 (1 1) 1 (1 0) 0 + (1 0) 1 (1 0) 0 (1 1) 1 + (1 1) 0 (0 1) 0 (0 1) 0 + */ + let changed = buttons ^ prev_buttons; + let pressed = changed & buttons; + let released = changed & prev_buttons; + for &(flag, button_id, button) in BUTTONS { + let button = Some(button); + if util::has_flag(pressed, flag) { + events.push(GamepadEvent::Button { + button_id, + button, + state: ElementState::Pressed, + }); + } else if util::has_flag(released, flag) { + events.push(GamepadEvent::Button { + button_id, + button, + state: ElementState::Released, + }); + } + } + Self::check_trigger_digital(events, left_trigger, prev_left, Side::Left); + Self::check_trigger_digital(events, right_trigger, prev_right, Side::Right); + } + + fn check_trigger( + events: &mut Vec, + value: u8, + prev_value: Option, + side: Side, + ) { + const LEFT_TRIGGER_ID: u32 = 4; + const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1; + if Some(value) != prev_value { + let (axis_id, axis) = match side { + Side::Left => (LEFT_TRIGGER_ID, Some(GamepadAxis::LeftTrigger)), + Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadAxis::RightTrigger)), + }; + events.push(GamepadEvent::Axis { + axis_id, + axis, + value: value as f64 / u8::max_value() as f64, + stick: false, + }); + } + } + + fn check_stick( + events: &mut Vec, + value: (i16, i16), + prev_value: Option<(i16, i16)>, + stick: Side, + ) { + let (id, axis) = match stick { + Side::Left => ((0, 1), (GamepadAxis::LeftStickX, GamepadAxis::LeftStickY)), + Side::Right => ((2, 3), (GamepadAxis::RightStickX, GamepadAxis::RightStickY)), + }; + let prev_x = prev_value.map(|prev| prev.0); + let prev_y = prev_value.map(|prev| prev.1); + + let value_f64 = |value_int: i16| match value_int.signum() { + 0 => 0.0, + 1 => value_int as f64 / i16::max_value() as f64, + -1 => value_int as f64 / (i16::min_value() as f64).abs(), + _ => unreachable!(), + }; + + let value_f64 = (value_f64(value.0), value_f64(value.1)); + if prev_x != Some(value.0) { + events.push(GamepadEvent::Axis { + axis_id: id.0, + axis: Some(axis.0), + value: value_f64.0, + stick: true, + }); + } + if prev_y != Some(value.1) { + events.push(GamepadEvent::Axis { + axis_id: id.1, + axis: Some(axis.1), + value: value_f64.1, + stick: true, + }); + } + if prev_x != Some(value.0) || prev_y != Some(value.1) { + events.push(GamepadEvent::Stick { + x_id: id.0, + y_id: id.1, + x_value: value_f64.0, + y_value: value_f64.1, + side: stick, + }) + } + } + + pub fn get_changed_axes(&self, events: &mut Vec) { + let state = match self.state { + Some(ref state) => state, + None => return, + }; + let left_stick = state.left_stick_raw(); + let right_stick = state.right_stick_raw(); + let left_trigger = state.left_trigger(); + let right_trigger = state.right_trigger(); + + let prev_state = self.prev_state.as_ref(); + let prev_left_stick = prev_state.map(|state| state.left_stick_raw()); + let prev_right_stick = prev_state.map(|state| state.right_stick_raw()); + let prev_left_trigger = prev_state.map(|state| state.left_trigger()); + let prev_right_trigger = prev_state.map(|state| state.right_trigger()); + + Self::check_stick(events, left_stick, prev_left_stick, Side::Left); + Self::check_stick(events, right_stick, prev_right_stick, Side::Right); + Self::check_trigger(events, left_trigger, prev_left_trigger, Side::Left); + Self::check_trigger(events, right_trigger, prev_right_trigger, Side::Right); + } + + pub fn get_gamepad_events(&self) -> Vec { + let mut events = Vec::new(); + self.get_changed_axes(&mut events); + self.get_changed_buttons(&mut events); + events + } + + pub fn shared_data(&self) -> Weak { + Arc::downgrade(&self.shared) + } +} + +impl Drop for XInputGamepad { + fn drop(&mut self) { + // For some reason, if you don't attempt to retrieve the xinput gamepad state at least once + // after the gamepad was disconnected, all future attempts to read from a given port (even + // if a controller was plugged back into said port) will fail! I don't know why that happens, + // but this fixes it, so 🤷. + XINPUT_HANDLE + .as_ref() + .and_then(|h| h.get_state(self.shared.port).ok()); + } +} + +impl XInputGamepadShared { + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + let left_speed = (left_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16; + let right_speed = (right_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16; + + let result = XINPUT_HANDLE + .as_ref() + .unwrap() + .set_state(self.port, left_speed, right_speed); + result.map_err(|e| match e { + XInputUsageError::XInputNotLoaded | XInputUsageError::InvalidControllerID => panic!( + "unexpected xinput error {:?}; this is a bug and should be reported", + e + ), + XInputUsageError::DeviceNotConnected => RumbleError::DeviceNotConnected, + XInputUsageError::UnknownError(code) => { + RumbleError::OsError(io::Error::from_raw_os_error(code as i32)) + } + }) + } + + pub fn port(&self) -> u8 { + self.port as _ + } + + pub fn battery_level(&self) -> Option { + use rusty_xinput::BatteryLevel as XBatteryLevel; + + let battery_info = XINPUT_HANDLE + .as_ref() + .unwrap() + .get_gamepad_battery_information(self.port) + .ok()?; + match battery_info.battery_type { + BatteryType::ALKALINE | BatteryType::NIMH => match battery_info.battery_level { + XBatteryLevel::EMPTY => Some(BatteryLevel::Empty), + XBatteryLevel::LOW => Some(BatteryLevel::Low), + XBatteryLevel::MEDIUM => Some(BatteryLevel::Medium), + XBatteryLevel::FULL => Some(BatteryLevel::Full), + _ => None, + }, + _ => None, + } + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000000..18e8c1941b --- /dev/null +++ b/src/util.rs @@ -0,0 +1,29 @@ +use std::ops::BitAnd; + +pub fn has_flag(bitset: T, flag: T) -> bool +where + T: Copy + PartialEq + BitAnd, +{ + bitset & flag == flag +} + +pub fn clamp(value: f64, min: f64, max: f64) -> f64 { + if value > max { + max + } else if value < min { + min + } else { + value + } +} + +pub fn normalize_asymmetric(value: f64, min: f64, max: f64) -> f64 { + let range = max - min; + let translated = value - min; + let scaled = translated / range; + clamp(scaled, 0.0, 1.0) +} + +pub fn normalize_symmetric(value: f64, min: f64, max: f64) -> f64 { + (2.0 * normalize_asymmetric(value, min, max)) - 1.0 +} diff --git a/tests/send_objects.rs b/tests/send_objects.rs index 9462d0736f..f214cd4732 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -20,6 +20,8 @@ fn window_send() { fn ids_send() { // ensures that the various `..Id` types implement `Send` needs_send::(); - needs_send::(); + needs_send::(); + needs_send::(); + needs_send::(); needs_send::(); }