Skip to content

Commit afcbcfa

Browse files
committed
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
1 parent b1b5aef commit afcbcfa

24 files changed

+1996
-423
lines changed

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@
4545
- On Windows, fix initial dimensions of a fullscreen window.
4646
- On Windows, Fix transparent borderless windows rendering wrong.
4747

48+
- Improve event API documentation.
49+
- Overhaul device event API:
50+
- **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`.
51+
- **Breaking**: Remove `DeviceEvent::Text` variant.
52+
- **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`.
53+
- **Breaking**: Removed device IDs from `WindowEvent` variants.
54+
- Add `enumerate` function on device ID types to list all attached devices of that type.
55+
- Add `is_connected` function on device ID types check if the specified device is still available.
56+
- **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`.
57+
- Add `handle` function to retrieve the underlying `HANDLE`.
58+
- On Windows, fix duplicate device events getting sent if Winit managed multiple windows.
59+
- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases.
60+
- Added gamepad support on Windows via raw input and XInput.
61+
4862
# Version 0.19.1 (2019-04-08)
4963

5064
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ objc = "0.2.3"
4141

4242
[target.'cfg(target_os = "windows")'.dependencies]
4343
bitflags = "1"
44+
rusty-xinput = "1.0"
4445

4546
[target.'cfg(target_os = "windows")'.dependencies.winapi]
4647
version = "0.3.6"
@@ -49,6 +50,7 @@ features = [
4950
"commctrl",
5051
"dwmapi",
5152
"errhandlingapi",
53+
"hidpi",
5254
"hidusage",
5355
"libloaderapi",
5456
"objbase",
@@ -64,6 +66,7 @@ features = [
6466
"wingdi",
6567
"winnt",
6668
"winuser",
69+
"xinput",
6770
]
6871

6972
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]

examples/cursor.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ fn main() {
1414

1515
event_loop.run(move |event, _, control_flow| {
1616
match event {
17-
Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => {
17+
Event::WindowEvent { event: WindowEvent::KeyboardInput(KeyboardInput { state: ElementState::Pressed, .. }), .. } => {
1818
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
1919
window.set_cursor_icon(CURSORS[cursor_idx]);
2020
if cursor_idx < CURSORS.len() - 1 {

examples/cursor_grab.rs

+5-8
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,12 @@ fn main() {
1717
if let Event::WindowEvent { event, .. } = event {
1818
match event {
1919
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
20-
WindowEvent::KeyboardInput {
21-
input: KeyboardInput {
22-
state: ElementState::Released,
23-
virtual_keycode: Some(key),
24-
modifiers,
25-
..
26-
},
20+
WindowEvent::KeyboardInput(KeyboardInput {
21+
state: ElementState::Released,
22+
virtual_keycode: Some(key),
23+
modifiers,
2724
..
28-
} => {
25+
}) => {
2926
use winit::event::VirtualKeyCode::*;
3027
match key {
3128
Escape => *control_flow = ControlFlow::Exit,

examples/fullscreen.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,11 @@ fn main() {
5656
match event {
5757
Event::WindowEvent { event, .. } => match event {
5858
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
59-
WindowEvent::KeyboardInput {
60-
input:
61-
KeyboardInput {
62-
virtual_keycode: Some(virtual_code),
63-
state,
64-
..
65-
},
59+
WindowEvent::KeyboardInput(KeyboardInput {
60+
virtual_keycode: Some(virtual_code),
61+
state,
6662
..
67-
} => match (virtual_code, state) {
63+
}) => match (virtual_code, state) {
6864
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
6965
(VirtualKeyCode::F, ElementState::Pressed) => {
7066
#[cfg(target_os = "macos")]

examples/gamepad.rs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
extern crate winit;
2+
use winit::window::WindowBuilder;
3+
use winit::event::{Event, WindowEvent};
4+
use winit::event::device::{GamepadEvent, GamepadHandle};
5+
use winit::event_loop::{EventLoop, ControlFlow};
6+
7+
fn main() {
8+
let event_loop = EventLoop::new();
9+
10+
let _window = WindowBuilder::new()
11+
.with_title("The world's worst video game")
12+
.build(&event_loop)
13+
.unwrap();
14+
15+
println!("enumerating gamepads:");
16+
for gamepad in GamepadHandle::enumerate(&event_loop) {
17+
println!(" gamepad={:?}\tport={:?}\tbattery level={:?}", gamepad, gamepad.port(), gamepad.battery_level());
18+
}
19+
20+
let deadzone = 0.12;
21+
22+
event_loop.run(move |event, _, control_flow| {
23+
match event {
24+
Event::GamepadEvent(gamepad_handle, event) => {
25+
match event {
26+
// Discard any Axis events that has a corresponding Stick event.
27+
GamepadEvent::Axis{stick: true, ..} => (),
28+
29+
// Discard any Stick event that falls inside the stick's deadzone.
30+
GamepadEvent::Stick{x_value, y_value, ..}
31+
if (x_value.powi(2) + y_value.powi(2)).sqrt() < deadzone
32+
=> (),
33+
34+
_ => println!("[{:?}] {:#?}", gamepad_handle, event)
35+
}
36+
},
37+
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit,
38+
_ => ()
39+
}
40+
});
41+
}

examples/gamepad_rumble.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
extern crate winit;
2+
use winit::event_loop::EventLoop;
3+
use std::time::Instant;
4+
5+
#[derive(Debug, Clone)]
6+
enum Rumble {
7+
None,
8+
Left,
9+
Right,
10+
}
11+
12+
fn main() {
13+
let event_loop = EventLoop::new();
14+
15+
// You should generally use `GamepadEvent::Added/Removed` to detect gamepads, as doing that will
16+
// allow you to more easily support gamepad hotswapping. However, we're using `enumerate` here
17+
// because it makes this example more concise.
18+
let gamepads = winit::event::device::GamepadHandle::enumerate(&event_loop).collect::<Vec<_>>();
19+
20+
let rumble_patterns = &[
21+
(0.5, Rumble::None),
22+
(2.0, Rumble::Left),
23+
(0.5, Rumble::None),
24+
(2.0, Rumble::Right),
25+
];
26+
let mut rumble_iter = rumble_patterns.iter().cloned().cycle();
27+
28+
let mut active_pattern = rumble_iter.next().unwrap();
29+
let mut timeout = active_pattern.0;
30+
let mut timeout_start = Instant::now();
31+
32+
event_loop.run(move |_, _, _| {
33+
if timeout <= active_pattern.0 {
34+
let t = (timeout / active_pattern.0) * std::f64::consts::PI;
35+
let intensity = t.sin();
36+
37+
for g in &gamepads {
38+
let result = match active_pattern.1 {
39+
Rumble::Left => g.rumble(intensity, 0.0),
40+
Rumble::Right => g.rumble(0.0, intensity),
41+
Rumble::None => Ok(()),
42+
};
43+
44+
if let Err(e) = result {
45+
println!("Rumble failed: {:?}", e);
46+
}
47+
}
48+
49+
timeout = (Instant::now() - timeout_start).as_millis() as f64 / 1000.0;
50+
} else {
51+
active_pattern = rumble_iter.next().unwrap();
52+
println!("Rumbling {:?} for {:?} seconds", active_pattern.1, active_pattern.0);
53+
54+
timeout = 0.0;
55+
timeout_start = Instant::now();
56+
}
57+
});
58+
}

examples/handling_close.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,11 @@ fn main() {
3838
// closing the window. How to close the window is detailed in the handler for
3939
// the Y key.
4040
}
41-
WindowEvent::KeyboardInput {
42-
input:
43-
KeyboardInput {
44-
virtual_keycode: Some(virtual_code),
45-
state: Released,
46-
..
47-
},
41+
WindowEvent::KeyboardInput(KeyboardInput {
42+
virtual_keycode: Some(virtual_code),
43+
state: Released,
4844
..
49-
} => match virtual_code {
45+
}) => match virtual_code {
5046
Y => {
5147
if close_requested {
5248
// This is where you'll want to do any cleanup you need.

examples/multithreaded.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ fn main() {
2525
thread::spawn(move || {
2626
while let Ok(event) = rx.recv() {
2727
match event {
28-
WindowEvent::KeyboardInput { input: KeyboardInput {
28+
WindowEvent::KeyboardInput (KeyboardInput {
2929
state: ElementState::Released,
3030
virtual_keycode: Some(key),
3131
modifiers,
3232
..
33-
}, .. } => {
33+
}) => {
3434
window.set_title(&format!("{:?}", key));
3535
let state = !modifiers.shift;
3636
use self::VirtualKeyCode::*;
@@ -99,9 +99,9 @@ fn main() {
9999
match event {
100100
WindowEvent::CloseRequested
101101
| WindowEvent::Destroyed
102-
| WindowEvent::KeyboardInput { input: KeyboardInput {
102+
| WindowEvent::KeyboardInput(KeyboardInput {
103103
virtual_keycode: Some(VirtualKeyCode::Escape),
104-
.. }, .. } => {
104+
.. }) => {
105105
window_senders.remove(&window_id);
106106
},
107107
_ => if let Some(tx) = window_senders.get(&window_id) {

examples/multiwindow.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn main() {
2929
*control_flow = ControlFlow::Exit;
3030
}
3131
},
32-
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => {
32+
WindowEvent::KeyboardInput(KeyboardInput { state: ElementState::Pressed, .. }) => {
3333
let window = Window::new(&event_loop).unwrap();
3434
windows.insert(window.id(), window);
3535
},

examples/resizable.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,11 @@ fn main() {
2020
match event {
2121
Event::WindowEvent { event, .. } => match event {
2222
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
23-
WindowEvent::KeyboardInput {
24-
input:
25-
KeyboardInput {
26-
virtual_keycode: Some(VirtualKeyCode::Space),
27-
state: ElementState::Released,
28-
..
29-
},
23+
WindowEvent::KeyboardInput(KeyboardInput {
24+
virtual_keycode: Some(VirtualKeyCode::Space),
25+
state: ElementState::Released,
3026
..
31-
} => {
27+
}) => {
3228
resizable = !resizable;
3329
println!("Resizable: {}", resizable);
3430
window.set_resizable(resizable);

0 commit comments

Comments
 (0)