mirror of
https://github.com/rust-windowing/winit.git
synced 2026-08-29 04:40:04 -04:00
Overhaul device events API and add gamepad support on Windows (#804)
* Initial implementation * Corrected RAWINPUT buffer sizing * Mostly complete XInput implementation * XInput triggers * Add preliminary CHANGELOG entry. * match unix common API to evl 2.0 * wayland: eventloop2.0 * make EventLoopProxy require T: 'static * Revamp device event API, as well as several misc. fixes on Windows: * When you have multiple windows, you no longer receive duplicate device events * Mouse Device Events now send X-button input * Mouse Device Events now send horizontal scroll wheel input * Add MouseEvent documentation and Device ID debug passthrough * Improve type safety on get_raw_input_data * Remove button_id field from MouseEvent::Button in favor of utton * Remove regex dependency on Windows * Remove axis filtering in XInput * Make gamepads not use lazy_static * Publicly expose gamepad rumble * Unstack DeviceEvent and fix examples/tests * Add HANDLE retrieval method to DeviceExtWindows * Add distinction between non-joystick axes and joystick axes. This helps with properly calculating the deadzone for controller joysticks. One potential issue is that the `Stick` variant isn't used for *all* joysticks, which could be potentially confusing - for example, raw input joysticks will never use the `Stick` variant because we don't understand the semantic meaning of raw input joystick axes. * Add ability to get gamepad port * Fix xinput controller hot swapping * Add functions for enumerating attached devices * Clamp input to [0.0, 1.0] on gamepad rumble * Expose gamepad rumble errors * Add method to check if device is still connected * Add docs * Rename AxisHint and ButtonHint to GamepadAxis and GamepadButton * Add CHANGELOG entry * Update CHANGELOG.md * Add HidId and MovedAbsolute * Fix xinput deprecation warnings * Add ability to retrieve gamepad battery level * Fix weird imports in gamepad example * Update CHANGELOG.md * Resolve francesca64 comments
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -150,6 +150,20 @@ and `WindowEvent::HoveredFile`.
|
|||||||
- On Windows, fix initial dimensions of a fullscreen window.
|
- On Windows, fix initial dimensions of a fullscreen window.
|
||||||
- On Windows, Fix transparent borderless windows rendering wrong.
|
- On Windows, Fix transparent borderless windows rendering wrong.
|
||||||
|
|
||||||
|
- Improve event API documentation.
|
||||||
|
- Overhaul device event API:
|
||||||
|
- **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`.
|
||||||
|
- **Breaking**: Remove `DeviceEvent::Text` variant.
|
||||||
|
- **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`.
|
||||||
|
- **Breaking**: Removed device IDs from `WindowEvent` variants.
|
||||||
|
- Add `enumerate` function on device ID types to list all attached devices of that type.
|
||||||
|
- Add `is_connected` function on device ID types check if the specified device is still available.
|
||||||
|
- **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`.
|
||||||
|
- Add `handle` function to retrieve the underlying `HANDLE`.
|
||||||
|
- On Windows, fix duplicate device events getting sent if Winit managed multiple windows.
|
||||||
|
- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases.
|
||||||
|
- Added gamepad support on Windows via raw input and XInput.
|
||||||
|
|
||||||
# Version 0.19.1 (2019-04-08)
|
# Version 0.19.1 (2019-04-08)
|
||||||
|
|
||||||
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
|
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ features = ["display_link"]
|
|||||||
|
|
||||||
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
|
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
|
rusty-xinput = "1.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -58,6 +59,7 @@ features = [
|
|||||||
"commctrl",
|
"commctrl",
|
||||||
"dwmapi",
|
"dwmapi",
|
||||||
"errhandlingapi",
|
"errhandlingapi",
|
||||||
|
"hidpi",
|
||||||
"hidusage",
|
"hidusage",
|
||||||
"libloaderapi",
|
"libloaderapi",
|
||||||
"objbase",
|
"objbase",
|
||||||
@@ -73,6 +75,7 @@ features = [
|
|||||||
"wingdi",
|
"wingdi",
|
||||||
"winnt",
|
"winnt",
|
||||||
"winuser",
|
"winuser",
|
||||||
|
"xinput",
|
||||||
]
|
]
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||||
|
|||||||
@@ -15,14 +15,10 @@ fn main() {
|
|||||||
event_loop.run(move |event, _, control_flow| match event {
|
event_loop.run(move |event, _, control_flow| match event {
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event:
|
event:
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
state: ElementState::Pressed,
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
},
|
}),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||||
|
|||||||
@@ -17,16 +17,12 @@ fn main() {
|
|||||||
match event {
|
match event {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
state: ElementState::Released,
|
||||||
KeyboardInput {
|
virtual_keycode: Some(key),
|
||||||
state: ElementState::Released,
|
modifiers,
|
||||||
virtual_keycode: Some(key),
|
|
||||||
modifiers,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
use winit::event::VirtualKeyCode::*;
|
use winit::event::VirtualKeyCode::*;
|
||||||
match key {
|
match key {
|
||||||
Escape => *control_flow = ControlFlow::Exit,
|
Escape => *control_flow = ControlFlow::Exit,
|
||||||
|
|||||||
@@ -35,15 +35,11 @@ fn main() {
|
|||||||
match event {
|
match event {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
virtual_keycode: Some(virtual_code),
|
||||||
KeyboardInput {
|
state,
|
||||||
virtual_keycode: Some(virtual_code),
|
|
||||||
state,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} => match (virtual_code, state) {
|
}) => match (virtual_code, state) {
|
||||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||||
if window.fullscreen().is_some() {
|
if window.fullscreen().is_some() {
|
||||||
|
|||||||
52
examples/gamepad.rs
Normal file
52
examples/gamepad.rs
Normal file
@@ -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,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
60
examples/gamepad_rumble.rs
Normal file
60
examples/gamepad_rumble.rs
Normal file
@@ -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::<Vec<_>>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -41,15 +41,11 @@ fn main() {
|
|||||||
// closing the window. How to close the window is detailed in the handler for
|
// closing the window. How to close the window is detailed in the handler for
|
||||||
// the Y key.
|
// the Y key.
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
virtual_keycode: Some(virtual_code),
|
||||||
KeyboardInput {
|
state: Released,
|
||||||
virtual_keycode: Some(virtual_code),
|
|
||||||
state: Released,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
match virtual_code {
|
match virtual_code {
|
||||||
Y => {
|
Y => {
|
||||||
if close_requested {
|
if close_requested {
|
||||||
|
|||||||
@@ -49,16 +49,12 @@ fn main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
state: ElementState::Released,
|
||||||
KeyboardInput {
|
virtual_keycode: Some(key),
|
||||||
state: ElementState::Released,
|
modifiers,
|
||||||
virtual_keycode: Some(key),
|
|
||||||
modifiers,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
window.set_title(&format!("{:?}", key));
|
window.set_title(&format!("{:?}", key));
|
||||||
let state = !modifiers.shift;
|
let state = !modifiers.shift;
|
||||||
use VirtualKeyCode::*;
|
use VirtualKeyCode::*;
|
||||||
@@ -148,15 +144,11 @@ fn main() {
|
|||||||
Event::WindowEvent { event, window_id } => match event {
|
Event::WindowEvent { event, window_id } => match event {
|
||||||
WindowEvent::CloseRequested
|
WindowEvent::CloseRequested
|
||||||
| WindowEvent::Destroyed
|
| WindowEvent::Destroyed
|
||||||
| WindowEvent::KeyboardInput {
|
| WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
state: ElementState::Released,
|
||||||
KeyboardInput {
|
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
window_senders.remove(&window_id);
|
window_senders.remove(&window_id);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@@ -29,14 +29,10 @@ fn main() {
|
|||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
state: ElementState::Pressed,
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
let window = Window::new(&event_loop).unwrap();
|
let window = Window::new(&event_loop).unwrap();
|
||||||
windows.insert(window.id(), window);
|
windows.insert(window.id(), window);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,11 @@ fn main() {
|
|||||||
match event {
|
match event {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
input:
|
virtual_keycode: Some(VirtualKeyCode::Space),
|
||||||
KeyboardInput {
|
state: ElementState::Released,
|
||||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
|
||||||
state: ElementState::Released,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
resizable = !resizable;
|
resizable = !resizable;
|
||||||
println!("Resizable: {}", resizable);
|
println!("Resizable: {}", resizable);
|
||||||
window.set_resizable(resizable);
|
window.set_resizable(resizable);
|
||||||
|
|||||||
155
src/event.rs
155
src/event.rs
@@ -9,11 +9,12 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
platform_impl,
|
|
||||||
window::WindowId,
|
window::WindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Describes a generic event.
|
pub mod device;
|
||||||
|
|
||||||
|
/// A generic event.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Event<T> {
|
pub enum Event<T> {
|
||||||
/// Emitted when the OS sends an event to a winit window.
|
/// Emitted when the OS sends an event to a winit window.
|
||||||
@@ -21,12 +22,16 @@ pub enum Event<T> {
|
|||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
event: WindowEvent,
|
event: WindowEvent,
|
||||||
},
|
},
|
||||||
/// Emitted when the OS sends an event to a device.
|
|
||||||
DeviceEvent {
|
/// Emitted when a mouse device has generated input.
|
||||||
device_id: DeviceId,
|
MouseEvent(device::MouseId, device::MouseEvent),
|
||||||
event: DeviceEvent,
|
/// Emitted when a keyboard device has generated input.
|
||||||
},
|
KeyboardEvent(device::KeyboardId, device::KeyboardEvent),
|
||||||
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
|
HidEvent(device::HidId, device::HidEvent),
|
||||||
|
/// Emitted when a gamepad/joystick device has generated input.
|
||||||
|
GamepadEvent(device::GamepadHandle, device::GamepadEvent),
|
||||||
|
|
||||||
|
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](../event_loop/struct.EventLoopProxy.html#method.send_event)
|
||||||
UserEvent(T),
|
UserEvent(T),
|
||||||
/// Emitted when new events arrive from the OS to be processed.
|
/// Emitted when new events arrive from the OS to be processed.
|
||||||
NewEvents(StartCause),
|
NewEvents(StartCause),
|
||||||
@@ -51,7 +56,10 @@ impl<T> Event<T> {
|
|||||||
match self {
|
match self {
|
||||||
UserEvent(_) => Err(self),
|
UserEvent(_) => Err(self),
|
||||||
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
|
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)),
|
NewEvents(cause) => Ok(NewEvents(cause)),
|
||||||
EventsCleared => Ok(EventsCleared),
|
EventsCleared => Ok(EventsCleared),
|
||||||
LoopDestroyed => Ok(LoopDestroyed),
|
LoopDestroyed => Ok(LoopDestroyed),
|
||||||
@@ -61,8 +69,8 @@ impl<T> Event<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes the reason the event loop is resuming.
|
/// The reason the event loop is resuming.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum StartCause {
|
pub enum StartCause {
|
||||||
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
|
/// 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
|
/// moment the timeout was requested and the requested resume time. The actual resume time is
|
||||||
@@ -87,7 +95,7 @@ pub enum StartCause {
|
|||||||
Init,
|
Init,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes an event from a `Window`.
|
/// An event from a `Window`.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum WindowEvent {
|
pub enum WindowEvent {
|
||||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||||
@@ -129,15 +137,10 @@ pub enum WindowEvent {
|
|||||||
Focused(bool),
|
Focused(bool),
|
||||||
|
|
||||||
/// An event from the keyboard has been received.
|
/// An event from the keyboard has been received.
|
||||||
KeyboardInput {
|
KeyboardInput(KeyboardInput),
|
||||||
device_id: DeviceId,
|
|
||||||
input: KeyboardInput,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The cursor has moved on the window.
|
/// The cursor has moved on the window.
|
||||||
CursorMoved {
|
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
|
/// (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
|
/// 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.
|
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
|
||||||
@@ -146,14 +149,13 @@ pub enum WindowEvent {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/// The cursor has entered the window.
|
/// The cursor has entered the window.
|
||||||
CursorEntered { device_id: DeviceId },
|
CursorEntered,
|
||||||
|
|
||||||
/// The cursor has left the window.
|
/// The cursor has left the window.
|
||||||
CursorLeft { device_id: DeviceId },
|
CursorLeft,
|
||||||
|
|
||||||
/// A mouse wheel movement or touchpad scroll occurred.
|
/// A mouse wheel movement or touchpad scroll occurred.
|
||||||
MouseWheel {
|
MouseWheel {
|
||||||
device_id: DeviceId,
|
|
||||||
delta: MouseScrollDelta,
|
delta: MouseScrollDelta,
|
||||||
phase: TouchPhase,
|
phase: TouchPhase,
|
||||||
modifiers: ModifiersState,
|
modifiers: ModifiersState,
|
||||||
@@ -161,7 +163,6 @@ pub enum WindowEvent {
|
|||||||
|
|
||||||
/// An mouse button press has been received.
|
/// An mouse button press has been received.
|
||||||
MouseInput {
|
MouseInput {
|
||||||
device_id: DeviceId,
|
|
||||||
state: ElementState,
|
state: ElementState,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
modifiers: ModifiersState,
|
modifiers: ModifiersState,
|
||||||
@@ -172,18 +173,7 @@ pub enum WindowEvent {
|
|||||||
/// At the moment, only supported on Apple forcetouch-capable macbooks.
|
/// 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
|
/// 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).
|
/// is being pressed) and stage (integer representing the click level).
|
||||||
TouchpadPressure {
|
TouchpadPressure { pressure: f32, stage: i64 },
|
||||||
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,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The OS or application has requested that the window be redrawn.
|
/// The OS or application has requested that the window be redrawn.
|
||||||
RedrawRequested,
|
RedrawRequested,
|
||||||
@@ -203,80 +193,7 @@ pub enum WindowEvent {
|
|||||||
HiDpiFactorChanged(f64),
|
HiDpiFactorChanged(f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifier of an input device.
|
/// A keyboard input event.
|
||||||
///
|
|
||||||
/// 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),
|
|
||||||
|
|
||||||
/// Keyboard modifiers have changed
|
|
||||||
#[doc(hidden)]
|
|
||||||
ModifiersChanged {
|
|
||||||
modifiers: ModifiersState,
|
|
||||||
},
|
|
||||||
|
|
||||||
Text {
|
|
||||||
codepoint: char,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a keyboard input event.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct KeyboardInput {
|
pub struct KeyboardInput {
|
||||||
@@ -302,13 +219,16 @@ pub struct KeyboardInput {
|
|||||||
pub modifiers: ModifiersState,
|
pub modifiers: ModifiersState,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes touch-screen input state.
|
/// Touch input state.
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum TouchPhase {
|
pub enum TouchPhase {
|
||||||
Started,
|
Started,
|
||||||
Moved,
|
Moved,
|
||||||
Ended,
|
Ended,
|
||||||
|
/// The touch has been cancelled by the OS.
|
||||||
|
///
|
||||||
|
/// This can occur in a variety of situations, such as the window losing focus.
|
||||||
Cancelled,
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +250,6 @@ pub enum TouchPhase {
|
|||||||
/// device against their face.
|
/// device against their face.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Touch {
|
pub struct Touch {
|
||||||
pub device_id: DeviceId,
|
|
||||||
pub phase: TouchPhase,
|
pub phase: TouchPhase,
|
||||||
pub location: LogicalPosition,
|
pub location: LogicalPosition,
|
||||||
/// Describes how hard the screen was pressed. May be `None` if the platform
|
/// Describes how hard the screen was pressed. May be `None` if the platform
|
||||||
@@ -341,6 +260,8 @@ pub struct Touch {
|
|||||||
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
|
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
|
||||||
pub force: Option<Force>,
|
pub force: Option<Force>,
|
||||||
/// Unique identifier of a finger.
|
/// Unique identifier of a finger.
|
||||||
|
///
|
||||||
|
/// This may get reused by the system after the touch ends.
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,16 +330,16 @@ pub type AxisId = u32;
|
|||||||
/// Identifier for a specific button on some device.
|
/// Identifier for a specific button on some device.
|
||||||
pub type ButtonId = u32;
|
pub type ButtonId = u32;
|
||||||
|
|
||||||
/// Describes the input state of a key.
|
/// The input state of a key or button.
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum ElementState {
|
pub enum ElementState {
|
||||||
Pressed,
|
Pressed,
|
||||||
Released,
|
Released,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a button of a mouse controller.
|
/// A button on a mouse.
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum MouseButton {
|
pub enum MouseButton {
|
||||||
Left,
|
Left,
|
||||||
@@ -427,7 +348,7 @@ pub enum MouseButton {
|
|||||||
Other(u8),
|
Other(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a difference in the mouse scroll wheel state.
|
/// A difference in the mouse scroll wheel state.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum MouseScrollDelta {
|
pub enum MouseScrollDelta {
|
||||||
@@ -446,7 +367,7 @@ pub enum MouseScrollDelta {
|
|||||||
PixelDelta(LogicalPosition),
|
PixelDelta(LogicalPosition),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Symbolic name for a keyboard key.
|
/// Symbolic name of a keyboard key.
|
||||||
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@@ -645,7 +566,7 @@ pub enum VirtualKeyCode {
|
|||||||
Cut,
|
Cut,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the current state of the keyboard modifiers
|
/// The current state of the keyboard modifiers
|
||||||
///
|
///
|
||||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||||
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||||
|
|||||||
363
src/event/device.rs
Normal file
363
src/event/device.rs
Normal file
@@ -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, MouseButton, ModifiersState},
|
||||||
|
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<GamepadAxis>,
|
||||||
|
value: f64,
|
||||||
|
/// Whether or not this axis has also produced a [`GamepadEvent::Stick`] event.
|
||||||
|
stick: bool,
|
||||||
|
},
|
||||||
|
/// A two-axis joystick's value has changed.
|
||||||
|
///
|
||||||
|
/// This is mainly provided to assist with deadzone calculation, as proper deadzones should be
|
||||||
|
/// calculated via the combined distance of each joystick axis from the center of the joystick,
|
||||||
|
/// rather than per-axis.
|
||||||
|
///
|
||||||
|
/// Note that this is only guaranteed to be emitted for traditionally laid out gamepads. More
|
||||||
|
/// complex joysticks generally don't report specifics of their layout to the operating system,
|
||||||
|
/// preventing Winit from automatically aggregating their axis input into two-axis stick events.
|
||||||
|
Stick {
|
||||||
|
/// The X axis' ID.
|
||||||
|
x_id: AxisId,
|
||||||
|
/// The Y axis' ID.
|
||||||
|
y_id: AxisId,
|
||||||
|
x_value: f64,
|
||||||
|
y_value: f64,
|
||||||
|
/// Which joystick side produced this event.
|
||||||
|
side: Side,
|
||||||
|
},
|
||||||
|
Button {
|
||||||
|
button_id: ButtonId,
|
||||||
|
/// A hint regarding the location of the button.
|
||||||
|
///
|
||||||
|
/// The caveats on the `Axis.hint` field also apply here.
|
||||||
|
button: Option<GamepadButton>,
|
||||||
|
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<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
|
||||||
|
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<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
|
||||||
|
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<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
|
||||||
|
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<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
|
||||||
|
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<u8> {
|
||||||
|
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<BatteryLevel> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,6 +138,7 @@ pub mod event_loop;
|
|||||||
mod icon;
|
mod icon;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
mod platform_impl;
|
mod platform_impl;
|
||||||
|
mod util;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use libc;
|
|||||||
use winapi::shared::windef::HWND;
|
use winapi::shared::windef::HWND;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
event::DeviceId,
|
event::device::{GamepadHandle, KeyboardId, MouseId},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
monitor::MonitorHandle,
|
monitor::MonitorHandle,
|
||||||
platform_impl::EventLoop as WindowsEventLoop,
|
platform_impl::EventLoop as WindowsEventLoop,
|
||||||
@@ -149,17 +149,49 @@ impl MonitorHandleExtWindows for MonitorHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
/// Additional methods on device types that are specific to Windows.
|
||||||
pub trait DeviceIdExtWindows {
|
pub trait DeviceExtWindows {
|
||||||
/// Returns an identifier that persistently refers to this specific device.
|
/// Returns an identifier that persistently refers to this specific device.
|
||||||
///
|
///
|
||||||
/// Will return `None` if the device is no longer available.
|
/// Will return `None` if the device is no longer available.
|
||||||
fn persistent_identifier(&self) -> Option<String>;
|
fn persistent_identifier(&self) -> Option<String>;
|
||||||
|
|
||||||
|
/// Returns the handle of the device - `HANDLE`.
|
||||||
|
fn handle(&self) -> *mut c_void;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceIdExtWindows for DeviceId {
|
impl DeviceExtWindows for MouseId {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn persistent_identifier(&self) -> Option<String> {
|
fn persistent_identifier(&self) -> Option<String> {
|
||||||
self.0.persistent_identifier()
|
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<String> {
|
||||||
|
self.0.persistent_identifier()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn handle(&self) -> *mut c_void {
|
||||||
|
self.0.handle() as _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceExtWindows for GamepadHandle {
|
||||||
|
#[inline]
|
||||||
|
fn persistent_identifier(&self) -> Option<String> {
|
||||||
|
self.0.persistent_identifier()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn handle(&self) -> *mut c_void {
|
||||||
|
self.0.handle() as _
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use parking_lot::Mutex;
|
|||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::VecDeque,
|
collections::{HashMap, VecDeque},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem, panic, ptr,
|
mem, panic, ptr,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -45,8 +45,11 @@ use winapi::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
|
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
||||||
event::{DeviceEvent, Event, Force, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent},
|
event::{
|
||||||
|
device::{GamepadEvent, HidEvent, KeyboardEvent, MouseEvent},
|
||||||
|
Event, Force, KeyboardInput, MouseButton, StartCause, Touch, TouchPhase, WindowEvent,
|
||||||
|
},
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||||
platform_impl::platform::{
|
platform_impl::platform::{
|
||||||
dpi::{
|
dpi::{
|
||||||
@@ -55,11 +58,12 @@ use crate::{
|
|||||||
drop_handler::FileDropHandler,
|
drop_handler::FileDropHandler,
|
||||||
event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey},
|
event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey},
|
||||||
monitor,
|
monitor,
|
||||||
raw_input::{get_raw_input_data, get_raw_mouse_button_state},
|
gamepad::Gamepad,
|
||||||
|
raw_input::{self, get_raw_input_data, get_raw_mouse_button_state, RawInputData},
|
||||||
util,
|
util,
|
||||||
window::adjust_size,
|
window::adjust_size,
|
||||||
window_state::{CursorFlags, WindowFlags, WindowState},
|
window_state::{CursorFlags, WindowFlags, WindowState},
|
||||||
wrap_device_id, WindowId, DEVICE_ID,
|
GamepadHandle, HidId, KeyboardId, MouseId, WindowId,
|
||||||
},
|
},
|
||||||
window::{Fullscreen, WindowId as RootWindowId},
|
window::{Fullscreen, WindowId as RootWindowId},
|
||||||
};
|
};
|
||||||
@@ -99,24 +103,32 @@ lazy_static! {
|
|||||||
|
|
||||||
pub(crate) struct SubclassInput<T> {
|
pub(crate) struct SubclassInput<T> {
|
||||||
pub window_state: Arc<Mutex<WindowState>>,
|
pub window_state: Arc<Mutex<WindowState>>,
|
||||||
pub event_loop_runner: EventLoopRunnerShared<T>,
|
pub shared_data: Rc<SubclassSharedData<T>>,
|
||||||
pub file_drop_handler: FileDropHandler,
|
pub file_drop_handler: FileDropHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SubclassInput<T> {
|
impl<T> SubclassInput<T> {
|
||||||
unsafe fn send_event(&self, event: Event<T>) {
|
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<T> {
|
struct ThreadMsgTargetSubclassInput<T> {
|
||||||
event_loop_runner: EventLoopRunnerShared<T>,
|
shared_data: Rc<SubclassSharedData<T>>,
|
||||||
user_event_receiver: Receiver<T>,
|
user_event_receiver: Receiver<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum DeviceId {
|
||||||
|
Mouse(MouseId),
|
||||||
|
Keyboard(KeyboardId),
|
||||||
|
Hid(HidId),
|
||||||
|
Gamepad(GamepadHandle, Gamepad),
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> ThreadMsgTargetSubclassInput<T> {
|
impl<T> ThreadMsgTargetSubclassInput<T> {
|
||||||
unsafe fn send_event(&self, event: Event<T>) {
|
unsafe fn send_event(&self, event: Event<T>) {
|
||||||
self.event_loop_runner.send_event(event);
|
self.shared_data.runner_shared.send_event(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +141,7 @@ pub struct EventLoopWindowTarget<T> {
|
|||||||
thread_id: DWORD,
|
thread_id: DWORD,
|
||||||
trigger_newevents_on_redraw: Arc<AtomicBool>,
|
trigger_newevents_on_redraw: Arc<AtomicBool>,
|
||||||
thread_msg_target: HWND,
|
thread_msg_target: HWND,
|
||||||
pub(crate) runner_shared: EventLoopRunnerShared<T>,
|
pub(crate) shared_data: Rc<SubclassSharedData<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! main_thread_check {
|
macro_rules! main_thread_check {
|
||||||
@@ -167,12 +179,15 @@ impl<T: 'static> EventLoop<T> {
|
|||||||
|
|
||||||
pub fn new_dpi_unaware_any_thread() -> EventLoop<T> {
|
pub fn new_dpi_unaware_any_thread() -> EventLoop<T> {
|
||||||
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
|
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
|
||||||
let runner_shared = Rc::new(ELRShared {
|
let shared_data = Rc::new(SubclassSharedData {
|
||||||
runner: RefCell::new(None),
|
runner_shared: ELRShared {
|
||||||
buffer: RefCell::new(VecDeque::new()),
|
runner: RefCell::new(None),
|
||||||
|
buffer: RefCell::new(VecDeque::new()),
|
||||||
|
},
|
||||||
|
active_device_ids: RefCell::new(HashMap::default()),
|
||||||
});
|
});
|
||||||
let (thread_msg_target, thread_msg_sender) =
|
let (thread_msg_target, thread_msg_sender) =
|
||||||
thread_event_target_window(runner_shared.clone());
|
thread_event_target_window(shared_data.clone());
|
||||||
|
|
||||||
EventLoop {
|
EventLoop {
|
||||||
thread_msg_sender,
|
thread_msg_sender,
|
||||||
@@ -181,7 +196,7 @@ impl<T: 'static> EventLoop<T> {
|
|||||||
thread_id,
|
thread_id,
|
||||||
trigger_newevents_on_redraw: Arc::new(AtomicBool::new(true)),
|
trigger_newevents_on_redraw: Arc::new(AtomicBool::new(true)),
|
||||||
thread_msg_target,
|
thread_msg_target,
|
||||||
runner_shared,
|
shared_data,
|
||||||
},
|
},
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
},
|
},
|
||||||
@@ -212,10 +227,10 @@ impl<T: 'static> EventLoop<T> {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
{
|
{
|
||||||
let runner_shared = self.window_target.p.runner_shared.clone();
|
let shared_data = self.window_target.p.shared_data.clone();
|
||||||
let mut runner_ref = runner_shared.runner.borrow_mut();
|
let mut runner_ref = shared_data.runner_shared.runner.borrow_mut();
|
||||||
loop {
|
loop {
|
||||||
let event = runner_shared.buffer.borrow_mut().pop_front();
|
let event = shared_data.runner_shared.buffer.borrow_mut().pop_front();
|
||||||
match event {
|
match event {
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
runner.process_event(e);
|
runner.process_event(e);
|
||||||
@@ -230,6 +245,7 @@ impl<T: 'static> EventLoop<T> {
|
|||||||
() => {
|
() => {
|
||||||
self.window_target
|
self.window_target
|
||||||
.p
|
.p
|
||||||
|
.shared_data
|
||||||
.runner_shared
|
.runner_shared
|
||||||
.runner
|
.runner
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@@ -280,7 +296,13 @@ impl<T: 'static> EventLoop<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runner!().call_event_handler(Event::LoopDestroyed);
|
runner!().call_event_handler(Event::LoopDestroyed);
|
||||||
*self.window_target.p.runner_shared.runner.borrow_mut() = None;
|
*self
|
||||||
|
.window_target
|
||||||
|
.p
|
||||||
|
.shared_data
|
||||||
|
.runner_shared
|
||||||
|
.runner
|
||||||
|
.borrow_mut() = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||||
@@ -289,6 +311,70 @@ impl<T: 'static> EventLoop<T> {
|
|||||||
event_send: self.thread_msg_sender.clone(),
|
event_send: self.thread_msg_sender.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn devices<R: 'static>(
|
||||||
|
&self,
|
||||||
|
f: impl FnMut(&DeviceId) -> Option<R>,
|
||||||
|
) -> impl '_ + Iterator<Item = R> {
|
||||||
|
// 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::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouses(&self) -> impl '_ + Iterator<Item = crate::event::device::MouseId> {
|
||||||
|
self.devices(|d| match d {
|
||||||
|
DeviceId::Mouse(id) => Some(id.clone().into()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keyboards(&self) -> impl '_ + Iterator<Item = crate::event::device::KeyboardId> {
|
||||||
|
self.devices(|d| match d {
|
||||||
|
DeviceId::Keyboard(id) => Some(id.clone().into()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hids(&self) -> impl '_ + Iterator<Item = crate::event::device::HidId> {
|
||||||
|
self.devices(|d| match d {
|
||||||
|
DeviceId::Hid(id) => Some(id.clone().into()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gamepads(&self) -> impl '_ + Iterator<Item = crate::event::device::GamepadHandle> {
|
||||||
|
self.devices(|d| match d {
|
||||||
|
DeviceId::Gamepad(handle, _) => Some(handle.clone().into()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
impl<T> EventLoopWindowTarget<T> {
|
||||||
@@ -317,7 +403,10 @@ fn main_thread_id() -> DWORD {
|
|||||||
unsafe { MAIN_THREAD_ID }
|
unsafe { MAIN_THREAD_ID }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
|
pub(crate) struct SubclassSharedData<T> {
|
||||||
|
pub runner_shared: ELRShared<T>,
|
||||||
|
pub active_device_ids: RefCell<HashMap<HANDLE, DeviceId>>,
|
||||||
|
}
|
||||||
pub(crate) struct ELRShared<T> {
|
pub(crate) struct ELRShared<T> {
|
||||||
runner: RefCell<Option<EventLoopRunner<T>>>,
|
runner: RefCell<Option<EventLoopRunner<T>>>,
|
||||||
buffer: RefCell<VecDeque<Event<T>>>,
|
buffer: RefCell<VecDeque<Event<T>>>,
|
||||||
@@ -825,7 +914,7 @@ lazy_static! {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) -> (HWND, Sender<T>) {
|
fn thread_event_target_window<T>(shared_data: Rc<SubclassSharedData<T>>) -> (HWND, Sender<T>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let window = winuser::CreateWindowExW(
|
let window = winuser::CreateWindowExW(
|
||||||
winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED,
|
winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED,
|
||||||
@@ -853,7 +942,7 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
|
|||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
let subclass_input = ThreadMsgTargetSubclassInput {
|
let subclass_input = ThreadMsgTargetSubclassInput {
|
||||||
event_loop_runner,
|
shared_data,
|
||||||
user_event_receiver: rx,
|
user_event_receiver: rx,
|
||||||
};
|
};
|
||||||
let input_ptr = Box::into_raw(Box::new(subclass_input));
|
let input_ptr = Box::into_raw(Box::new(subclass_input));
|
||||||
@@ -865,6 +954,9 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
|
|||||||
);
|
);
|
||||||
assert_eq!(subclass_result, 1);
|
assert_eq!(subclass_result, 1);
|
||||||
|
|
||||||
|
// Set up raw input
|
||||||
|
raw_input::register_for_raw_input(window);
|
||||||
|
|
||||||
(window, tx)
|
(window, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -926,14 +1018,14 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
winuser::WM_ENTERSIZEMOVE => {
|
winuser::WM_ENTERSIZEMOVE => {
|
||||||
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
|
let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
|
||||||
if let Some(ref mut runner) = *runner {
|
if let Some(ref mut runner) = *runner {
|
||||||
runner.in_modal_loop = true;
|
runner.in_modal_loop = true;
|
||||||
}
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
winuser::WM_EXITSIZEMOVE => {
|
winuser::WM_EXITSIZEMOVE => {
|
||||||
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
|
let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
|
||||||
if let Some(ref mut runner) = *runner {
|
if let Some(ref mut runner) = *runner {
|
||||||
runner.in_modal_loop = false;
|
runner.in_modal_loop = false;
|
||||||
}
|
}
|
||||||
@@ -982,7 +1074,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
|
|
||||||
_ if msg == *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID => {
|
_ if msg == *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID => {
|
||||||
use crate::event::WindowEvent::RedrawRequested;
|
use crate::event::WindowEvent::RedrawRequested;
|
||||||
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
|
let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
|
||||||
subclass_input.window_state.lock().queued_out_of_band_redraw = false;
|
subclass_input.window_state.lock().queued_out_of_band_redraw = false;
|
||||||
if let Some(ref mut runner) = *runner {
|
if let Some(ref mut runner) = *runner {
|
||||||
// This check makes sure that calls to `request_redraw()` during `EventsCleared`
|
// This check makes sure that calls to `request_redraw()` during `EventsCleared`
|
||||||
@@ -1171,9 +1263,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
if mouse_was_outside_window {
|
if mouse_was_outside_window {
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: CursorEntered {
|
event: CursorEntered,
|
||||||
device_id: DEVICE_ID,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calling TrackMouseEvent in order to receive mouse leave events.
|
// Calling TrackMouseEvent in order to receive mouse leave events.
|
||||||
@@ -1193,7 +1283,6 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: CursorMoved {
|
event: CursorMoved {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
position,
|
position,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
@@ -1213,9 +1302,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
|
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: CursorLeft {
|
event: CursorLeft,
|
||||||
device_id: DEVICE_ID,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
0
|
0
|
||||||
@@ -1231,7 +1318,6 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: WindowEvent::MouseWheel {
|
event: WindowEvent::MouseWheel {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
delta: LineDelta(0.0, value),
|
delta: LineDelta(0.0, value),
|
||||||
phase: TouchPhase::Moved,
|
phase: TouchPhase::Moved,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
@@ -1251,7 +1337,6 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: WindowEvent::MouseWheel {
|
event: WindowEvent::MouseWheel {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
delta: LineDelta(value, 0.0),
|
delta: LineDelta(value, 0.0),
|
||||||
phase: TouchPhase::Moved,
|
phase: TouchPhase::Moved,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
@@ -1269,15 +1354,12 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: WindowEvent::KeyboardInput {
|
event: WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
device_id: DEVICE_ID,
|
state: Pressed,
|
||||||
input: KeyboardInput {
|
scancode,
|
||||||
state: Pressed,
|
virtual_keycode: vkey,
|
||||||
scancode,
|
modifiers: event::get_key_mods(),
|
||||||
virtual_keycode: vkey,
|
}),
|
||||||
modifiers: event::get_key_mods(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
// Windows doesn't emit a delete character by default, but in order to make it
|
// 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.
|
// consistent with the other platforms we'll emit a delete character here.
|
||||||
@@ -1297,31 +1379,27 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: WindowEvent::KeyboardInput {
|
event: WindowEvent::KeyboardInput(KeyboardInput {
|
||||||
device_id: DEVICE_ID,
|
state: Released,
|
||||||
input: KeyboardInput {
|
scancode,
|
||||||
state: Released,
|
virtual_keycode: vkey,
|
||||||
scancode,
|
modifiers: event::get_key_mods(),
|
||||||
virtual_keycode: vkey,
|
}),
|
||||||
modifiers: event::get_key_mods(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_LBUTTONDOWN => {
|
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());
|
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||||
|
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Pressed,
|
state: Pressed,
|
||||||
button: Left,
|
button: MouseButton::Left,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1329,18 +1407,15 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_LBUTTONUP => {
|
winuser::WM_LBUTTONUP => {
|
||||||
use crate::event::{
|
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
|
||||||
ElementState::Released, MouseButton::Left, WindowEvent::MouseInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
release_mouse(&mut *subclass_input.window_state.lock());
|
release_mouse(&mut *subclass_input.window_state.lock());
|
||||||
|
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Released,
|
state: Released,
|
||||||
button: Left,
|
button: MouseButton::Left,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1348,18 +1423,15 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_RBUTTONDOWN => {
|
winuser::WM_RBUTTONDOWN => {
|
||||||
use crate::event::{
|
use crate::event::{ElementState::Pressed, WindowEvent::MouseInput};
|
||||||
ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||||
|
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Pressed,
|
state: Pressed,
|
||||||
button: Right,
|
button: MouseButton::Right,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1367,18 +1439,15 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_RBUTTONUP => {
|
winuser::WM_RBUTTONUP => {
|
||||||
use crate::event::{
|
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
|
||||||
ElementState::Released, MouseButton::Right, WindowEvent::MouseInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
release_mouse(&mut *subclass_input.window_state.lock());
|
release_mouse(&mut *subclass_input.window_state.lock());
|
||||||
|
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Released,
|
state: Released,
|
||||||
button: Right,
|
button: MouseButton::Right,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1386,18 +1455,15 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_MBUTTONDOWN => {
|
winuser::WM_MBUTTONDOWN => {
|
||||||
use crate::event::{
|
use crate::event::{ElementState::Pressed, WindowEvent::MouseInput};
|
||||||
ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||||
|
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Pressed,
|
state: Pressed,
|
||||||
button: Middle,
|
button: MouseButton::Middle,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1405,18 +1471,15 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_MBUTTONUP => {
|
winuser::WM_MBUTTONUP => {
|
||||||
use crate::event::{
|
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
|
||||||
ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
release_mouse(&mut *subclass_input.window_state.lock());
|
release_mouse(&mut *subclass_input.window_state.lock());
|
||||||
|
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Released,
|
state: Released,
|
||||||
button: Middle,
|
button: MouseButton::Middle,
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1424,9 +1487,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_XBUTTONDOWN => {
|
winuser::WM_XBUTTONDOWN => {
|
||||||
use crate::event::{
|
use crate::event::{ElementState::Pressed, WindowEvent::MouseInput};
|
||||||
ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput,
|
|
||||||
};
|
|
||||||
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
|
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
|
||||||
|
|
||||||
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||||
@@ -1434,9 +1495,8 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Pressed,
|
state: Pressed,
|
||||||
button: Other(xbutton as u8),
|
button: MouseButton::Other(xbutton as u8),
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1444,9 +1504,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_XBUTTONUP => {
|
winuser::WM_XBUTTONUP => {
|
||||||
use crate::event::{
|
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
|
||||||
ElementState::Released, MouseButton::Other, WindowEvent::MouseInput,
|
|
||||||
};
|
|
||||||
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
|
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
|
||||||
|
|
||||||
release_mouse(&mut *subclass_input.window_state.lock());
|
release_mouse(&mut *subclass_input.window_state.lock());
|
||||||
@@ -1454,129 +1512,14 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: MouseInput {
|
event: MouseInput {
|
||||||
device_id: DEVICE_ID,
|
|
||||||
state: Released,
|
state: Released,
|
||||||
button: Other(xbutton as u8),
|
button: MouseButton::Other(xbutton as u8),
|
||||||
modifiers: event::get_key_mods(),
|
modifiers: event::get_key_mods(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
winuser::WM_INPUT_DEVICE_CHANGE => {
|
|
||||||
let event = match wparam as _ {
|
|
||||||
winuser::GIDC_ARRIVAL => DeviceEvent::Added,
|
|
||||||
winuser::GIDC_REMOVAL => DeviceEvent::Removed,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
subclass_input.send_event(Event::DeviceEvent {
|
|
||||||
device_id: wrap_device_id(lparam as _),
|
|
||||||
event,
|
|
||||||
});
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
winuser::WM_INPUT => {
|
|
||||||
use crate::event::{
|
|
||||||
DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel},
|
|
||||||
ElementState::{Pressed, Released},
|
|
||||||
MouseScrollDelta::LineDelta,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(data) = get_raw_input_data(lparam as _) {
|
|
||||||
let device_id = wrap_device_id(data.header.hDevice as _);
|
|
||||||
|
|
||||||
if data.header.dwType == winuser::RIM_TYPEMOUSE {
|
|
||||||
let mouse = data.data.mouse();
|
|
||||||
|
|
||||||
if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) {
|
|
||||||
let x = mouse.lLastX as f64;
|
|
||||||
let y = mouse.lLastY as f64;
|
|
||||||
|
|
||||||
if x != 0.0 {
|
|
||||||
subclass_input.send_event(Event::DeviceEvent {
|
|
||||||
device_id,
|
|
||||||
event: Motion { axis: 0, value: x },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if y != 0.0 {
|
|
||||||
subclass_input.send_event(Event::DeviceEvent {
|
|
||||||
device_id,
|
|
||||||
event: Motion { axis: 1, value: y },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if x != 0.0 || y != 0.0 {
|
|
||||||
subclass_input.send_event(Event::DeviceEvent {
|
|
||||||
device_id,
|
|
||||||
event: MouseMotion { delta: (x, y) },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) {
|
|
||||||
let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA;
|
|
||||||
subclass_input.send_event(Event::DeviceEvent {
|
|
||||||
device_id,
|
|
||||||
event: MouseWheel {
|
|
||||||
delta: LineDelta(0.0, delta as f32),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let button_state = get_raw_mouse_button_state(mouse.usButtonFlags);
|
|
||||||
// Left, middle, and right, respectively.
|
|
||||||
for (index, state) in button_state.iter().enumerate() {
|
|
||||||
if let Some(state) = *state {
|
|
||||||
// This gives us consistency with X11, since there doesn't
|
|
||||||
// seem to be anything else reasonable to do for a mouse
|
|
||||||
// button ID.
|
|
||||||
let button = (index + 1) as _;
|
|
||||||
subclass_input.send_event(Event::DeviceEvent {
|
|
||||||
device_id,
|
|
||||||
event: Button { button, state },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if data.header.dwType == winuser::RIM_TYPEKEYBOARD {
|
|
||||||
let keyboard = data.data.keyboard();
|
|
||||||
|
|
||||||
let pressed = keyboard.Message == winuser::WM_KEYDOWN
|
|
||||||
|| keyboard.Message == winuser::WM_SYSKEYDOWN;
|
|
||||||
let released = keyboard.Message == winuser::WM_KEYUP
|
|
||||||
|| keyboard.Message == winuser::WM_SYSKEYUP;
|
|
||||||
|
|
||||||
if pressed || released {
|
|
||||||
let state = if pressed { Pressed } else { Released };
|
|
||||||
|
|
||||||
let scancode = keyboard.MakeCode as _;
|
|
||||||
let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _)
|
|
||||||
| util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _);
|
|
||||||
if let Some((vkey, scancode)) =
|
|
||||||
handle_extended_keys(keyboard.VKey as _, scancode, extended)
|
|
||||||
{
|
|
||||||
let virtual_keycode = vkey_to_winit_vkey(vkey);
|
|
||||||
|
|
||||||
subclass_input.send_event(Event::DeviceEvent {
|
|
||||||
device_id,
|
|
||||||
event: Key(KeyboardInput {
|
|
||||||
scancode,
|
|
||||||
state,
|
|
||||||
virtual_keycode,
|
|
||||||
modifiers: event::get_key_mods(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
|
||||||
}
|
|
||||||
|
|
||||||
winuser::WM_TOUCH => {
|
winuser::WM_TOUCH => {
|
||||||
let pcount = LOWORD(wparam as DWORD) as usize;
|
let pcount = LOWORD(wparam as DWORD) as usize;
|
||||||
let mut inputs = Vec::with_capacity(pcount);
|
let mut inputs = Vec::with_capacity(pcount);
|
||||||
@@ -1618,7 +1561,6 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
location,
|
location,
|
||||||
force: None, // WM_TOUCH doesn't support pressure information
|
force: None, // WM_TOUCH doesn't support pressure information
|
||||||
id: input.dwID as u64,
|
id: input.dwID as u64,
|
||||||
device_id: DEVICE_ID,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1758,7 +1700,6 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
location,
|
location,
|
||||||
force,
|
force,
|
||||||
id: pointer_info.pointerId as u64,
|
id: pointer_info.pointerId as u64,
|
||||||
device_id: DEVICE_ID,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1972,7 +1913,7 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
let in_modal_loop = {
|
let in_modal_loop = {
|
||||||
let runner = subclass_input.event_loop_runner.runner.borrow_mut();
|
let runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
|
||||||
if let Some(ref runner) = *runner {
|
if let Some(ref runner) = *runner {
|
||||||
runner.in_modal_loop
|
runner.in_modal_loop
|
||||||
} else {
|
} else {
|
||||||
@@ -2006,7 +1947,7 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
|
let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
|
||||||
if let Some(ref mut runner) = *runner {
|
if let Some(ref mut runner) = *runner {
|
||||||
runner.events_cleared();
|
runner.events_cleared();
|
||||||
match runner.control_flow {
|
match runner.control_flow {
|
||||||
@@ -2026,6 +1967,230 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
|||||||
}
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
winuser::WM_INPUT_DEVICE_CHANGE => {
|
||||||
|
use super::raw_input::RawDeviceInfo;
|
||||||
|
|
||||||
|
let handle = lparam as HANDLE;
|
||||||
|
|
||||||
|
match wparam as _ {
|
||||||
|
winuser::GIDC_ARRIVAL => {
|
||||||
|
if let Some(handle_info) = raw_input::get_raw_input_device_info(handle) {
|
||||||
|
let device: DeviceId;
|
||||||
|
let event: Event<T>;
|
||||||
|
|
||||||
|
match handle_info {
|
||||||
|
RawDeviceInfo::Mouse(_) => {
|
||||||
|
let mouse_id = MouseId(handle);
|
||||||
|
device = DeviceId::Mouse(mouse_id);
|
||||||
|
event = Event::MouseEvent(mouse_id.into(), MouseEvent::Added);
|
||||||
|
}
|
||||||
|
RawDeviceInfo::Keyboard(_) => {
|
||||||
|
let keyboard_id = KeyboardId(handle);
|
||||||
|
device = DeviceId::Keyboard(keyboard_id);
|
||||||
|
event =
|
||||||
|
Event::KeyboardEvent(keyboard_id.into(), KeyboardEvent::Added);
|
||||||
|
}
|
||||||
|
RawDeviceInfo::Hid(_) => match Gamepad::new(handle) {
|
||||||
|
Some(gamepad) => {
|
||||||
|
let gamepad_handle = GamepadHandle {
|
||||||
|
handle,
|
||||||
|
shared_data: gamepad.shared_data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
device = DeviceId::Gamepad(gamepad_handle.clone(), gamepad);
|
||||||
|
event = Event::GamepadEvent(
|
||||||
|
gamepad_handle.into(),
|
||||||
|
GamepadEvent::Added,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let hid_id = HidId(handle);
|
||||||
|
device = DeviceId::Hid(hid_id.into());
|
||||||
|
event = Event::HidEvent(hid_id.into(), HidEvent::Added);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
subclass_input
|
||||||
|
.shared_data
|
||||||
|
.active_device_ids
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(handle, device);
|
||||||
|
subclass_input.send_event(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
winuser::GIDC_REMOVAL => {
|
||||||
|
let removed_device = subclass_input
|
||||||
|
.shared_data
|
||||||
|
.active_device_ids
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&handle);
|
||||||
|
if let Some(device_id) = removed_device {
|
||||||
|
let event = match device_id {
|
||||||
|
DeviceId::Mouse(mouse_id) => {
|
||||||
|
Event::MouseEvent(mouse_id.into(), MouseEvent::Removed)
|
||||||
|
}
|
||||||
|
DeviceId::Keyboard(keyboard_id) => {
|
||||||
|
Event::KeyboardEvent(keyboard_id.into(), KeyboardEvent::Removed)
|
||||||
|
}
|
||||||
|
DeviceId::Hid(hid_id) => {
|
||||||
|
Event::HidEvent(hid_id.into(), HidEvent::Removed)
|
||||||
|
}
|
||||||
|
DeviceId::Gamepad(gamepad_handle, _) => {
|
||||||
|
Event::GamepadEvent(gamepad_handle.into(), GamepadEvent::Removed)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
subclass_input.send_event(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
winuser::WM_INPUT => {
|
||||||
|
use crate::event::ElementState::{Pressed, Released};
|
||||||
|
|
||||||
|
match get_raw_input_data(lparam as _) {
|
||||||
|
Some(RawInputData::Mouse {
|
||||||
|
device_handle,
|
||||||
|
raw_mouse,
|
||||||
|
}) => {
|
||||||
|
let mouse_handle = MouseId(device_handle).into();
|
||||||
|
|
||||||
|
if util::has_flag(raw_mouse.usFlags, winuser::MOUSE_MOVE_ABSOLUTE) {
|
||||||
|
let x = raw_mouse.lLastX as f64;
|
||||||
|
let y = raw_mouse.lLastY as f64;
|
||||||
|
|
||||||
|
if x != 0.0 || y != 0.0 {
|
||||||
|
subclass_input.send_event(Event::MouseEvent(
|
||||||
|
mouse_handle,
|
||||||
|
MouseEvent::MovedAbsolute(PhysicalPosition { x, y }),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if util::has_flag(raw_mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) {
|
||||||
|
let x = raw_mouse.lLastX as f64;
|
||||||
|
let y = raw_mouse.lLastY as f64;
|
||||||
|
|
||||||
|
if x != 0.0 || y != 0.0 {
|
||||||
|
subclass_input.send_event(Event::MouseEvent(
|
||||||
|
mouse_handle,
|
||||||
|
MouseEvent::MovedRelative(x, y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if util::has_flag(raw_mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) {
|
||||||
|
// TODO: HOW IS RAW WHEEL DELTA HANDLED ON OTHER PLATFORMS?
|
||||||
|
let delta = raw_mouse.usButtonData as SHORT / winuser::WHEEL_DELTA;
|
||||||
|
subclass_input.send_event(Event::MouseEvent(
|
||||||
|
mouse_handle,
|
||||||
|
MouseEvent::Wheel(0.0, delta as f64),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Check if there's horizontal wheel movement.
|
||||||
|
if util::has_flag(raw_mouse.usButtonFlags, 0x0800) {
|
||||||
|
// TODO: HOW IS RAW WHEEL DELTA HANDLED ON OTHER PLATFORMS?
|
||||||
|
let delta = raw_mouse.usButtonData as SHORT / winuser::WHEEL_DELTA;
|
||||||
|
subclass_input.send_event(Event::MouseEvent(
|
||||||
|
mouse_handle,
|
||||||
|
MouseEvent::Wheel(delta as f64, 0.0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let button_state = get_raw_mouse_button_state(raw_mouse.usButtonFlags);
|
||||||
|
for (index, state) in button_state
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, state)| state.map(|s| (i, s)))
|
||||||
|
{
|
||||||
|
subclass_input.send_event(Event::MouseEvent(
|
||||||
|
mouse_handle,
|
||||||
|
MouseEvent::Button {
|
||||||
|
state,
|
||||||
|
button: match index {
|
||||||
|
0 => MouseButton::Left,
|
||||||
|
1 => MouseButton::Middle,
|
||||||
|
2 => MouseButton::Right,
|
||||||
|
_ => MouseButton::Other(index as u8 - 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(RawInputData::Keyboard {
|
||||||
|
device_handle,
|
||||||
|
raw_keyboard,
|
||||||
|
}) => {
|
||||||
|
let keyboard_id = KeyboardId(device_handle).into();
|
||||||
|
|
||||||
|
let pressed = raw_keyboard.Message == winuser::WM_KEYDOWN
|
||||||
|
|| raw_keyboard.Message == winuser::WM_SYSKEYDOWN;
|
||||||
|
let released = raw_keyboard.Message == winuser::WM_KEYUP
|
||||||
|
|| raw_keyboard.Message == winuser::WM_SYSKEYUP;
|
||||||
|
|
||||||
|
if pressed || released {
|
||||||
|
let state = if pressed { Pressed } else { Released };
|
||||||
|
|
||||||
|
let scancode = raw_keyboard.MakeCode as _;
|
||||||
|
let extended = util::has_flag(raw_keyboard.Flags, winuser::RI_KEY_E0 as _)
|
||||||
|
| util::has_flag(raw_keyboard.Flags, winuser::RI_KEY_E1 as _);
|
||||||
|
if let Some((vkey, scancode)) =
|
||||||
|
handle_extended_keys(raw_keyboard.VKey as _, scancode, extended)
|
||||||
|
{
|
||||||
|
let virtual_keycode = vkey_to_winit_vkey(vkey);
|
||||||
|
|
||||||
|
subclass_input.send_event(Event::KeyboardEvent(
|
||||||
|
keyboard_id,
|
||||||
|
KeyboardEvent::Input(KeyboardInput {
|
||||||
|
scancode,
|
||||||
|
state,
|
||||||
|
virtual_keycode,
|
||||||
|
modifiers: event::get_key_mods(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(RawInputData::Hid {
|
||||||
|
device_handle,
|
||||||
|
mut raw_hid,
|
||||||
|
}) => {
|
||||||
|
let mut gamepad_handle_opt: Option<crate::event::device::GamepadHandle> = 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 msg == *USER_EVENT_MSG_ID => {
|
||||||
if let Ok(event) = subclass_input.user_event_receiver.recv() {
|
if let Ok(event) = subclass_input.user_event_receiver.recv() {
|
||||||
subclass_input.send_event(Event::UserEvent(event));
|
subclass_input.send_event(Event::UserEvent(event));
|
||||||
|
|||||||
89
src/platform_impl/windows/gamepad.rs
Normal file
89
src/platform_impl/windows/gamepad.rs
Normal file
@@ -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<XInputGamepadShared>),
|
||||||
|
Dummy,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Gamepad {
|
||||||
|
handle: HANDLE,
|
||||||
|
backend: GamepadType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gamepad {
|
||||||
|
pub fn new(handle: HANDLE) -> Option<Self> {
|
||||||
|
// 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<GamepadEvent> {
|
||||||
|
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<u8> {
|
||||||
|
match self {
|
||||||
|
GamepadShared::Raw(_) | GamepadShared::Dummy => None,
|
||||||
|
GamepadShared::XInput(ref data) => data.upgrade().map(|r| r.port()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn battery_level(&self) -> Option<BatteryLevel> {
|
||||||
|
match self {
|
||||||
|
GamepadShared::Raw(_) | GamepadShared::Dummy => None,
|
||||||
|
GamepadShared::XInput(ref data) => data.upgrade().and_then(|r| r.battery_level()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,35 @@
|
|||||||
#![cfg(target_os = "windows")]
|
#![cfg(target_os = "windows")]
|
||||||
|
|
||||||
use winapi::{self, shared::windef::HWND};
|
#[macro_use]
|
||||||
|
mod util;
|
||||||
|
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;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
|
||||||
|
fmt,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
use winapi::{self, shared::windef::HWND, um::winnt::HANDLE};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||||
|
gamepad::GamepadShared,
|
||||||
monitor::{MonitorHandle, VideoMode},
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::Window,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{event::DeviceId as RootDeviceId, window::Icon};
|
use crate::{event::device, window::Icon};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
@@ -26,34 +47,6 @@ pub struct Cursor(pub *const winapi::ctypes::wchar_t);
|
|||||||
unsafe impl Send for Cursor {}
|
unsafe impl Send for Cursor {}
|
||||||
unsafe impl Sync for Cursor {}
|
unsafe impl Sync for Cursor {}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId(u32);
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
DeviceId(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub fn persistent_identifier(&self) -> Option<String> {
|
|
||||||
if self.0 != 0 {
|
|
||||||
raw_input::get_raw_input_device_name(self.0 as _)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
|
||||||
const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0));
|
|
||||||
|
|
||||||
fn wrap_device_id(id: u32) -> RootDeviceId {
|
|
||||||
RootDeviceId(DeviceId(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type OsError = std::io::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct WindowId(HWND);
|
pub struct WindowId(HWND);
|
||||||
unsafe impl Send for WindowId {}
|
unsafe impl Send for WindowId {}
|
||||||
@@ -61,20 +54,142 @@ unsafe impl Sync for WindowId {}
|
|||||||
|
|
||||||
impl WindowId {
|
impl WindowId {
|
||||||
pub unsafe fn dummy() -> Self {
|
pub unsafe fn dummy() -> Self {
|
||||||
use std::ptr::null_mut;
|
WindowId(ptr::null_mut())
|
||||||
|
|
||||||
WindowId(null_mut())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_use]
|
macro_rules! device_id {
|
||||||
mod util;
|
($name:ident, $enumerate:ident) => {
|
||||||
mod dpi;
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
mod drop_handler;
|
pub(crate) struct $name(HANDLE);
|
||||||
mod event;
|
|
||||||
mod event_loop;
|
unsafe impl Send for $name {}
|
||||||
mod icon;
|
unsafe impl Sync for $name {}
|
||||||
mod monitor;
|
|
||||||
mod raw_input;
|
impl $name {
|
||||||
mod window;
|
pub unsafe fn dummy() -> Self {
|
||||||
mod window_state;
|
Self(ptr::null_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn persistent_identifier(&self) -> Option<String> {
|
||||||
|
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<T>,
|
||||||
|
) -> impl 'a + Iterator<Item = device::$name> {
|
||||||
|
event_loop.$enumerate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$name> for device::$name {
|
||||||
|
fn from(platform_id: $name) -> Self {
|
||||||
|
Self(platform_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
device_id!(MouseId, mouses);
|
||||||
|
device_id!(KeyboardId, keyboards);
|
||||||
|
device_id!(HidId, hids);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct GamepadHandle {
|
||||||
|
handle: HANDLE,
|
||||||
|
shared_data: GamepadShared,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type OsError = std::io::Error;
|
||||||
|
|
||||||
|
unsafe impl Send for GamepadHandle where GamepadShared: Send {}
|
||||||
|
unsafe impl Sync for GamepadHandle where GamepadShared: Sync {}
|
||||||
|
|
||||||
|
impl GamepadHandle {
|
||||||
|
pub unsafe fn dummy() -> Self {
|
||||||
|
Self {
|
||||||
|
handle: ptr::null_mut(),
|
||||||
|
shared_data: GamepadShared::Dummy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn persistent_identifier(&self) -> Option<String> {
|
||||||
|
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<u8> {
|
||||||
|
self.shared_data.port()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn battery_level(&self) -> Option<device::BatteryLevel> {
|
||||||
|
self.shared_data.battery_level()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enumerate<'a, T>(
|
||||||
|
event_loop: &'a EventLoop<T>,
|
||||||
|
) -> impl 'a + Iterator<Item = device::GamepadHandle> {
|
||||||
|
event_loop.gamepads()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GamepadHandle> 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<Ordering> {
|
||||||
|
self.handle.partial_cmp(&other.handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for GamepadHandle {
|
||||||
|
#[inline(always)]
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.handle.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
cmp::max,
|
||||||
|
fmt,
|
||||||
mem::{self, size_of},
|
mem::{self, size_of},
|
||||||
ptr,
|
ptr, slice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use winapi::{
|
use winapi::{
|
||||||
ctypes::wchar_t,
|
ctypes::wchar_t,
|
||||||
shared::{
|
shared::{
|
||||||
hidusage::{HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC},
|
hidpi::{
|
||||||
minwindef::{TRUE, UINT, USHORT},
|
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,
|
windef::HWND,
|
||||||
},
|
},
|
||||||
um::{
|
um::{
|
||||||
winnt::HANDLE,
|
winnt::{HANDLE, PCHAR},
|
||||||
winuser::{
|
winuser::{
|
||||||
self, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER,
|
self, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER,
|
||||||
RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO,
|
RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RIDI_PREPARSEDDATA,
|
||||||
RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT,
|
RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE,
|
||||||
RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
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)]
|
#[allow(dead_code)]
|
||||||
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
||||||
@@ -52,7 +68,6 @@ pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
|||||||
Some(buffer)
|
Some(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum RawDeviceInfo {
|
pub enum RawDeviceInfo {
|
||||||
Mouse(RID_DEVICE_INFO_MOUSE),
|
Mouse(RID_DEVICE_INFO_MOUSE),
|
||||||
Keyboard(RID_DEVICE_INFO_KEYBOARD),
|
Keyboard(RID_DEVICE_INFO_KEYBOARD),
|
||||||
@@ -72,28 +87,27 @@ impl From<RID_DEVICE_INFO> for RawDeviceInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
|
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
|
||||||
let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() };
|
let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() };
|
||||||
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
|
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
|
||||||
|
|
||||||
info.cbSize = info_size;
|
info.cbSize = info_size;
|
||||||
|
|
||||||
let mut minimum_size = 0;
|
let mut data_size = info_size;
|
||||||
let status = unsafe {
|
let status = unsafe {
|
||||||
winuser::GetRawInputDeviceInfoW(
|
winuser::GetRawInputDeviceInfoW(
|
||||||
handle,
|
handle,
|
||||||
RIDI_DEVICEINFO,
|
RIDI_DEVICEINFO,
|
||||||
&mut info as *mut _ as _,
|
&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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert_eq!(info_size, status);
|
debug_assert_eq!(info_size, status as _);
|
||||||
|
|
||||||
Some(info.into())
|
Some(info.into())
|
||||||
}
|
}
|
||||||
@@ -130,6 +144,43 @@ pub fn get_raw_input_device_name(handle: HANDLE) -> Option<String> {
|
|||||||
Some(util::wchar_to_string(&name))
|
Some(util::wchar_to_string(&name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_raw_input_pre_parse_info(handle: HANDLE) -> Option<Vec<u8>> {
|
||||||
|
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<u8> = Vec::with_capacity(minimum_size as _);
|
||||||
|
|
||||||
|
let status = unsafe {
|
||||||
|
winuser::GetRawInputDeviceInfoW(
|
||||||
|
handle,
|
||||||
|
RIDI_PREPARSEDDATA,
|
||||||
|
buf.as_ptr() as _,
|
||||||
|
&mut minimum_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if status == UINT::max_value() || status == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert_eq!(minimum_size, status);
|
||||||
|
|
||||||
|
unsafe { buf.set_len(minimum_size as _) };
|
||||||
|
|
||||||
|
Some(buf)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
|
pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
|
||||||
let device_size = size_of::<RAWINPUTDEVICE>() as UINT;
|
let device_size = size_of::<RAWINPUTDEVICE>() as UINT;
|
||||||
|
|
||||||
@@ -140,12 +191,12 @@ pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
|
|||||||
success == TRUE
|
success == TRUE
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool {
|
pub fn register_for_raw_input(window_handle: HWND) -> bool {
|
||||||
// RIDEV_DEVNOTIFY: receive hotplug events
|
// `RIDEV_DEVNOTIFY`: receive hotplug events
|
||||||
// RIDEV_INPUTSINK: receive events even if we're not in the foreground
|
// `RIDEV_INPUTSINK`: receive events even if we're not in the foreground
|
||||||
let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
|
let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
|
||||||
|
|
||||||
let devices: [RAWINPUTDEVICE; 2] = [
|
let devices: [RAWINPUTDEVICE; 5] = [
|
||||||
RAWINPUTDEVICE {
|
RAWINPUTDEVICE {
|
||||||
usUsagePage: HID_USAGE_PAGE_GENERIC,
|
usUsagePage: HID_USAGE_PAGE_GENERIC,
|
||||||
usUsage: HID_USAGE_GENERIC_MOUSE,
|
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,
|
dwFlags: flags,
|
||||||
hwndTarget: window_handle,
|
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)
|
register_raw_input_devices(&devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RAWINPUT> {
|
pub enum RawInputData {
|
||||||
let mut data: RAWINPUT = unsafe { mem::zeroed() };
|
Mouse {
|
||||||
let mut data_size = size_of::<RAWINPUT>() as UINT;
|
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<RawInputData> {
|
||||||
|
let mut data_size = 0;
|
||||||
let header_size = size_of::<RAWINPUTHEADER>() as UINT;
|
let header_size = size_of::<RAWINPUTHEADER>() 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(
|
winuser::GetRawInputData(
|
||||||
handle,
|
handle,
|
||||||
RID_INPUT,
|
RID_INPUT,
|
||||||
&mut data as *mut _ as _,
|
ptr::null_mut(),
|
||||||
&mut data_size,
|
&mut data_size,
|
||||||
header_size,
|
header_size,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if status == UINT::max_value() || status == 0 {
|
let (status, data): (INT, RawInputData);
|
||||||
|
|
||||||
|
if data_size <= size_of::<RAWINPUT>() 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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +406,7 @@ fn button_flags_to_element_state(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>; 3] {
|
pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>; 5] {
|
||||||
[
|
[
|
||||||
button_flags_to_element_state(
|
button_flags_to_element_state(
|
||||||
button_flags,
|
button_flags,
|
||||||
@@ -217,5 +423,264 @@ pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>
|
|||||||
winuser::RI_MOUSE_RIGHT_BUTTON_DOWN,
|
winuser::RI_MOUSE_RIGHT_BUTTON_DOWN,
|
||||||
winuser::RI_MOUSE_RIGHT_BUTTON_UP,
|
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<GamepadAxis>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<GamepadAxis>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<u8>,
|
||||||
|
button_count: usize,
|
||||||
|
pub button_state: Vec<bool>,
|
||||||
|
pub prev_button_state: Vec<bool>,
|
||||||
|
axis_count: usize,
|
||||||
|
pub axis_state: Vec<Axis>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<Self> {
|
||||||
|
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<Item = GamepadEvent> {
|
||||||
|
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<Item = GamepadEvent> {
|
||||||
|
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<GamepadEvent> {
|
||||||
|
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.
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io, mem,
|
io, mem,
|
||||||
ops::BitAnd,
|
|
||||||
os::raw::c_void,
|
os::raw::c_void,
|
||||||
ptr, slice,
|
ptr, slice,
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
@@ -21,6 +20,8 @@ use winapi::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use crate::util::*;
|
||||||
|
|
||||||
// Helper function to dynamically load function pointer.
|
// Helper function to dynamically load function pointer.
|
||||||
// `library` and `function` must be zero-terminated.
|
// `library` and `function` must be zero-terminated.
|
||||||
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
||||||
@@ -51,13 +52,6 @@ macro_rules! get_function {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
|
||||||
where
|
|
||||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
|
||||||
{
|
|
||||||
bitset & flag == flag
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
|
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
|
||||||
String::from_utf16_lossy(wchar).to_string()
|
String::from_utf16_lossy(wchar).to_string()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ use crate::{
|
|||||||
REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID,
|
REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID,
|
||||||
},
|
},
|
||||||
icon::{self, IconType, WinIcon},
|
icon::{self, IconType, WinIcon},
|
||||||
monitor,
|
monitor, util,
|
||||||
raw_input::register_all_mice_and_keyboards_for_raw_input,
|
|
||||||
util,
|
|
||||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||||
PlatformSpecificWindowBuilderAttributes, WindowId,
|
PlatformSpecificWindowBuilderAttributes, WindowId,
|
||||||
},
|
},
|
||||||
@@ -86,12 +84,12 @@ impl Window {
|
|||||||
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
|
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_drop_runner = event_loop.runner_shared.clone();
|
let shared_data = event_loop.shared_data.clone();
|
||||||
let file_drop_handler = FileDropHandler::new(
|
let file_drop_handler = FileDropHandler::new(
|
||||||
win.window.0,
|
win.window.0,
|
||||||
Box::new(move |event| {
|
Box::new(move |event| {
|
||||||
if let Ok(e) = event.map_nonuser_event() {
|
if let Ok(e) = event.map_nonuser_event() {
|
||||||
file_drop_runner.send_event(e)
|
shared_data.runner_shared.send_event(e)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -107,7 +105,7 @@ impl Window {
|
|||||||
|
|
||||||
let subclass_input = event_loop::SubclassInput {
|
let subclass_input = event_loop::SubclassInput {
|
||||||
window_state: win.window_state.clone(),
|
window_state: win.window_state.clone(),
|
||||||
event_loop_runner: event_loop.runner_shared.clone(),
|
shared_data: event_loop.shared_data.clone(),
|
||||||
file_drop_handler,
|
file_drop_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -839,9 +837,6 @@ unsafe fn init<T: 'static>(
|
|||||||
WindowWrapper(handle)
|
WindowWrapper(handle)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up raw input
|
|
||||||
register_all_mice_and_keyboards_for_raw_input(real_window.0);
|
|
||||||
|
|
||||||
// Register for touch events if applicable
|
// Register for touch events if applicable
|
||||||
{
|
{
|
||||||
let digitizer = winuser::GetSystemMetrics(winuser::SM_DIGITIZER) as u32;
|
let digitizer = winuser::GetSystemMetrics(winuser::SM_DIGITIZER) as u32;
|
||||||
|
|||||||
334
src/platform_impl/windows/xinput.rs
Normal file
334
src/platform_impl/windows/xinput.rs
Normal file
@@ -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> = 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<DWORD> {
|
||||||
|
// 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<XInputGamepadShared>,
|
||||||
|
prev_state: Option<XInputState>,
|
||||||
|
state: Option<XInputState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct XInputGamepadShared {
|
||||||
|
port: DWORD,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XInputGamepad {
|
||||||
|
pub fn new(port: DWORD) -> Option<Self> {
|
||||||
|
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<GamepadEvent>,
|
||||||
|
value: bool,
|
||||||
|
prev_value: Option<bool>,
|
||||||
|
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<GamepadEvent>) {
|
||||||
|
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<GamepadEvent>,
|
||||||
|
value: u8,
|
||||||
|
prev_value: Option<u8>,
|
||||||
|
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<GamepadEvent>,
|
||||||
|
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<GamepadEvent>) {
|
||||||
|
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<GamepadEvent> {
|
||||||
|
let mut events = Vec::new();
|
||||||
|
self.get_changed_axes(&mut events);
|
||||||
|
self.get_changed_buttons(&mut events);
|
||||||
|
events
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shared_data(&self) -> Weak<XInputGamepadShared> {
|
||||||
|
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<BatteryLevel> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/util.rs
Normal file
29
src/util.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use std::ops::BitAnd;
|
||||||
|
|
||||||
|
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||||
|
where
|
||||||
|
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||||
|
{
|
||||||
|
bitset & flag == flag
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
|
||||||
|
if value > max {
|
||||||
|
max
|
||||||
|
} else if value < min {
|
||||||
|
min
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_asymmetric(value: f64, min: f64, max: f64) -> f64 {
|
||||||
|
let range = max - min;
|
||||||
|
let translated = value - min;
|
||||||
|
let scaled = translated / range;
|
||||||
|
clamp(scaled, 0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_symmetric(value: f64, min: f64, max: f64) -> f64 {
|
||||||
|
(2.0 * normalize_asymmetric(value, min, max)) - 1.0
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ fn window_send() {
|
|||||||
fn ids_send() {
|
fn ids_send() {
|
||||||
// ensures that the various `..Id` types implement `Send`
|
// ensures that the various `..Id` types implement `Send`
|
||||||
needs_send::<winit::window::WindowId>();
|
needs_send::<winit::window::WindowId>();
|
||||||
needs_send::<winit::event::DeviceId>();
|
needs_send::<winit::event::device::MouseId>();
|
||||||
|
needs_send::<winit::event::device::KeyboardId>();
|
||||||
|
needs_send::<winit::event::device::GamepadHandle>();
|
||||||
needs_send::<winit::monitor::MonitorHandle>();
|
needs_send::<winit::monitor::MonitorHandle>();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user