diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f2165345..5b2cf9001b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,20 @@ - On Wayland, the window now exists even if nothing has been drawn. - On Windows, fix initial dimensions of a fullscreen window. +- 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 a4b8d9fa63..707e71f050 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 59441c2233..dab7474fea 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -14,7 +14,7 @@ fn main() { event_loop.run(move |event, _, control_flow| { match event { - Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => { + Event::WindowEvent { event: WindowEvent::KeyboardInput(KeyboardInput { state: ElementState::Pressed, .. }), .. } => { println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); window.set_cursor_icon(CURSORS[cursor_idx]); if cursor_idx < CURSORS.len() - 1 { diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 8e7b1f7e3b..87b576d115 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -17,15 +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 8827751f19..2459a87e59 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..a0e3886043 --- /dev/null +++ b/examples/gamepad.rs @@ -0,0 +1,41 @@ +extern crate winit; +use winit::window::WindowBuilder; +use winit::event::{Event, WindowEvent}; +use winit::event::device::{GamepadEvent, GamepadHandle}; +use winit::event_loop::{EventLoop, ControlFlow}; + +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..2578f307c7 --- /dev/null +++ b/examples/gamepad_rumble.rs @@ -0,0 +1,58 @@ +extern crate winit; +use winit::event_loop::EventLoop; +use std::time::Instant; + +#[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 31f17da33e..8e01deaaaa 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -38,15 +38,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 { + }) => match virtual_code { Y => { if close_requested { // This is where you'll want to do any cleanup you need. diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 054087994e..a48e390285 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -25,12 +25,12 @@ fn main() { thread::spawn(move || { while let Ok(event) = rx.recv() { match event { - WindowEvent::KeyboardInput { input: KeyboardInput { + WindowEvent::KeyboardInput (KeyboardInput { state: ElementState::Released, virtual_keycode: Some(key), modifiers, .. - }, .. } => { + }) => { window.set_title(&format!("{:?}", key)); let state = !modifiers.shift; use self::VirtualKeyCode::*; @@ -99,9 +99,9 @@ fn main() { match event { WindowEvent::CloseRequested | WindowEvent::Destroyed - | WindowEvent::KeyboardInput { input: KeyboardInput { + | WindowEvent::KeyboardInput(KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Escape), - .. }, .. } => { + .. }) => { window_senders.remove(&window_id); }, _ => if let Some(tx) = window_senders.get(&window_id) { diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index cf2703c2dd..5657f72f25 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -29,7 +29,7 @@ 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 969ce04b3a..39bf005ecb 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -20,15 +20,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 e8db64a0a3..ec2dc758b7 100644 --- a/src/event.rs +++ b/src/event.rs @@ -9,9 +9,10 @@ use std::path::PathBuf; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::window::WindowId; -use crate::platform_impl; -/// 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. @@ -19,11 +20,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. @@ -48,7 +53,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), @@ -57,7 +65,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 @@ -83,7 +91,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. @@ -125,12 +133,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. @@ -139,16 +145,16 @@ 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 }, + MouseWheel { delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState }, /// An mouse button press has been received. - MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton, modifiers: ModifiersState }, + MouseInput { state: ElementState, button: MouseButton, modifiers: ModifiersState }, /// Touchpad pressure event. @@ -156,10 +162,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, @@ -179,64 +182,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 { @@ -262,37 +208,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, - Cancelled + /// 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 +/// A 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. -/// -/// -/// 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 } @@ -305,16 +244,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, @@ -323,7 +262,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 { @@ -342,7 +281,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))] @@ -541,7 +480,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..c268891585 --- /dev/null +++ b/src/event/device.rs @@ -0,0 +1,373 @@ +//! 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::{ + platform_impl, + dpi::PhysicalPosition, + event::{AxisId, ButtonId, ElementState, KeyboardInput, MouseButton}, + event_loop::EventLoop, +}; +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, + + // @francesca64 review: why were these variants here? I don't see how it makes sense for the dpad or hat switch + // to have axes, since they're both four separate buttons. + + // /// This is supposed to have a specialized meaning, referring to a point-of-view switch present on joysticks used + // /// for flight simulation. However, Xbox 360 controllers (and their derivatives) use a hat switch for the D-pad. + // HatSwitch, + // DPadUp, + // DPadDown, + // DPadLeft, + // DPadRight, +} + +/// 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 f772148826..4e277d754f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,5 +104,6 @@ mod icon; mod platform_impl; pub mod window; pub mod monitor; +mod util; pub mod platform; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 0202ac8644..afa1e9b8de 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -5,9 +5,9 @@ use std::os::raw::c_void; use libc; use winapi::shared::windef::HWND; -use crate::event::DeviceId; use crate::monitor::MonitorHandle; use crate::event_loop::EventLoop; +use crate::event::device::{MouseId, KeyboardId, GamepadHandle}; use crate::window::{Icon, Window, WindowBuilder}; use crate::platform_impl::EventLoop as WindowsEventLoop; @@ -104,17 +104,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 4c399386ae..483dfa6bff 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -22,7 +22,7 @@ use std::sync::mpsc::{self, Sender, Receiver}; use std::time::{Duration, Instant}; use std::rc::Rc; use std::cell::RefCell; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use std::marker::PhantomData; use parking_lot::Mutex; @@ -41,45 +41,55 @@ use winapi::shared::minwindef::{ use winapi::shared::windef::{HWND, POINT, RECT}; use winapi::shared::{windowsx, winerror}; use winapi::um::{winuser, winbase, ole2, processthreadsapi, commctrl, libloaderapi}; -use winapi::um::winnt::{LONG, LPCSTR, SHORT}; +use winapi::um::winnt::{HANDLE, LONG, LPCSTR, SHORT}; use crate::window::WindowId as RootWindowId; use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; -use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; -use crate::event::{DeviceEvent, Touch, TouchPhase, StartCause, KeyboardInput, Event, WindowEvent}; -use crate::platform_impl::platform::{event, WindowId, DEVICE_ID, wrap_device_id, util}; -use crate::platform_impl::platform::dpi::{ - become_dpi_aware, - dpi_to_scale_factor, - enable_non_client_dpi_scaling, - hwnd_scale_factor, +use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use crate::event::{ + MouseButton, Touch, TouchPhase, StartCause, KeyboardInput, Event, WindowEvent, + device::{GamepadEvent, KeyboardEvent, HidEvent, MouseEvent} +}; +use crate::platform_impl::platform::{ + MouseId, KeyboardId, HidId, GamepadHandle, WindowId, + dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_scale_factor}, + drop_handler::FileDropHandler, + event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, + gamepad::Gamepad, + raw_input::{self, get_raw_input_data, get_raw_mouse_button_state, RawInputData}, + util, + window::adjust_size, + window_state::{CursorFlags, WindowFlags, WindowState}, }; -use crate::platform_impl::platform::drop_handler::FileDropHandler; -use crate::platform_impl::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey}; -use crate::platform_impl::platform::raw_input::{get_raw_input_data, get_raw_mouse_button_state}; -use crate::platform_impl::platform::window::adjust_size; -use crate::platform_impl::platform::window_state::{CursorFlags, WindowFlags, WindowState}; 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); } } @@ -92,7 +102,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 { @@ -108,11 +118,14 @@ 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()); + let (thread_msg_target, thread_msg_sender) = thread_event_target_window(shared_data.clone()); EventLoop { thread_msg_sender, @@ -121,7 +134,7 @@ impl EventLoop { thread_id, trigger_newevents_on_redraw: Arc::new(AtomicBool::new(true)), thread_msg_target, - runner_shared, + shared_data, }, _marker: PhantomData, }, @@ -149,10 +162,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); }, None => break @@ -162,7 +175,7 @@ impl EventLoop { } macro_rules! runner { - () => { self.window_target.p.runner_shared.runner.borrow_mut().as_mut().unwrap() }; + () => { self.window_target.p.shared_data.runner_shared.runner.borrow_mut().as_mut().unwrap() }; } unsafe { @@ -204,7 +217,7 @@ 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 { @@ -213,6 +226,63 @@ 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 { @@ -226,7 +296,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>>, @@ -691,7 +765,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, @@ -717,7 +791,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)); @@ -729,6 +803,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) } } @@ -781,14 +858,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; } @@ -837,7 +914,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 @@ -956,7 +1033,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. @@ -975,7 +1052,7 @@ 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() }, + event: CursorMoved { position, modifiers: event::get_key_mods() }, }); 0 @@ -990,7 +1067,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 @@ -1005,7 +1082,7 @@ 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() }, + event: WindowEvent::MouseWheel { delta: LineDelta(0.0, value), phase: TouchPhase::Moved, modifiers: event::get_key_mods() }, }); 0 @@ -1020,7 +1097,7 @@ 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() }, + event: WindowEvent::MouseWheel { delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, modifiers: event::get_key_mods() }, }); 0 @@ -1035,15 +1112,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: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + state: Pressed, + scancode: 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. @@ -1063,15 +1137,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: Released, - scancode: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + state: Released, + scancode: scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + }), }); } 0 @@ -1079,91 +1150,84 @@ unsafe extern "system" fn public_window_callback( winuser::WM_LBUTTONDOWN => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Left; use crate::event::ElementState::Pressed; 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, modifiers: event::get_key_mods() }, + event: MouseInput { state: Pressed, button: MouseButton::Left, modifiers: event::get_key_mods() }, }); 0 }, winuser::WM_LBUTTONUP => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Left; use crate::event::ElementState::Released; 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, modifiers: event::get_key_mods() }, + event: MouseInput { state: Released, button: MouseButton::Left, modifiers: event::get_key_mods() }, }); 0 }, winuser::WM_RBUTTONDOWN => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Right; use crate::event::ElementState::Pressed; 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, modifiers: event::get_key_mods() }, + event: MouseInput { state: Pressed, button: MouseButton::Right, modifiers: event::get_key_mods() }, }); 0 }, winuser::WM_RBUTTONUP => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Right; use crate::event::ElementState::Released; 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, modifiers: event::get_key_mods() }, + event: MouseInput { state: Released, button: MouseButton::Right, modifiers: event::get_key_mods() }, }); 0 }, winuser::WM_MBUTTONDOWN => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Middle; use crate::event::ElementState::Pressed; 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, modifiers: event::get_key_mods() }, + event: MouseInput { state: Pressed, button: MouseButton::Middle, modifiers: event::get_key_mods() }, }); 0 }, winuser::WM_MBUTTONUP => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Middle; use crate::event::ElementState::Released; 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, modifiers: event::get_key_mods() }, + event: MouseInput { state: Released, button: MouseButton::Middle, modifiers: event::get_key_mods() }, }); 0 }, winuser::WM_XBUTTONDOWN => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Other; use crate::event::ElementState::Pressed; let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); @@ -1171,14 +1235,13 @@ 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), modifiers: event::get_key_mods() }, + event: MouseInput { state: Pressed, button: MouseButton::Other(xbutton as u8), modifiers: event::get_key_mods() }, }); 0 }, winuser::WM_XBUTTONUP => { use crate::event::WindowEvent::MouseInput; - use crate::event::MouseButton::Other; use crate::event::ElementState::Released; let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); @@ -1186,130 +1249,11 @@ 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), 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, + event: MouseInput { state: Released, button: MouseButton::Other(xbutton as u8), modifiers: event::get_key_mods() }, }); - 0 }, - winuser::WM_INPUT => { - use crate::event::DeviceEvent::{Motion, MouseMotion, MouseWheel, Button, Key}; - use crate::event::MouseScrollDelta::LineDelta; - use crate::event::ElementState::{Pressed, Released}; - - 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 ); @@ -1341,7 +1285,6 @@ unsafe extern "system" fn public_window_callback( }, location, id: input.dwID as u64, - device_id: DEVICE_ID, }), }); } @@ -1547,7 +1490,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 { @@ -1582,7 +1525,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 { @@ -1603,6 +1546,245 @@ 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..9983bcf377 --- /dev/null +++ b/src/platform_impl/windows/gamepad.rs @@ -0,0 +1,93 @@ +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}, + platform_impl::platform::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 c0a06c0bc0..68ef14154b 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -1,14 +1,33 @@ #![cfg(target_os = "windows")] +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::{Ordering, Eq, Ord, PartialEq, PartialOrd}; +use std::hash::{Hash, Hasher}; +use std::fmt; +use std::ptr; use winapi; use winapi::shared::windef::HWND; +use winapi::um::winnt::HANDLE; pub use self::event_loop::{EventLoop, EventLoopWindowTarget, EventLoopProxy}; +pub use self::gamepad::GamepadShared; pub use self::monitor::MonitorHandle; pub use self::window::Window; use crate::window::Icon; -use crate::event::DeviceId as RootDeviceId; +use crate::event::device; #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -27,53 +46,146 @@ 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() + } + + pub fn battery_level(&self) -> Option { + self.shared_data.battery_level() + } - WindowId(null_mut()) + 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 c75bb75785..c444b97183 100644 --- a/src/platform_impl/windows/raw_input.rs +++ b/src/platform_impl/windows/raw_input.rs @@ -1,38 +1,62 @@ +use std::{fmt, ptr, slice}; +use std::cmp::max; use std::mem::{self, size_of}; -use std::ptr; use winapi::ctypes::wchar_t; -use winapi::shared::minwindef::{UINT, USHORT, TRUE}; +use winapi::shared::minwindef::{TRUE, INT, UINT, USHORT}; +use winapi::shared::hidpi::{ + HidP_GetButtonCaps, + HidP_GetCaps, + HidP_GetScaledUsageValue, + HidP_GetUsagesEx, + HidP_GetUsageValue, + HidP_GetValueCaps, + HidP_Input, + /*HIDP_STATUS_BUFFER_TOO_SMALL, + HIDP_STATUS_INCOMPATIBLE_REPORT_ID, + HIDP_STATUS_INVALID_PREPARSED_DATA, + HIDP_STATUS_INVALID_REPORT_LENGTH, + HIDP_STATUS_INVALID_REPORT_TYPE,*/ + HIDP_STATUS_SUCCESS, + HIDP_VALUE_CAPS, + PHIDP_PREPARSED_DATA, +}; use winapi::shared::hidusage::{ HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, HID_USAGE_GENERIC_KEYBOARD, + HID_USAGE_GENERIC_JOYSTICK, + HID_USAGE_GENERIC_GAMEPAD, }; use winapi::shared::windef::HWND; -use winapi::um::winnt::HANDLE; +use winapi::um::winnt::{HANDLE, PCHAR}; use winapi::um::winuser::{ self, + HRAWINPUT, + RAWINPUT, + RAWINPUTDEVICE, RAWINPUTDEVICELIST, + RAWINPUTHEADER, + RID_INPUT, RID_DEVICE_INFO, - RID_DEVICE_INFO_MOUSE, - RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_HID, + RID_DEVICE_INFO_KEYBOARD, + RID_DEVICE_INFO_MOUSE, + RIDEV_DEVNOTIFY, + RIDEV_INPUTSINK, + RIDI_DEVICEINFO, + RIDI_DEVICENAME, + RIDI_PREPARSEDDATA, RIM_TYPEMOUSE, RIM_TYPEKEYBOARD, RIM_TYPEHID, - RIDI_DEVICEINFO, - RIDI_DEVICENAME, - RAWINPUTDEVICE, - RIDEV_DEVNOTIFY, - RIDEV_INPUTSINK, - HRAWINPUT, - RAWINPUT, - RAWINPUTHEADER, - RID_INPUT, }; use crate::platform_impl::platform::util; -use crate::event::ElementState; +use crate::event::{ + ElementState, + device::{GamepadAxis, GamepadEvent}, +}; #[allow(dead_code)] pub fn get_raw_input_device_list() -> Option> { @@ -68,7 +92,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), @@ -88,26 +111,25 @@ 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()) } @@ -145,6 +167,39 @@ 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; @@ -157,12 +212,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, @@ -175,32 +230,169 @@ 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 { winuser::GetRawInputData( + // 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; } Some(data) } - fn button_flags_to_element_state(button_flags: USHORT, down_flag: USHORT, up_flag: USHORT) -> Option { @@ -214,7 +406,7 @@ fn button_flags_to_element_state(button_flags: USHORT, down_flag: USHORT, up_fla } } -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, @@ -231,5 +423,265 @@ 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, + // @francesca64 when reviewing - where did you get these values? I've commented them + // out because the HOTAS controller I've been using to test the raw input backend has + // been getting axis hints when it shouldn't, and this seems to be the culprit. + // match unsafe { axis_cap.u.Range().UsageMin } { + // 0x30 => Some(GamepadAxis::LeftStickX), + // 0x31 => Some(GamepadAxis::LeftStickY), + // 0x32 => Some(GamepadAxis::RightStickX), + // 0x33 => Some(GamepadAxis::LeftTrigger), + // 0x34 => Some(GamepadAxis::RightTrigger), + // 0x35 => Some(GamepadAxis::RightStickY), + // 0x39 => Some(GamepadAxis::HatSwitch), + // 0x90 => Some(GamepadAxis::DPadUp), + // 0x91 => Some(GamepadAxis::DPadDown), + // 0x92 => Some(GamepadAxis::DPadRight), + // 0x93 => Some(GamepadAxis::DPadLeft), + // _ => 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 a8f30e22ab..c1acf56790 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -1,5 +1,4 @@ use std::{mem, ptr, slice, io}; -use std::ops::BitAnd; use std::sync::atomic::{AtomicBool, Ordering}; use crate::window::CursorIcon; @@ -9,12 +8,7 @@ use winapi::shared::windef::{HWND, POINT, RECT}; use winapi::um::winbase::lstrlenW; use winapi::um::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 b16dd6f187..8796587b43 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -29,7 +29,6 @@ use crate::platform_impl::platform::{ event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID, REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID}, icon::{self, IconType, WinIcon}, monitor, - raw_input::register_all_mice_and_keyboards_for_raw_input, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, }; @@ -70,10 +69,10 @@ 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)}) + Box::new(move |event| if let Ok(e) = event.map_nonuser_event() {shared_data.runner_shared.send_event(e)}) ); let handler_interface_ptr = &mut (*file_drop_handler.data).interface as LPDROPTARGET; @@ -83,7 +82,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, }; @@ -661,9 +660,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..c499b2e40a --- /dev/null +++ b/src/platform_impl/windows/xinput.rs @@ -0,0 +1,295 @@ +use std::{io, mem}; +use std::sync::{Arc, Weak}; + +use rusty_xinput::*; +use winapi::shared::minwindef::{DWORD, WORD}; +use winapi::um::xinput::*; + +use crate::{ + platform_impl::platform::util, + event::{ + ElementState, + device::{BatteryLevel, GamepadAxis, GamepadButton, GamepadEvent, RumbleError, Side}, + }, +}; + +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..c371990f11 --- /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 77da9cbffb..b0a8c7f5eb 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -21,6 +21,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::(); }