diff --git a/CHANGELOG.md b/CHANGELOG.md index 28ecbf8320..6016e638a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Unreleased +- 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. - On macOS, drop the event callback before exiting. - On Android, implement `Window::request_redraw` - **Breaking:** On Web, remove the `stdweb` backend. diff --git a/Cargo.toml b/Cargo.toml index 88ac1cc66c..bae24cd0c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ features = ["display_link"] [target.'cfg(target_os = "windows")'.dependencies] parking_lot = "0.11" +rusty-xinput = "1.0" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" @@ -64,6 +65,7 @@ features = [ "commctrl", "dwmapi", "errhandlingapi", + "hidpi", "imm", "hidusage", "libloaderapi", @@ -80,6 +82,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/control_flow.rs b/examples/control_flow.rs index 64372a9bc8..732eba3531 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -51,15 +51,11 @@ fn main() { WindowEvent::CloseRequested => { close_requested = true; } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: ElementState::Pressed, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + virtual_keycode: Some(virtual_code), + state: ElementState::Pressed, .. - } => match virtual_code { + }) => match virtual_code { VirtualKeyCode::Key1 => { mode = Mode::Wait; println!("\nmode: {:?}\n", mode); diff --git a/examples/cursor.rs b/examples/cursor.rs index a466e889a8..8aa3c43e78 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -20,14 +20,10 @@ fn main() { match event { Event::WindowEvent { event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Pressed, .. - }, + }), .. } => { println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 90a94764de..2e10ea09d2 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -1,6 +1,6 @@ use simple_logger::SimpleLogger; use winit::{ - event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, + event::{device::MouseEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -22,15 +22,11 @@ fn main() { match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(key), - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), .. - } => { + }) => { use winit::event::VirtualKeyCode::*; match key { Escape => *control_flow = ControlFlow::Exit, @@ -42,14 +38,16 @@ fn main() { WindowEvent::ModifiersChanged(m) => modifiers = m, _ => (), }, - Event::DeviceEvent { event, .. } => match event { - DeviceEvent::MouseMotion { delta } => println!("mouse moved: {:?}", delta), - DeviceEvent::Button { button, state } => match state { - ElementState::Pressed => println!("mouse button {} pressed", button), - ElementState::Released => println!("mouse button {} released", button), + + Event::MouseEvent(_, event) => match event { + MouseEvent::MovedRelative(x, y) => println!("mouse moved: {:?}", (x, y)), + MouseEvent::Button { button, state } => match state { + ElementState::Pressed => println!("mouse button {:?} pressed", button), + ElementState::Released => println!("mouse button {:?} released", button), }, _ => (), }, + _ => (), } }); diff --git a/examples/drag_window.rs b/examples/drag_window.rs index a408c7c722..18c05e9c52 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -42,15 +42,11 @@ fn main() { entered_id = window_id; name_windows(entered_id, switched, &window_1, &window_2) } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::X), - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(VirtualKeyCode::X), .. - } => { + }) => { switched = !switched; name_windows(entered_id, switched, &window_1, &window_2); println!("Switched!") diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 83fbde30db..207c2f28c0 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -37,15 +37,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) => { if window.fullscreen().is_some() { diff --git a/examples/gamepad.rs b/examples/gamepad.rs new file mode 100644 index 0000000000..42023a81f9 --- /dev/null +++ b/examples/gamepad.rs @@ -0,0 +1,52 @@ +use winit::{ + event::{ + device::{GamepadEvent, GamepadHandle}, + Event, WindowEvent, + }, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +fn main() { + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("The world's worst video game") + .build(&event_loop) + .unwrap(); + + println!("enumerating gamepads:"); + for gamepad in GamepadHandle::enumerate(&event_loop) { + println!( + " gamepad={:?}\tport={:?}\tbattery level={:?}", + gamepad, + gamepad.port(), + gamepad.battery_level() + ); + } + + let deadzone = 0.12; + + event_loop.run(move |event, _, control_flow| { + match event { + Event::GamepadEvent(gamepad_handle, event) => { + match event { + // Discard any Axis events that has a corresponding Stick event. + GamepadEvent::Axis { stick: true, .. } => (), + + // Discard any Stick event that falls inside the stick's deadzone. + GamepadEvent::Stick { + x_value, y_value, .. + } if (x_value.powi(2) + y_value.powi(2)).sqrt() < deadzone => (), + + _ => println!("[{:?}] {:#?}", gamepad_handle, event), + } + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } + }); +} diff --git a/examples/gamepad_rumble.rs b/examples/gamepad_rumble.rs new file mode 100644 index 0000000000..8ae6ccc8c9 --- /dev/null +++ b/examples/gamepad_rumble.rs @@ -0,0 +1,60 @@ +use std::time::Instant; +use winit::event_loop::EventLoop; + +#[derive(Debug, Clone)] +enum Rumble { + None, + Left, + Right, +} + +fn main() { + let event_loop = EventLoop::new(); + + // You should generally use `GamepadEvent::Added/Removed` to detect gamepads, as doing that will + // allow you to more easily support gamepad hotswapping. However, we're using `enumerate` here + // because it makes this example more concise. + let gamepads = winit::event::device::GamepadHandle::enumerate(&event_loop).collect::>(); + + let rumble_patterns = &[ + (0.5, Rumble::None), + (2.0, Rumble::Left), + (0.5, Rumble::None), + (2.0, Rumble::Right), + ]; + let mut rumble_iter = rumble_patterns.iter().cloned().cycle(); + + let mut active_pattern = rumble_iter.next().unwrap(); + let mut timeout = active_pattern.0; + let mut timeout_start = Instant::now(); + + event_loop.run(move |_, _, _| { + if timeout <= active_pattern.0 { + let t = (timeout / active_pattern.0) * std::f64::consts::PI; + let intensity = t.sin(); + + for g in &gamepads { + let result = match active_pattern.1 { + Rumble::Left => g.rumble(intensity, 0.0), + Rumble::Right => g.rumble(0.0, intensity), + Rumble::None => Ok(()), + }; + + if let Err(e) = result { + println!("Rumble failed: {:?}", e); + } + } + + timeout = (Instant::now() - timeout_start).as_millis() as f64 / 1000.0; + } else { + active_pattern = rumble_iter.next().unwrap(); + println!( + "Rumbling {:?} for {:?} seconds", + active_pattern.1, active_pattern.0 + ); + + timeout = 0.0; + timeout_start = Instant::now(); + } + }); +} diff --git a/examples/handling_close.rs b/examples/handling_close.rs index 8334c1773f..e127e97aea 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -43,15 +43,11 @@ fn main() { // closing the window. How to close the window is detailed in the handler for // the Y key. } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: Released, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + virtual_keycode: Some(virtual_code), + state: Released, .. - } => { + }) => { match virtual_code { Y => { if close_requested { diff --git a/examples/minimize.rs b/examples/minimize.rs index eb02a752c9..6fa483d11f 100644 --- a/examples/minimize.rs +++ b/examples/minimize.rs @@ -25,7 +25,7 @@ fn main() { // Keyboard input event to handle minimize via a hotkey Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, + event: WindowEvent::KeyboardInput(input), window_id, } => { if window_id == window.id() { diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index e61b64af8f..96649dca88 100644 --- a/examples/mouse_wheel.rs +++ b/examples/mouse_wheel.rs @@ -1,6 +1,6 @@ use simple_logger::SimpleLogger; use winit::{ - event::{DeviceEvent, Event, WindowEvent}, + event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -20,10 +20,7 @@ fn main() { match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - _ => (), - }, - Event::DeviceEvent { event, .. } => match event { - DeviceEvent::MouseWheel { delta } => match delta { + WindowEvent::MouseWheel { delta, .. } => match delta { winit::event::MouseScrollDelta::LineDelta(x, y) => { println!("mouse wheel Line Delta: ({},{})", x, y); let pixels_per_line = 120.0; diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 68cdb60b78..6b6f3274af 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -49,17 +49,12 @@ fn main() { ); } } - #[allow(deprecated)] - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(key), - modifiers, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + modifiers, .. - } => { + }) => { window.set_title(&format!("{:?}", key)); let state = !modifiers.shift(); use VirtualKeyCode::*; @@ -154,15 +149,11 @@ fn main() { Event::WindowEvent { event, window_id } => match event { WindowEvent::CloseRequested | WindowEvent::Destroyed - | WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, + | WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(VirtualKeyCode::Escape), .. - } => { + }) => { window_senders.remove(&window_id); } _ => { diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index ec97eee097..a9d8fcb57e 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -33,14 +33,10 @@ fn main() { *control_flow = ControlFlow::Exit; } } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - .. - }, + WindowEvent::KeyboardInput(KeyboardInput { + state: ElementState::Pressed, .. - } => { + }) => { let window = Window::new(&event_loop).unwrap(); windows.insert(window.id(), window); } diff --git a/examples/resizable.rs b/examples/resizable.rs index 17892d8741..ab10c15722 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -25,15 +25,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/examples/window_debug.rs b/examples/window_debug.rs index 577ad5cd73..d81e6de8ad 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -3,7 +3,9 @@ use simple_logger::SimpleLogger; use winit::{ dpi::{LogicalSize, PhysicalSize}, - event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ + device::KeyboardEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent, + }, event_loop::{ControlFlow, EventLoop}, window::{Fullscreen, WindowBuilder}, }; @@ -34,15 +36,14 @@ fn main() { *control_flow = ControlFlow::Wait; match event { - Event::DeviceEvent { - event: - DeviceEvent::Key(KeyboardInput { - virtual_keycode: Some(key), - state: ElementState::Pressed, - .. - }), - .. - } => match key { + Event::KeyboardEvent( + _, + KeyboardEvent::Input(KeyboardInput { + virtual_keycode: Some(key), + state: ElementState::Pressed, + .. + }), + ) => match key { VirtualKeyCode::M => { if minimized { minimized = !minimized; @@ -58,7 +59,7 @@ fn main() { _ => (), }, Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, + event: WindowEvent::KeyboardInput(input), .. } => match input { KeyboardInput { diff --git a/src/event.rs b/src/event.rs index 4de6c36d5d..721ec5b095 100644 --- a/src/event.rs +++ b/src/event.rs @@ -38,10 +38,12 @@ use std::path::PathBuf; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - platform_impl, - window::{Theme, WindowId}, + window::Theme, + window::WindowId, }; +pub mod device; + /// Describes a generic event. /// /// See the module-level docs for more information on the event loop manages each event. @@ -61,11 +63,13 @@ pub enum Event<'a, T: 'static> { event: WindowEvent<'a>, }, - /// 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`](crate::event_loop::EventLoopProxy::send_event) UserEvent(T), @@ -127,10 +131,10 @@ impl Clone for Event<'static, T> { event: event.clone(), }, UserEvent(event) => UserEvent(event.clone()), - DeviceEvent { device_id, event } => DeviceEvent { - device_id: *device_id, - event: event.clone(), - }, + MouseEvent(id, event) => MouseEvent(id.clone(), event.clone()), + KeyboardEvent(id, event) => KeyboardEvent(id.clone(), event.clone()), + HidEvent(id, event) => HidEvent(id.clone(), event.clone()), + GamepadEvent(id, event) => GamepadEvent(id.clone(), event.clone()), NewEvents(cause) => NewEvents(cause.clone()), MainEventsCleared => MainEventsCleared, RedrawRequested(wid) => RedrawRequested(*wid), @@ -148,7 +152,10 @@ impl<'a, T> Event<'a, T> { 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)), MainEventsCleared => Ok(MainEventsCleared), RedrawRequested(wid) => Ok(RedrawRequested(wid)), @@ -168,7 +175,10 @@ impl<'a, T> Event<'a, T> { .to_static() .map(|event| WindowEvent { window_id, event }), UserEvent(event) => Some(UserEvent(event)), - DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }), + MouseEvent(id, event) => Some(MouseEvent(id, event)), + KeyboardEvent(id, event) => Some(KeyboardEvent(id, event)), + HidEvent(id, event) => Some(HidEvent(id, event)), + GamepadEvent(id, event) => Some(GamepadEvent(id, event)), NewEvents(cause) => Some(NewEvents(cause)), MainEventsCleared => Some(MainEventsCleared), RedrawRequested(wid) => Some(RedrawRequested(wid)), @@ -180,8 +190,8 @@ impl<'a, T> Event<'a, T> { } } -/// Describes the reason the event loop is resuming. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// 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 /// moment the timeout was requested and the requested resume time. The actual resume time is @@ -248,20 +258,7 @@ pub enum WindowEvent<'a> { Focused(bool), /// An event from the keyboard has been received. - KeyboardInput { - device_id: DeviceId, - input: KeyboardInput, - /// If `true`, the event was generated synthetically by winit - /// in one of the following circumstances: - /// - /// * Synthetic key press events are generated for all keys pressed - /// when a window gains focus. Likewise, synthetic key release events - /// are generated for all keys pressed when a window goes out of focus. - /// ***Currently, this is only functional on X11 and Windows*** - /// - /// Otherwise, this value is always `false`. - is_synthetic: bool, - }, + KeyboardInput(KeyboardInput), /// The keyboard modifiers have changed. /// @@ -272,8 +269,6 @@ pub enum WindowEvent<'a> { /// 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. @@ -283,14 +278,13 @@ pub enum WindowEvent<'a> { }, /// 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, #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] @@ -299,7 +293,6 @@ pub enum WindowEvent<'a> { /// An mouse button press has been received. MouseInput { - device_id: DeviceId, state: ElementState, button: MouseButton, #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] @@ -311,18 +304,7 @@ pub enum WindowEvent<'a> { /// 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 }, /// Touch event has been received Touch(Touch), @@ -367,75 +349,42 @@ impl Clone for WindowEvent<'static> { HoveredFileCancelled => HoveredFileCancelled, ReceivedCharacter(c) => ReceivedCharacter(*c), Focused(f) => Focused(*f), - KeyboardInput { - device_id, - input, - is_synthetic, - } => KeyboardInput { - device_id: *device_id, - input: *input, - is_synthetic: *is_synthetic, - }, - + KeyboardInput(keyboard_input) => KeyboardInput(keyboard_input.clone()), ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()), #[allow(deprecated)] CursorMoved { - device_id, position, modifiers, } => CursorMoved { - device_id: *device_id, position: *position, modifiers: *modifiers, }, - CursorEntered { device_id } => CursorEntered { - device_id: *device_id, - }, - CursorLeft { device_id } => CursorLeft { - device_id: *device_id, - }, + CursorEntered => CursorEntered, + CursorLeft => CursorLeft, #[allow(deprecated)] MouseWheel { - device_id, delta, phase, modifiers, } => MouseWheel { - device_id: *device_id, delta: *delta, phase: *phase, modifiers: *modifiers, }, #[allow(deprecated)] MouseInput { - device_id, state, button, modifiers, } => MouseInput { - device_id: *device_id, state: *state, button: *button, modifiers: *modifiers, }, - TouchpadPressure { - device_id, - pressure, - stage, - } => TouchpadPressure { - device_id: *device_id, + TouchpadPressure { pressure, stage } => TouchpadPressure { pressure: *pressure, stage: *stage, }, - AxisMotion { - device_id, - axis, - value, - } => AxisMotion { - device_id: *device_id, - axis: *axis, - value: *value, - }, Touch(touch) => Touch(*touch), ThemeChanged(theme) => ThemeChanged(theme.clone()), ScaleFactorChanged { .. } => { @@ -458,70 +407,39 @@ impl<'a> WindowEvent<'a> { HoveredFileCancelled => Some(HoveredFileCancelled), ReceivedCharacter(c) => Some(ReceivedCharacter(c)), Focused(focused) => Some(Focused(focused)), - KeyboardInput { - device_id, - input, - is_synthetic, - } => Some(KeyboardInput { - device_id, - input, - is_synthetic, - }), + KeyboardInput(keyboard_input) => Some(KeyboardInput(keyboard_input)), ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), #[allow(deprecated)] CursorMoved { - device_id, position, modifiers, } => Some(CursorMoved { - device_id, position, modifiers, }), - CursorEntered { device_id } => Some(CursorEntered { device_id }), - CursorLeft { device_id } => Some(CursorLeft { device_id }), + CursorEntered {} => Some(CursorEntered {}), + CursorLeft {} => Some(CursorLeft {}), #[allow(deprecated)] MouseWheel { - device_id, delta, phase, modifiers, } => Some(MouseWheel { - device_id, delta, phase, modifiers, }), #[allow(deprecated)] MouseInput { - device_id, state, button, modifiers, } => Some(MouseInput { - device_id, state, button, modifiers, }), - TouchpadPressure { - device_id, - pressure, - stage, - } => Some(TouchpadPressure { - device_id, - pressure, - stage, - }), - AxisMotion { - device_id, - axis, - value, - } => Some(AxisMotion { - device_id, - axis, - value, - }), + TouchpadPressure { pressure, stage } => Some(TouchpadPressure { pressure, stage }), Touch(touch) => Some(Touch(touch)), ThemeChanged(theme) => Some(ThemeChanged(theme)), ScaleFactorChanged { .. } => None, @@ -529,74 +447,7 @@ impl<'a> WindowEvent<'a> { } } -/// 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 { @@ -623,13 +474,16 @@ pub struct KeyboardInput { pub modifiers: ModifiersState, } -/// Describes touch-screen input state. +/// Touch input state. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TouchPhase { Started, Moved, Ended, + /// The touch has been cancelled by the OS. + /// + /// This can occur in a variety of situations, such as the window losing focus. Cancelled, } @@ -651,7 +505,6 @@ pub enum TouchPhase { /// device against their face. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Touch { - pub device_id: DeviceId, pub phase: TouchPhase, pub location: PhysicalPosition, /// Describes how hard the screen was pressed. May be `None` if the platform @@ -662,6 +515,8 @@ pub struct Touch { /// - Only available on **iOS** 9.0+ and **Windows** 8+. pub force: Option, /// Unique identifier of a finger. + /// + /// This may get reused by the system after the touch ends. pub id: u64, } @@ -730,16 +585,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, @@ -748,7 +603,7 @@ pub enum MouseButton { Other(u16), } -/// 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 { @@ -767,7 +622,7 @@ pub enum MouseScrollDelta { PixelDelta(PhysicalPosition), } -/// 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))] diff --git a/src/event/device.rs b/src/event/device.rs new file mode 100644 index 0000000000..1b9352f3c3 --- /dev/null +++ b/src/event/device.rs @@ -0,0 +1,363 @@ +//! Raw hardware events that are not associated with any particular window. +//! +//! Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person +//! game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because +//! window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs +//! may not match. +//! +//! All attached devices are guaranteed to emit an `Added` event upon the initialization of the event loop. +//! +//! Note that device events are always delivered regardless of window focus. + +use crate::{ + dpi::PhysicalPosition, + event::{AxisId, ButtonId, ElementState, KeyboardInput, ModifiersState, MouseButton}, + event_loop::EventLoop, + platform_impl, +}; +use std::{fmt, io}; + +/// A hint suggesting the type of button that was pressed. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GamepadButton { + Start, + Select, + + /// The north face button. + /// + /// * Nintendo: X + /// * Playstation: Triangle + /// * XBox: Y + North, + /// The south face button. + /// + /// * Nintendo: B + /// * Playstation: X + /// * XBox: A + South, + /// The east face button. + /// + /// * Nintendo: A + /// * Playstation: Circle + /// * XBox: B + East, + /// The west face button. + /// + /// * Nintendo: Y + /// * Playstation: Square + /// * XBox: X + West, + + LeftStick, + RightStick, + + LeftTrigger, + RightTrigger, + + LeftShoulder, + RightShoulder, + + DPadUp, + DPadDown, + DPadLeft, + DPadRight, +} + +/// A hint suggesting the type of axis that moved. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GamepadAxis { + LeftStickX, + LeftStickY, + + RightStickX, + RightStickY, + + LeftTrigger, + RightTrigger, +} + +/// A given joystick's side on the gamepad. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Side { + Left, + Right, +} + +/// Raw mouse events. +/// +/// See the module-level docs for more information. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum MouseEvent { + /// A mouse device has been added. + Added, + /// A mouse device has been removed. + Removed, + /// A mouse button has been pressed or released. + Button { + state: ElementState, + button: MouseButton, + }, + /// Relative change in physical position of a pointing device. + /// + /// This represents raw, unfiltered physical motion, NOT the position of the mouse. Accordingly, + /// the values provided here are the change in position of the mouse since the previous + /// `MovedRelative` event. + MovedRelative(f64, f64), + /// Change in absolute position of a pointing device. + /// + /// The `PhysicalPosition` value is the new position of the cursor relative to the desktop. This + /// generally doesn't get output by standard mouse devices, but can get output from tablet devices. + MovedAbsolute(PhysicalPosition), + /// Change in rotation of mouse wheel. + Wheel(f64, f64), +} + +/// Raw keyboard events. +/// +/// See the module-level docs for more information. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum KeyboardEvent { + /// A keyboard device has been added. + Added, + /// A keyboard device has been removed. + Removed, + /// A key has been pressed or released. + Input(KeyboardInput), + ModifiersChanged(ModifiersState), +} + +/// 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.axis` 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 c3101d8dc3..4108a58ac5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,9 +24,10 @@ //! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can //! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the //! window or a key getting pressed while the window is focused. Devices can generate -//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window. +//! device events which contain unfiltered data that isn't specific to a certain window. Examples of such +//! events are [`GamepadEvent`], [`KeyboardEvent`], [`MouseEvent`] and [`HidEvent`]. //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a -//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired. +//! device event. You can also create and handle your own custom [`UserEvent`]s, if desired. //! //! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and @@ -123,7 +124,10 @@ //! [window_id_fn]: window::Window::id //! [`Event`]: event::Event //! [`WindowEvent`]: event::WindowEvent -//! [`DeviceEvent`]: event::DeviceEvent +//! [`GamepadEvent`]: event::device::GamepadEvent +//! [`KeyboardEvent`]: event::device::KeyboardEvent +//! [`MouseEvent`]: event::device::MouseEvent +//! [`HidEvent`]: event::device::HidEvent //! [`UserEvent`]: event::Event::UserEvent //! [`LoopDestroyed`]: event::Event::LoopDestroyed //! [`platform`]: platform @@ -155,6 +159,7 @@ pub mod event_loop; mod icon; pub mod monitor; mod platform_impl; +mod util; pub mod window; pub mod platform; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 83a33c6373..a52154366c 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -9,7 +9,7 @@ use winapi::shared::windef::{HMENU, HWND}; use crate::{ dpi::PhysicalSize, - event::DeviceId, + event::device::{GamepadHandle, KeyboardId, MouseId}, event_loop::EventLoop, monitor::MonitorHandle, platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon}, @@ -243,19 +243,51 @@ 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 _ + } } /// Additional methods on `Icon` that are specific to Windows. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index a213fa2625..4a952791aa 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -5,7 +5,8 @@ mod runner; use parking_lot::Mutex; use std::{ cell::Cell, - collections::VecDeque, + cell::RefCell, + collections::{HashMap, VecDeque}, marker::PhantomData, mem, panic, ptr, rc::Rc, @@ -33,7 +34,10 @@ use winapi::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, + event::{ + device::{GamepadEvent, HidEvent, KeyboardEvent, MouseEvent}, + Event, Force, KeyboardInput, MouseButton, Touch, TouchPhase, WindowEvent, + }, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ @@ -41,10 +45,12 @@ use crate::{ dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, + gamepad::Gamepad, monitor::{self, MonitorHandle}, - raw_input, util, + raw_input::{self, get_raw_input_data, get_raw_mouse_button_state, RawInputData}, + util, window_state::{CursorFlags, WindowFlags, WindowState}, - wrap_device_id, WindowId, DEVICE_ID, + GamepadHandle, HidId, KeyboardId, MouseId, WindowId, }, window::{Fullscreen, WindowId as RootWindowId}, }; @@ -83,9 +89,17 @@ lazy_static! { get_function!("user32.dll", GetPointerPenInfo); } +#[derive(Debug)] +pub(crate) enum DeviceId { + Mouse(MouseId), + Keyboard(KeyboardId), + Hid(HidId), + Gamepad(GamepadHandle, Gamepad), +} + pub(crate) struct SubclassInput { pub window_state: Arc>, - pub event_loop_runner: EventLoopRunnerShared, + pub shared_data: Rc>, pub file_drop_handler: Option, pub subclass_removed: Cell, pub recurse_depth: Cell, @@ -93,18 +107,18 @@ pub(crate) struct SubclassInput { impl SubclassInput { unsafe fn send_event(&self, event: Event<'_, T>) { - 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, } impl ThreadMsgTargetSubclassInput { unsafe fn send_event(&self, event: Event<'_, T>) { - self.event_loop_runner.send_event(event); + self.shared_data.runner_shared.send_event(event); } } @@ -116,7 +130,7 @@ pub struct EventLoop { pub struct EventLoopWindowTarget { thread_id: DWORD, thread_msg_target: HWND, - pub(crate) runner_shared: EventLoopRunnerShared, + pub(crate) shared_data: Rc>, } macro_rules! main_thread_check { @@ -155,17 +169,19 @@ impl EventLoop { pub fn new_dpi_unaware_any_thread() -> EventLoop { let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; - let thread_msg_target = create_event_target_window(); + let thread_msg_target = create_event_target_window::(); let send_thread_msg_target = thread_msg_target as usize; thread::spawn(move || wait_thread(thread_id, send_thread_msg_target as HWND)); let wait_thread_id = get_wait_thread_id(); - let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + let shared_data = Rc::new(SubclassSharedData { + runner_shared: Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)), + active_device_ids: RefCell::new(HashMap::default()), + }); let thread_msg_sender = - subclass_event_target_window(thread_msg_target, runner_shared.clone()); - raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target); + subclass_event_target_window(thread_msg_target, shared_data.clone()); EventLoop { thread_msg_sender, @@ -173,7 +189,7 @@ impl EventLoop { p: EventLoopWindowTarget { thread_id, thread_msg_target, - runner_shared, + shared_data, }, _marker: PhantomData, }, @@ -201,13 +217,14 @@ impl EventLoop { unsafe { self.window_target .p + .shared_data .runner_shared .set_event_handler(move |event, control_flow| { event_handler(event, event_loop_windows_ref, control_flow) }); } - let runner = &self.window_target.p.runner_shared; + let runner = &self.window_target.p.shared_data.runner_shared; unsafe { let mut msg = mem::zeroed(); @@ -243,6 +260,70 @@ impl EventLoop { event_send: self.thread_msg_sender.clone(), } } + + fn devices( + &self, + f: impl FnMut(&DeviceId) -> Option, + ) -> impl '_ + Iterator { + // Flush WM_INPUT and WM_INPUT_DEVICE_CHANGE events so that the active_device_ids list is + // accurate. This is essential to make this function work if called before calling `run` or + // `run_return`. + unsafe { + let mut msg = mem::zeroed(); + 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 { @@ -306,6 +387,11 @@ fn main_thread_id() -> DWORD { unsafe { MAIN_THREAD_ID } } +pub(crate) struct SubclassSharedData { + pub runner_shared: EventLoopRunnerShared, + pub active_device_ids: RefCell>, +} + fn get_wait_thread_id() -> DWORD { unsafe { let mut msg = mem::zeroed(); @@ -593,7 +679,7 @@ lazy_static! { }; } -fn create_event_target_window() -> HWND { +fn create_event_target_window() -> HWND { unsafe { let window = winuser::CreateWindowExW( winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED, @@ -623,13 +709,13 @@ fn create_event_target_window() -> HWND { fn subclass_event_target_window( window: HWND, - event_loop_runner: EventLoopRunnerShared, + shared_data: Rc>, ) -> Sender { unsafe { 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)); @@ -641,6 +727,9 @@ fn subclass_event_target_window( ); assert_eq!(subclass_result, 1); + // Set up raw input + raw_input::register_for_raw_input(window); + tx } } @@ -677,7 +766,10 @@ unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowStat const WINDOW_SUBCLASS_ID: UINT_PTR = 0; const THREAD_EVENT_TARGET_SUBCLASS_ID: UINT_PTR = 1; pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) { - subclass_input.event_loop_runner.register_window(window); + subclass_input + .shared_data + .runner_shared + .register_window(window); let input_ptr = Box::into_raw(Box::new(subclass_input)); let subclass_result = unsafe { commctrl::SetWindowSubclass( @@ -839,7 +931,7 @@ unsafe fn public_window_callback_inner( subclass_input: &SubclassInput, ) -> LRESULT { winuser::RedrawWindow( - subclass_input.event_loop_runner.thread_msg_target(), + subclass_input.shared_data.runner_shared.thread_msg_target(), ptr::null(), ptr::null_mut(), winuser::RDW_INTERNALPAINT, @@ -892,7 +984,10 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: Destroyed, }); - subclass_input.event_loop_runner.remove_window(window); + subclass_input + .shared_data + .runner_shared + .remove_window(window); 0 } @@ -903,7 +998,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_PAINT => { - if subclass_input.event_loop_runner.should_buffer() { + if subclass_input.shared_data.runner_shared.should_buffer() { // this branch can happen in response to `UpdateWindow`, if win32 decides to // redraw the window outside the normal flow of the event loop. winuser::RedrawWindow( @@ -914,11 +1009,14 @@ unsafe fn public_window_callback_inner( ); } else { let managing_redraw = - flush_paint_messages(Some(window), &subclass_input.event_loop_runner); + flush_paint_messages(Some(window), &subclass_input.shared_data.runner_shared); subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); if managing_redraw { - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + subclass_input + .shared_data + .runner_shared + .redraw_events_cleared(); + process_control_flow(&subclass_input.shared_data.runner_shared); } } @@ -1088,9 +1186,7 @@ unsafe fn public_window_callback_inner( 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. @@ -1120,7 +1216,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { - device_id: DEVICE_ID, position, modifiers: event::get_key_mods(), }, @@ -1141,9 +1236,7 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: CursorLeft { - device_id: DEVICE_ID, - }, + event: CursorLeft, }); 0 @@ -1161,7 +1254,6 @@ unsafe fn public_window_callback_inner( 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(), @@ -1183,7 +1275,6 @@ unsafe fn public_window_callback_inner( 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(), @@ -1204,16 +1295,12 @@ unsafe fn public_window_callback_inner( #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Pressed, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - is_synthetic: false, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + state: Pressed, + scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + }), }); // Windows doesn't emit a delete character by default, but in order to make it // consistent with the other platforms we'll emit a delete character here. @@ -1236,23 +1323,19 @@ unsafe fn public_window_callback_inner( #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Released, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - is_synthetic: false, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + state: Released, + scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + }), }); } 0 } winuser::WM_LBUTTONDOWN => { - use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; capture_mouse(window, &mut *subclass_input.window_state.lock()); @@ -1261,9 +1344,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Left, + button: MouseButton::Left, modifiers: event::get_key_mods(), }, }); @@ -1271,9 +1353,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_LBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Left, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; release_mouse(subclass_input.window_state.lock()); @@ -1282,9 +1362,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Left, + button: MouseButton::Left, modifiers: event::get_key_mods(), }, }); @@ -1292,9 +1371,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_RBUTTONDOWN => { - use crate::event::{ - ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; capture_mouse(window, &mut *subclass_input.window_state.lock()); @@ -1303,9 +1380,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Right, + button: MouseButton::Right, modifiers: event::get_key_mods(), }, }); @@ -1313,9 +1389,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_RBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Right, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; release_mouse(subclass_input.window_state.lock()); @@ -1324,9 +1398,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Right, + button: MouseButton::Right, modifiers: event::get_key_mods(), }, }); @@ -1334,9 +1407,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_MBUTTONDOWN => { - use crate::event::{ - ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; capture_mouse(window, &mut *subclass_input.window_state.lock()); @@ -1345,9 +1416,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Middle, + button: MouseButton::Middle, modifiers: event::get_key_mods(), }, }); @@ -1355,9 +1425,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_MBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; release_mouse(subclass_input.window_state.lock()); @@ -1366,9 +1434,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Middle, + button: MouseButton::Middle, modifiers: event::get_key_mods(), }, }); @@ -1376,9 +1443,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_XBUTTONDOWN => { - use crate::event::{ - ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Pressed, WindowEvent::MouseInput}; let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); capture_mouse(window, &mut *subclass_input.window_state.lock()); @@ -1388,9 +1453,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, - button: Other(xbutton), + button: MouseButton::Other(xbutton as u16), modifiers: event::get_key_mods(), }, }); @@ -1398,9 +1462,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_XBUTTONUP => { - use crate::event::{ - ElementState::Released, MouseButton::Other, WindowEvent::MouseInput, - }; + use crate::event::{ElementState::Released, WindowEvent::MouseInput}; let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); release_mouse(subclass_input.window_state.lock()); @@ -1410,9 +1472,8 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, - button: Other(xbutton), + button: MouseButton::Other(xbutton as u16), modifiers: event::get_key_mods(), }, }); @@ -1470,7 +1531,6 @@ unsafe fn public_window_callback_inner( location, force: None, // WM_TOUCH doesn't support pressure information id: input.dwID as u64, - device_id: DEVICE_ID, }), }); } @@ -1609,7 +1669,6 @@ unsafe fn public_window_callback_inner( location, force, id: pointer_info.pointerId as u64, - device_id: DEVICE_ID, }), }); } @@ -1631,16 +1690,12 @@ unsafe fn public_window_callback_inner( #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }), }) } @@ -1666,16 +1721,12 @@ unsafe fn public_window_callback_inner( #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, + event: WindowEvent::KeyboardInput(KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }), }) } @@ -2004,7 +2055,8 @@ unsafe fn public_window_callback_inner( }; subclass_input - .event_loop_runner + .shared_data + .runner_shared .catch_unwind(callback) .unwrap_or(-1) } @@ -2046,8 +2098,8 @@ unsafe extern "system" fn thread_event_target_callback( // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw // events, `handling_events` will return false and we won't emit a second // `RedrawEventsCleared` event. - if subclass_input.event_loop_runner.handling_events() { - if subclass_input.event_loop_runner.should_buffer() { + if subclass_input.shared_data.runner_shared.handling_events() { + if subclass_input.shared_data.runner_shared.should_buffer() { // This branch can be triggered when a nested win32 event loop is triggered // inside of the `event_handler` callback. winuser::RedrawWindow( @@ -2061,10 +2113,13 @@ unsafe extern "system" fn thread_event_target_callback( // doesn't call WM_PAINT for the thread event target (i.e. this window). assert!(flush_paint_messages( None, - &subclass_input.event_loop_runner + &subclass_input.shared_data.runner_shared )); - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + subclass_input + .shared_data + .runner_shared + .redraw_events_cleared(); + process_control_flow(&subclass_input.shared_data.runner_shared); } } @@ -2073,122 +2128,230 @@ unsafe extern "system" fn thread_event_target_callback( } winuser::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam as _ { - winuser::GIDC_ARRIVAL => DeviceEvent::Added, - winuser::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; - - subclass_input.send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam as _), - event, - }); - - 0 - } + use super::raw_input::RawDeviceInfo; - winuser::WM_INPUT => { - use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, - ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, - }; + let handle = lparam as HANDLE; - if let Some(data) = raw_input::get_raw_input_data(lparam as _) { - let device_id = wrap_device_id(data.header.hDevice as _); + 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<'_, T>; - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); + 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); + } + }, + } - if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; + 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!(), + } - if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } + 0 + } - if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); - } + 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; + + 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::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) }, - }); + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::MovedRelative(x, y), + )); } } - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { + if util::has_flag(raw_mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { + // TODO: HOW IS RAW WHEEL DELTA HANDLED ON OTHER PLATFORMS? let delta = - mouse.usButtonData as SHORT as f32 / winuser::WHEEL_DELTA as f32; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta), - }, - }); + raw_mouse.usButtonData as SHORT as f64 / winuser::WHEEL_DELTA as f64; + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Wheel(0.0, delta), + )); + } + // 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 as f64 / winuser::WHEEL_DELTA as f64; + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Wheel(delta as f64, 0.0), + )); } - let button_state = raw_input::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 }, - }); - } + 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 u16 - 2), + }, + }, + )); } - } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); + } + Some(RawInputData::Keyboard { + device_handle, + raw_keyboard, + }) => { + let keyboard_id = KeyboardId(device_handle).into(); - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; + 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 = 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 _); - + 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(keyboard.VKey as _, scancode, extended) + handle_extended_keys(raw_keyboard.VKey as _, scancode, extended) { let virtual_keycode = vkey_to_winit_vkey(vkey); #[allow(deprecated)] - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { + 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)); @@ -2202,7 +2365,7 @@ unsafe extern "system" fn thread_event_target_callback( } _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { winuser::PostThreadMessageW( - subclass_input.event_loop_runner.wait_thread_id(), + subclass_input.shared_data.runner_shared.wait_thread_id(), *CANCEL_WAIT_UNTIL_MSG_ID, 0, 0, @@ -2211,7 +2374,7 @@ unsafe extern "system" fn thread_event_target_callback( // if the control_flow is WaitUntil, make sure the given moment has actually passed // before emitting NewEvents if let ControlFlow::WaitUntil(wait_until) = - subclass_input.event_loop_runner.control_flow() + subclass_input.shared_data.runner_shared.control_flow() { let mut msg = mem::zeroed(); while Instant::now() < wait_until { @@ -2238,14 +2401,15 @@ unsafe extern "system" fn thread_event_target_callback( } } } - subclass_input.event_loop_runner.poll(); + subclass_input.shared_data.runner_shared.poll(); 0 } _ => commctrl::DefSubclassProc(window, msg, wparam, lparam), }; let result = subclass_input - .event_loop_runner + .shared_data + .runner_shared .catch_unwind(callback) .unwrap_or(-1); if subclass_removed { diff --git a/src/platform_impl/windows/gamepad.rs b/src/platform_impl/windows/gamepad.rs new file mode 100644 index 0000000000..4867105099 --- /dev/null +++ b/src/platform_impl/windows/gamepad.rs @@ -0,0 +1,89 @@ +use std::sync::Weak; + +use winapi::um::winnt::HANDLE; + +use crate::{ + event::device::{BatteryLevel, GamepadEvent, RumbleError}, + platform_impl::platform::{ + raw_input::{get_raw_input_device_name, RawGamepad}, + xinput::{self, XInputGamepad, XInputGamepadShared}, + }, +}; + +#[derive(Debug)] +pub enum GamepadType { + Raw(RawGamepad), + XInput(XInputGamepad), +} + +#[derive(Clone)] +pub enum GamepadShared { + Raw(()), + XInput(Weak), + Dummy, +} + +#[derive(Debug)] +pub struct Gamepad { + handle: HANDLE, + backend: GamepadType, +} + +impl Gamepad { + pub fn new(handle: HANDLE) -> Option { + // TODO: Verify that this is an HID device + let name = get_raw_input_device_name(handle)?; + xinput::id_from_name(&name) + .and_then(XInputGamepad::new) + .map(GamepadType::XInput) + .or_else(|| RawGamepad::new(handle).map(GamepadType::Raw)) + .map(|backend| Gamepad { handle, backend }) + } + + pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + match self.backend { + GamepadType::Raw(ref mut gamepad) => gamepad.update_state(raw_input_report), + GamepadType::XInput(ref mut gamepad) => gamepad.update_state(), + } + } + + pub fn get_gamepad_events(&self) -> Vec { + match self.backend { + GamepadType::Raw(ref gamepad) => gamepad.get_gamepad_events(), + GamepadType::XInput(ref gamepad) => gamepad.get_gamepad_events(), + } + } + + pub fn shared_data(&self) -> GamepadShared { + match self.backend { + GamepadType::Raw(_) => GamepadShared::Raw(()), + GamepadType::XInput(ref gamepad) => GamepadShared::XInput(gamepad.shared_data()), + } + } +} + +impl GamepadShared { + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => Ok(()), + GamepadShared::XInput(ref data) => data + .upgrade() + .map(|r| r.rumble(left_speed, right_speed)) + .unwrap_or(Err(RumbleError::DeviceNotConnected)), + } + } + + pub fn port(&self) -> Option { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => None, + GamepadShared::XInput(ref data) => data.upgrade().map(|r| r.port()), + } + } + + pub fn battery_level(&self) -> Option { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => None, + GamepadShared::XInput(ref data) => data.upgrade().and_then(|r| r.battery_level()), + } + } +} diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 07629e6dfb..93872892c8 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -1,9 +1,20 @@ #![cfg(target_os = "windows")] -use winapi::{self, shared::windef::HMENU, shared::windef::HWND}; +use std::{ + cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, + fmt, + hash::{Hash, Hasher}, + ptr, +}; +use winapi::{ + self, + shared::windef::{HMENU, HWND}, + um::winnt::HANDLE, +}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, + gamepad::GamepadShared, icon::WinIcon, monitor::{MonitorHandle, VideoMode}, window::Window, @@ -11,7 +22,7 @@ pub use self::{ pub use self::icon::WinIcon as PlatformIcon; -use crate::event::DeviceId as RootDeviceId; +use crate::event::device; use crate::icon::Icon; use crate::window::Theme; @@ -55,43 +66,149 @@ 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() + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.gamepads() + } +} + +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) + } +} - WindowId(null_mut()) +impl Hash for GamepadHandle { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.handle.hash(state); } } @@ -102,8 +219,10 @@ mod dpi; mod drop_handler; mod event; mod event_loop; +mod gamepad; mod icon; mod monitor; mod raw_input; mod window; mod window_state; +mod xinput; diff --git a/src/platform_impl/windows/raw_input.rs b/src/platform_impl/windows/raw_input.rs index 73b136a82f..e62bec897c 100644 --- a/src/platform_impl/windows/raw_input.rs +++ b/src/platform_impl/windows/raw_input.rs @@ -1,27 +1,43 @@ use std::{ + cmp::max, + fmt, mem::{self, size_of}, - ptr, + ptr, slice, }; use winapi::{ ctypes::wchar_t, shared::{ - hidusage::{HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC}, - minwindef::{TRUE, UINT, USHORT}, + hidpi::{ + HidP_GetButtonCaps, HidP_GetCaps, HidP_GetScaledUsageValue, HidP_GetUsageValue, + HidP_GetUsagesEx, HidP_GetValueCaps, HidP_Input, HIDP_STATUS_SUCCESS, HIDP_VALUE_CAPS, + PHIDP_PREPARSED_DATA, + }, + hidusage::{ + HID_USAGE_GENERIC_GAMEPAD, HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_GENERIC_KEYBOARD, + HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC, + }, + minwindef::{INT, TRUE, UINT, USHORT}, windef::HWND, }, um::{ - winnt::HANDLE, + winnt::{HANDLE, PCHAR}, winuser::{ self, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER, - RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, - RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, - RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, + RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RIDI_PREPARSEDDATA, + RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, + RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }, }, }; -use crate::{event::ElementState, platform_impl::platform::util}; +use crate::{ + event::{ + device::{GamepadAxis, GamepadEvent}, + ElementState, + }, + platform_impl::platform::util, +}; #[allow(dead_code)] pub fn get_raw_input_device_list() -> Option> { @@ -52,7 +68,6 @@ pub fn get_raw_input_device_list() -> Option> { Some(buffer) } -#[allow(dead_code)] pub enum RawDeviceInfo { Mouse(RID_DEVICE_INFO_MOUSE), Keyboard(RID_DEVICE_INFO_KEYBOARD), @@ -72,28 +87,27 @@ impl From for RawDeviceInfo { } } -#[allow(dead_code)] pub fn get_raw_input_device_info(handle: HANDLE) -> Option { let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() }; let info_size = size_of::() as UINT; info.cbSize = info_size; - let mut minimum_size = 0; + let mut data_size = info_size; let status = unsafe { winuser::GetRawInputDeviceInfoW( handle, RIDI_DEVICEINFO, &mut info as *mut _ as _, - &mut minimum_size, + &mut data_size, ) - }; + } as INT; - if status == UINT::max_value() || status == 0 { + if status <= 0 { return None; } - debug_assert_eq!(info_size, status); + debug_assert_eq!(info_size, status as _); Some(info.into()) } @@ -130,6 +144,43 @@ pub fn get_raw_input_device_name(handle: HANDLE) -> Option { Some(util::wchar_to_string(&name)) } +pub fn get_raw_input_pre_parse_info(handle: HANDLE) -> Option> { + let mut minimum_size = 0; + let status = unsafe { + winuser::GetRawInputDeviceInfoW( + handle, + RIDI_PREPARSEDDATA, + ptr::null_mut(), + &mut minimum_size, + ) + }; + + if status != 0 { + return None; + } + + let mut buf: Vec = Vec::with_capacity(minimum_size as _); + + let status = unsafe { + winuser::GetRawInputDeviceInfoW( + handle, + RIDI_PREPARSEDDATA, + buf.as_mut_ptr() as _, + &mut minimum_size, + ) + }; + + if status == UINT::max_value() || status == 0 { + return None; + } + + debug_assert_eq!(minimum_size, status); + + unsafe { buf.set_len(minimum_size as _) }; + + Some(buf) +} + pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { let device_size = size_of::() as UINT; @@ -140,12 +191,12 @@ pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { success == TRUE } -pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool { - // RIDEV_DEVNOTIFY: receive hotplug events - // RIDEV_INPUTSINK: receive events even if we're not in the foreground +pub fn register_for_raw_input(window_handle: HWND) -> bool { + // `RIDEV_DEVNOTIFY`: receive hotplug events + // `RIDEV_INPUTSINK`: receive events even if we're not in the foreground let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; - let devices: [RAWINPUTDEVICE; 2] = [ + let devices: [RAWINPUTDEVICE; 5] = [ RAWINPUTDEVICE { usUsagePage: HID_USAGE_PAGE_GENERIC, usUsage: HID_USAGE_GENERIC_MOUSE, @@ -158,27 +209,182 @@ pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> boo dwFlags: flags, hwndTarget: window_handle, }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_JOYSTICK, + dwFlags: flags, + hwndTarget: window_handle, + }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_GAMEPAD, + dwFlags: flags, + hwndTarget: window_handle, + }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: 0x08, // multi-axis + dwFlags: flags, + hwndTarget: window_handle, + }, ]; register_raw_input_devices(&devices) } -pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { - let mut data: RAWINPUT = unsafe { mem::zeroed() }; - let mut data_size = size_of::() as UINT; +pub enum RawInputData { + Mouse { + device_handle: HANDLE, + raw_mouse: winuser::RAWMOUSE, + }, + Keyboard { + device_handle: HANDLE, + raw_keyboard: winuser::RAWKEYBOARD, + }, + Hid { + device_handle: HANDLE, + raw_hid: RawHidData, + }, +} + +pub struct RawHidData { + pub hid_input_size: u32, + pub hid_input_count: u32, + pub raw_input: Box<[u8]>, +} + +pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { + let mut data_size = 0; let header_size = size_of::() as UINT; - let status = unsafe { + // There are two classes of data this function can output: + // - Raw mouse and keyboard data + // - Raw HID data + // The first class (mouse and keyboard) is always going to write data formatted like the + // `RAWINPUT` struct, with no other data, and can be placed on the stack into `RAWINPUT`. + // The second class (raw HID data) writes the struct, and then a buffer of data appended to + // the end. That data needs to be heap-allocated so we can store all of it. + unsafe { winuser::GetRawInputData( handle, RID_INPUT, - &mut data as *mut _ as _, + ptr::null_mut(), &mut data_size, header_size, ) }; - if status == UINT::max_value() || status == 0 { + let (status, data): (INT, RawInputData); + + if data_size <= size_of::() as UINT { + // Since GetRawInputData is going to write... well, a buffer that's `RAWINPUT` bytes long + // and structured like `RAWINPUT`, we're just going to cut to the chase and write directly into + // a `RAWINPUT` struct. + let mut rawinput_data: RAWINPUT = unsafe { mem::zeroed() }; + + status = unsafe { + winuser::GetRawInputData( + handle, + RID_INPUT, + &mut rawinput_data as *mut RAWINPUT as *mut _, + &mut data_size, + header_size, + ) + } as INT; + + assert_ne!(-1, status); + + let device_handle = rawinput_data.header.hDevice; + + data = match rawinput_data.header.dwType { + winuser::RIM_TYPEMOUSE => { + let raw_mouse = unsafe { rawinput_data.data.mouse().clone() }; + RawInputData::Mouse { + device_handle, + raw_mouse, + } + } + winuser::RIM_TYPEKEYBOARD => { + let raw_keyboard = unsafe { rawinput_data.data.keyboard().clone() }; + RawInputData::Keyboard { + device_handle, + raw_keyboard, + } + } + winuser::RIM_TYPEHID => { + let hid_data = unsafe { rawinput_data.data.hid() }; + let buf_len = hid_data.dwSizeHid as usize * hid_data.dwCount as usize; + let data = unsafe { slice::from_raw_parts(hid_data.bRawData.as_ptr(), buf_len) }; + RawInputData::Hid { + device_handle, + raw_hid: RawHidData { + hid_input_size: hid_data.dwSizeHid, + hid_input_count: hid_data.dwCount, + raw_input: Box::from(data), + }, + } + } + _ => unreachable!(), + }; + } else { + let mut buf = vec![0u8; data_size as usize]; + + status = unsafe { + winuser::GetRawInputData( + handle, + RID_INPUT, + buf.as_mut_ptr() as *mut _, + &mut data_size, + header_size, + ) + } as INT; + + let rawinput_data = buf.as_ptr() as *const RAWINPUT; + + let device_handle = unsafe { (&*rawinput_data).header.hDevice }; + + data = match unsafe { *rawinput_data }.header.dwType { + winuser::RIM_TYPEMOUSE => { + let raw_mouse = unsafe { (&*rawinput_data).data.mouse().clone() }; + RawInputData::Mouse { + device_handle, + raw_mouse, + } + } + winuser::RIM_TYPEKEYBOARD => { + let raw_keyboard = unsafe { (&*rawinput_data).data.keyboard().clone() }; + RawInputData::Keyboard { + device_handle, + raw_keyboard, + } + } + winuser::RIM_TYPEHID => { + let hid_data: winuser::RAWHID = unsafe { (&*rawinput_data).data.hid().clone() }; + + let hid_data_index = { + let hid_data_start = + unsafe { &((&*rawinput_data).data.hid().bRawData) } as *const _; + hid_data_start as usize - buf.as_ptr() as usize + }; + + buf.drain(..hid_data_index); + + RawInputData::Hid { + device_handle, + raw_hid: RawHidData { + hid_input_size: hid_data.dwSizeHid, + hid_input_count: hid_data.dwCount, + raw_input: buf.into_boxed_slice(), + }, + } + } + _ => unreachable!(), + }; + + assert_ne!(-1, status); + } + + if status == 0 { return None; } @@ -200,7 +406,7 @@ fn button_flags_to_element_state( } } -pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option; 3] { +pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option; 5] { [ button_flags_to_element_state( button_flags, @@ -217,5 +423,264 @@ pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option winuser::RI_MOUSE_RIGHT_BUTTON_DOWN, winuser::RI_MOUSE_RIGHT_BUTTON_UP, ), + button_flags_to_element_state( + button_flags, + winuser::RI_MOUSE_BUTTON_4_DOWN, + winuser::RI_MOUSE_BUTTON_4_UP, + ), + button_flags_to_element_state( + button_flags, + winuser::RI_MOUSE_BUTTON_5_DOWN, + winuser::RI_MOUSE_BUTTON_5_UP, + ), ] } + +pub struct Axis { + caps: HIDP_VALUE_CAPS, + value: f64, + prev_value: f64, + axis: Option, +} + +impl fmt::Debug for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[derive(Debug)] + struct Axis { + value: f64, + prev_value: f64, + axis: Option, + } + + let axis_proxy = Axis { + value: self.value, + prev_value: self.prev_value, + axis: self.axis, + }; + + axis_proxy.fmt(f) + } +} + +#[derive(Debug)] +pub struct RawGamepad { + handle: HANDLE, + pre_parsed_data: Vec, + button_count: usize, + pub button_state: Vec, + pub prev_button_state: Vec, + axis_count: usize, + pub axis_state: Vec, +} + +// Reference: https://chromium.googlesource.com/chromium/chromium/+/trunk/content/browser/gamepad/raw_input_data_fetcher_win.cc +impl RawGamepad { + pub fn new(handle: HANDLE) -> Option { + let pre_parsed_data = get_raw_input_pre_parse_info(handle)?; + let data_ptr = pre_parsed_data.as_ptr() as PHIDP_PREPARSED_DATA; + let mut caps = unsafe { mem::zeroed() }; + let status = unsafe { HidP_GetCaps(data_ptr, &mut caps) }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + let mut button_caps_len = caps.NumberInputButtonCaps; + let mut button_caps = Vec::with_capacity(button_caps_len as _); + let status = unsafe { + HidP_GetButtonCaps( + HidP_Input, + button_caps.as_mut_ptr(), + &mut button_caps_len, + data_ptr, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { button_caps.set_len(button_caps_len as _) }; + let mut button_count = 0; + for button_cap in button_caps { + let range = unsafe { button_cap.u.Range() }; + button_count = max(button_count, range.UsageMax); + } + let button_state = vec![false; button_count as usize]; + let mut axis_caps_len = caps.NumberInputValueCaps; + let mut axis_caps = Vec::with_capacity(axis_caps_len as _); + let status = unsafe { + HidP_GetValueCaps( + HidP_Input, + axis_caps.as_mut_ptr(), + &mut axis_caps_len, + data_ptr, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { axis_caps.set_len(axis_caps_len as _) }; + let mut axis_state = Vec::with_capacity(axis_caps_len as _); + let mut axis_count = 0; + for (axis_index, axis_cap) in axis_caps.drain(0..).enumerate() { + axis_state.push(Axis { + caps: axis_cap, + value: 0.0, + prev_value: 0.0, + axis: None, + }); + axis_count = max(axis_count, axis_index + 1); + } + Some(RawGamepad { + handle, + pre_parsed_data, + button_count: button_count as usize, + button_state: button_state.clone(), + prev_button_state: button_state, + axis_count, + axis_state, + }) + } + + fn pre_parsed_data_ptr(&mut self) -> PHIDP_PREPARSED_DATA { + self.pre_parsed_data.as_mut_ptr() as PHIDP_PREPARSED_DATA + } + + fn update_button_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + let pre_parsed_data_ptr = self.pre_parsed_data_ptr(); + self.prev_button_state = + mem::replace(&mut self.button_state, vec![false; self.button_count]); + let mut usages_len = 0; + // This is the officially documented way to get the required length, but it nonetheless returns + // `HIDP_STATUS_BUFFER_TOO_SMALL`... + unsafe { + HidP_GetUsagesEx( + HidP_Input, + 0, + ptr::null_mut(), + &mut usages_len, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + let mut usages = Vec::with_capacity(usages_len as _); + let status = unsafe { + HidP_GetUsagesEx( + HidP_Input, + 0, + usages.as_mut_ptr(), + &mut usages_len, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { usages.set_len(usages_len as _) }; + for usage in usages { + if usage.UsagePage != 0xFF << 8 { + let button_index = (usage.Usage - 1) as usize; + self.button_state[button_index] = true; + } + } + Some(()) + } + + fn update_axis_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + let pre_parsed_data_ptr = self.pre_parsed_data_ptr(); + for axis in &mut self.axis_state { + let (status, axis_value) = if axis.caps.LogicalMin < 0 { + let mut scaled_axis_value = 0; + let status = unsafe { + HidP_GetScaledUsageValue( + HidP_Input, + axis.caps.UsagePage, + 0, + axis.caps.u.Range().UsageMin, + &mut scaled_axis_value, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + (status, scaled_axis_value as f64) + } else { + let mut axis_value = 0; + let status = unsafe { + HidP_GetUsageValue( + HidP_Input, + axis.caps.UsagePage, + 0, + axis.caps.u.Range().UsageMin, + &mut axis_value, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + (status, axis_value as f64) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + axis.prev_value = axis.value; + axis.value = util::normalize_symmetric( + axis_value, + axis.caps.LogicalMin as f64, + axis.caps.LogicalMax as f64, + ); + } + Some(()) + } + + pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + self.update_button_state(raw_input_report)?; + self.update_axis_state(raw_input_report)?; + Some(()) + } + + pub fn get_changed_buttons(&self) -> impl '_ + Iterator { + self.button_state + .iter() + .zip(self.prev_button_state.iter()) + .enumerate() + .filter(|&(_, (button, prev_button))| button != prev_button) + .map(|(index, (button, _))| { + let state = if *button { + ElementState::Pressed + } else { + ElementState::Released + }; + GamepadEvent::Button { + button_id: index as _, + button: None, + state, + } + }) + } + + pub fn get_changed_axes(&self) -> impl '_ + Iterator { + self.axis_state + .iter() + .enumerate() + .filter(|&(_, axis)| axis.value != axis.prev_value) + .map(|(index, axis)| GamepadEvent::Axis { + axis_id: index as _, + axis: axis.axis, + value: axis.value, + stick: false, + }) + } + + pub fn get_gamepad_events(&self) -> Vec { + self.get_changed_axes() + .chain(self.get_changed_buttons()) + .collect() + } + + // pub fn rumble(&mut self, _left_speed: u16, _right_speed: u16) { + // // Even though I can't read German, this is still the most useful resource I found: + // // https://zfx.info/viewtopic.php?t=3574&f=7 + // // I'm not optimistic about it being possible to implement this. + // } +} diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 5faaae3e68..4a761d2fd2 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -1,6 +1,5 @@ use std::{ io, mem, - ops::BitAnd, os::raw::c_void, ptr, slice, sync::atomic::{AtomicBool, Ordering}, @@ -22,12 +21,7 @@ use winapi::{ }, }; -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 081009dc0b..b1a7f7d4da 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -90,12 +90,12 @@ impl Window { ); } - let file_drop_runner = event_loop.runner_shared.clone(); + let shared_data = event_loop.shared_data.clone(); let file_drop_handler = FileDropHandler::new( win.window.0, Box::new(move |event| { if let Ok(e) = event.map_nonuser_event() { - file_drop_runner.send_event(e) + shared_data.runner_shared.send_event(e) } }), ); @@ -113,7 +113,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, subclass_removed: Cell::new(false), recurse_depth: Cell::new(0), diff --git a/src/platform_impl/windows/xinput.rs b/src/platform_impl/windows/xinput.rs new file mode 100644 index 0000000000..9984c8a512 --- /dev/null +++ b/src/platform_impl/windows/xinput.rs @@ -0,0 +1,334 @@ +use std::{ + io, mem, + sync::{Arc, Weak}, +}; + +use rusty_xinput::*; +use winapi::{ + shared::minwindef::{DWORD, WORD}, + um::xinput::*, +}; + +use crate::{ + event::{ + device::{BatteryLevel, GamepadAxis, GamepadButton, GamepadEvent, RumbleError, Side}, + ElementState, + }, + platform_impl::platform::util, +}; + +lazy_static! { + static ref XINPUT_HANDLE: Option = XInputHandle::load_default().ok(); +} + +static BUTTONS: &[(WORD, u32, GamepadButton)] = &[ + (XINPUT_GAMEPAD_DPAD_UP, 12, GamepadButton::DPadUp), + (XINPUT_GAMEPAD_DPAD_DOWN, 13, GamepadButton::DPadDown), + (XINPUT_GAMEPAD_DPAD_LEFT, 14, GamepadButton::DPadLeft), + (XINPUT_GAMEPAD_DPAD_RIGHT, 15, GamepadButton::DPadRight), + (XINPUT_GAMEPAD_START, 9, GamepadButton::Start), + (XINPUT_GAMEPAD_BACK, 8, GamepadButton::Select), + (XINPUT_GAMEPAD_LEFT_THUMB, 10, GamepadButton::LeftStick), + (XINPUT_GAMEPAD_RIGHT_THUMB, 11, GamepadButton::RightStick), + (XINPUT_GAMEPAD_LEFT_SHOULDER, 4, GamepadButton::LeftShoulder), + ( + XINPUT_GAMEPAD_RIGHT_SHOULDER, + 5, + GamepadButton::RightShoulder, + ), + (XINPUT_GAMEPAD_A, 0, GamepadButton::South), + (XINPUT_GAMEPAD_B, 1, GamepadButton::East), + (XINPUT_GAMEPAD_X, 2, GamepadButton::West), + (XINPUT_GAMEPAD_Y, 3, GamepadButton::North), +]; + +pub fn id_from_name(name: &str) -> Option { + // A device name looks like \\?\HID#VID_046D&PID_C21D&IG_00#8&6daf3b6&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} + // The IG_00 substring indicates that this is an XInput gamepad, and that the ID is 00 + let pat = "IG_0"; + name.find(pat) + .and_then(|i| name[i + pat.len()..].chars().next()) + .and_then(|c| match c { + '0' => Some(0), + '1' => Some(1), + '2' => Some(2), + '3' => Some(3), + _ => None, + }) +} + +#[derive(Debug)] +pub struct XInputGamepad { + shared: Arc, + prev_state: Option, + state: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct XInputGamepadShared { + port: DWORD, +} + +impl XInputGamepad { + pub fn new(port: DWORD) -> Option { + XINPUT_HANDLE.as_ref().map(|_| XInputGamepad { + shared: Arc::new(XInputGamepadShared { port }), + prev_state: None, + state: None, + }) + } + + pub fn update_state(&mut self) -> Option<()> { + let state = XINPUT_HANDLE + .as_ref() + .and_then(|h| h.get_state(self.shared.port).ok()); + if state.is_some() { + self.prev_state = mem::replace(&mut self.state, state); + Some(()) + } else { + None + } + } + + fn check_trigger_digital( + events: &mut Vec, + value: bool, + prev_value: Option, + side: Side, + ) { + const LEFT_TRIGGER_ID: u32 = /*BUTTONS.len() as _*/ 16; + const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1; + if Some(value) != prev_value { + let state = if value { + ElementState::Pressed + } else { + ElementState::Released + }; + let (button_id, button) = match side { + Side::Left => (LEFT_TRIGGER_ID, Some(GamepadButton::LeftTrigger)), + Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadButton::RightTrigger)), + }; + events.push(GamepadEvent::Button { + button_id, + button, + state, + }); + } + } + + pub fn get_changed_buttons(&self, events: &mut Vec) { + let (buttons, left_trigger, right_trigger) = match self.state.as_ref() { + Some(state) => ( + state.raw.Gamepad.wButtons, + state.left_trigger_bool(), + state.right_trigger_bool(), + ), + None => return, + }; + let (prev_buttons, prev_left, prev_right) = self + .prev_state + .as_ref() + .map(|state| { + ( + state.raw.Gamepad.wButtons, + Some(state.left_trigger_bool()), + Some(state.right_trigger_bool()), + ) + }) + .unwrap_or_else(|| (0, None, None)); + /* + A = buttons + B = prev_buttons + C = changed + P = pressed + R = released + A B C C A P C B R + (0 0) 0 (0 0) 0 (0 0) 0 + (0 1) 1 (1 1) 1 (1 0) 0 + (1 0) 1 (1 0) 0 (1 1) 1 + (1 1) 0 (0 1) 0 (0 1) 0 + */ + let changed = buttons ^ prev_buttons; + let pressed = changed & buttons; + let released = changed & prev_buttons; + for &(flag, button_id, button) in BUTTONS { + let button = Some(button); + if util::has_flag(pressed, flag) { + events.push(GamepadEvent::Button { + button_id, + button, + state: ElementState::Pressed, + }); + } else if util::has_flag(released, flag) { + events.push(GamepadEvent::Button { + button_id, + button, + state: ElementState::Released, + }); + } + } + Self::check_trigger_digital(events, left_trigger, prev_left, Side::Left); + Self::check_trigger_digital(events, right_trigger, prev_right, Side::Right); + } + + fn check_trigger( + events: &mut Vec, + value: u8, + prev_value: Option, + side: Side, + ) { + const LEFT_TRIGGER_ID: u32 = 4; + const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1; + if Some(value) != prev_value { + let (axis_id, axis) = match side { + Side::Left => (LEFT_TRIGGER_ID, Some(GamepadAxis::LeftTrigger)), + Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadAxis::RightTrigger)), + }; + events.push(GamepadEvent::Axis { + axis_id, + axis, + value: value as f64 / u8::max_value() as f64, + stick: false, + }); + } + } + + fn check_stick( + events: &mut Vec, + value: (i16, i16), + prev_value: Option<(i16, i16)>, + stick: Side, + ) { + let (id, axis) = match stick { + Side::Left => ((0, 1), (GamepadAxis::LeftStickX, GamepadAxis::LeftStickY)), + Side::Right => ((2, 3), (GamepadAxis::RightStickX, GamepadAxis::RightStickY)), + }; + let prev_x = prev_value.map(|prev| prev.0); + let prev_y = prev_value.map(|prev| prev.1); + + let value_f64 = |value_int: i16| match value_int.signum() { + 0 => 0.0, + 1 => value_int as f64 / i16::max_value() as f64, + -1 => value_int as f64 / (i16::min_value() as f64).abs(), + _ => unreachable!(), + }; + + let value_f64 = (value_f64(value.0), value_f64(value.1)); + if prev_x != Some(value.0) { + events.push(GamepadEvent::Axis { + axis_id: id.0, + axis: Some(axis.0), + value: value_f64.0, + stick: true, + }); + } + if prev_y != Some(value.1) { + events.push(GamepadEvent::Axis { + axis_id: id.1, + axis: Some(axis.1), + value: value_f64.1, + stick: true, + }); + } + if prev_x != Some(value.0) || prev_y != Some(value.1) { + events.push(GamepadEvent::Stick { + x_id: id.0, + y_id: id.1, + x_value: value_f64.0, + y_value: value_f64.1, + side: stick, + }) + } + } + + pub fn get_changed_axes(&self, events: &mut Vec) { + let state = match self.state { + Some(ref state) => state, + None => return, + }; + let left_stick = state.left_stick_raw(); + let right_stick = state.right_stick_raw(); + let left_trigger = state.left_trigger(); + let right_trigger = state.right_trigger(); + + let prev_state = self.prev_state.as_ref(); + let prev_left_stick = prev_state.map(|state| state.left_stick_raw()); + let prev_right_stick = prev_state.map(|state| state.right_stick_raw()); + let prev_left_trigger = prev_state.map(|state| state.left_trigger()); + let prev_right_trigger = prev_state.map(|state| state.right_trigger()); + + Self::check_stick(events, left_stick, prev_left_stick, Side::Left); + Self::check_stick(events, right_stick, prev_right_stick, Side::Right); + Self::check_trigger(events, left_trigger, prev_left_trigger, Side::Left); + Self::check_trigger(events, right_trigger, prev_right_trigger, Side::Right); + } + + pub fn get_gamepad_events(&self) -> Vec { + let mut events = Vec::new(); + self.get_changed_axes(&mut events); + self.get_changed_buttons(&mut events); + events + } + + pub fn shared_data(&self) -> Weak { + Arc::downgrade(&self.shared) + } +} + +impl Drop for XInputGamepad { + fn drop(&mut self) { + // For some reason, if you don't attempt to retrieve the xinput gamepad state at least once + // after the gamepad was disconnected, all future attempts to read from a given port (even + // if a controller was plugged back into said port) will fail! I don't know why that happens, + // but this fixes it, so 🤷. + XINPUT_HANDLE + .as_ref() + .and_then(|h| h.get_state(self.shared.port).ok()); + } +} + +impl XInputGamepadShared { + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + let left_speed = (left_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16; + let right_speed = (right_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16; + + let result = XINPUT_HANDLE + .as_ref() + .unwrap() + .set_state(self.port, left_speed, right_speed); + result.map_err(|e| match e { + XInputUsageError::XInputNotLoaded | XInputUsageError::InvalidControllerID => panic!( + "unexpected xinput error {:?}; this is a bug and should be reported", + e + ), + XInputUsageError::DeviceNotConnected => RumbleError::DeviceNotConnected, + XInputUsageError::UnknownError(code) => { + RumbleError::OsError(io::Error::from_raw_os_error(code as i32)) + } + }) + } + + pub fn port(&self) -> u8 { + self.port as _ + } + + pub fn battery_level(&self) -> Option { + use rusty_xinput::BatteryLevel as XBatteryLevel; + + let battery_info = XINPUT_HANDLE + .as_ref() + .unwrap() + .get_gamepad_battery_information(self.port) + .ok()?; + match battery_info.battery_type { + BatteryType::ALKALINE | BatteryType::NIMH => match battery_info.battery_level { + XBatteryLevel::EMPTY => Some(BatteryLevel::Empty), + XBatteryLevel::LOW => Some(BatteryLevel::Low), + XBatteryLevel::MEDIUM => Some(BatteryLevel::Medium), + XBatteryLevel::FULL => Some(BatteryLevel::Full), + _ => None, + }, + _ => None, + } + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000000..69e56224a1 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,19 @@ +use std::ops::BitAnd; + +pub fn has_flag(bitset: T, flag: T) -> bool +where + T: Copy + PartialEq + BitAnd, +{ + bitset & flag == flag +} + +pub fn normalize_asymmetric(value: f64, min: f64, max: f64) -> f64 { + let range = max - min; + let translated = value - min; + let scaled = translated / range; + scaled.clamp(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 252b271a4b..7221009813 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -22,6 +22,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::(); }