Compare commits

...

2 Commits

Author SHA1 Message Date
Christophe Massolin
e004bd2bb3 Gamepad device events - Web/WASM (#1414)
Add gamepad support for stdweb and web-sys, as well as web-specific gamepad examples. 

* [web] Fix compilation error from device api

* [wasm] Apply device api changes

* [wasm] Format and cleanup

* [wasm32] Implement gamepad connections

* [wasm] Harmonize

* [Test] Made some tests with wasm-pack

* Quick fix instant non supporting Hash trait

* Fix on_received_character

* [web_sys] Split add_event and add_window_event

* [web] split device implementations

* Update tests/web...still does not work

* [tests/web] do not ignore index.html

* [web/web_sys] split canvas and window

* [tests/web] enable stack trace

* [web] fix borrowmut

* [web_sys] fix gamepad registration

* [web] harmonize naming

* [web_sys] create global emitter

* [web] implement gamepad buttons

* [web] implement gamepad axis

* [web] cleanup

* [web] update test

* [web] move tests/web to examples/web

* [web] axis does produce stick event

* [web] Support Stick event

* [web] implement gamepad to stdweb

* [web] rename examples/web to examples/wasm

* [web/web-sys] Move gamepad_manager from backend

* [web/web_sys] implement EventLoop::gamepads

* [web/web_sys] Drain gamepad events

* [web/stdweb] apply web_sys changes

* [web] update web/examples

* [web] move gamepads code to gamepad_manager

* [web] simplify and optimise

* [web] replace EventCode to GamepadAxis and GamepadButton structs

* [web] reuse gamepad events due to chrome issue

* [web] rumble does not work

* [web/stdweb] try debugging

* [web] fix Chrome gamepad not updated

* [web/stdweb] created an example

* [examples] fix paths

* fix warnings

* [web/examples] update comments

* [web/stdweb] add experimental support to vibrate()

* [web] add CR
2020-03-03 09:56:11 -05:00
Osspial
0729074ce3 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
2019-11-29 16:50:50 -05:00
55 changed files with 3599 additions and 693 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ Cargo.lock
target/
rls/
.vscode/
.cargo/
util/
*~
*.wasm

View File

@@ -150,6 +150,20 @@ and `WindowEvent::HoveredFile`.
- On Windows, fix initial dimensions of a fullscreen window.
- On Windows, Fix transparent borderless windows rendering wrong.
- Improve event API documentation.
- Overhaul device event API:
- **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`.
- **Breaking**: Remove `DeviceEvent::Text` variant.
- **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`.
- **Breaking**: Removed device IDs from `WindowEvent` variants.
- Add `enumerate` function on device ID types to list all attached devices of that type.
- Add `is_connected` function on device ID types check if the specified device is still available.
- **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`.
- Add `handle` function to retrieve the underlying `HANDLE`.
- On Windows, fix duplicate device events getting sent if Winit managed multiple windows.
- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases.
- Added gamepad support on Windows via raw input and XInput.
# Version 0.19.1 (2019-04-08)
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.

View File

@@ -50,6 +50,7 @@ features = ["display_link"]
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
bitflags = "1"
rusty-xinput = "1.0"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6"
@@ -58,6 +59,7 @@ features = [
"commctrl",
"dwmapi",
"errhandlingapi",
"hidpi",
"hidusage",
"libloaderapi",
"objbase",
@@ -73,6 +75,7 @@ features = [
"wingdi",
"winnt",
"winuser",
"xinput",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
@@ -103,9 +106,24 @@ features = [
'KeyboardEvent',
'MouseEvent',
'Node',
'Navigator',
'PointerEvent',
'Window',
'WheelEvent'
'WheelEvent',
'Gamepad',
'GamepadAxisMoveEvent',
'GamepadAxisMoveEventInit',
'GamepadButton',
'GamepadButtonEvent',
'GamepadButtonEventInit',
'GamepadEvent',
'GamepadEventInit',
'GamepadHand',
'GamepadHapticActuator',
'GamepadHapticActuatorType',
'GamepadMappingType',
'GamepadPose',
'GamepadServiceTest'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]

View File

@@ -15,14 +15,10 @@ fn main() {
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
..
},
}),
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);

View File

@@ -17,16 +17,12 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
} => {
}) => {
use winit::event::VirtualKeyCode::*;
match key {
Escape => *control_flow = ControlFlow::Exit,

View File

@@ -35,15 +35,11 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
} => match (virtual_code, state) {
}) => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => {
if window.fullscreen().is_some() {

52
examples/gamepad.rs Normal file
View 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,
_ => (),
}
});
}

View 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();
}
});
}

View File

@@ -41,15 +41,11 @@ fn main() {
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
} => {
}) => {
match virtual_code {
Y => {
if close_requested {

View File

@@ -49,16 +49,12 @@ fn main() {
);
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
} => {
}) => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift;
use VirtualKeyCode::*;
@@ -148,15 +144,11 @@ fn main() {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
| WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
} => {
}) => {
window_senders.remove(&window_id);
}
_ => {

View File

@@ -29,14 +29,10 @@ fn main() {
*control_flow = ControlFlow::Exit;
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
..
} => {
}) => {
let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
}

View File

@@ -21,15 +21,11 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
} => {
}) => {
resizable = !resizable;
println!("Resizable: {}", resizable);
window.set_resizable(resizable);

View File

@@ -0,0 +1,11 @@
[package]
name = "stdweb-gamepad"
version = "0.1.0"
authors = ["furiouzz <christophe.massolin@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
winit = { path = "../../../../", features = [ "stdweb" ] }
stdweb = "0.4.20"

View File

@@ -0,0 +1,80 @@
use winit::{
event::{device::GamepadEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use stdweb::js;
/**
* Build example (from examples/web/gamepad/stdweb):
* cargo web build
* Run example (from examples/web/gamepad/stdweb):
* cargo web start
* Development (from project root):
* npx nodemon --watch src --watch examples/web/gamepad/stdweb/src -e rs --exec 'cargo web check'
*/
pub fn main() {
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Gamepad tests")
.build(&event_loop)
.unwrap();
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| match event {
Event::GamepadEvent(gamepad_handle, event) => match event {
GamepadEvent::Axis {
axis_id,
axis,
value,
stick,
} if value > deadzone => {
let string = format!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick);
js! { console.log( @{string} ); }
}
GamepadEvent::Stick {
x_id,
y_id,
x_value,
y_value,
side,
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
let string = format!(
"Stick {:#?} {:#?} {:#?} {:#?} {:#?}",
x_id, y_id, x_value, y_value, side
);
js! { console.log( @{string} ); }
}
GamepadEvent::Button {
button_id,
button,
state,
} => {
let string = format!("Button {:#?} {:#?} {:#?}", button_id, button, state);
js! { console.log( @{string} ); }
}
GamepadEvent::Added => {
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
js! { console.log( @{string} ); }
}
GamepadEvent::Removed => {
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
js! { console.log( @{string} ); }
}
_ => {}
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
});
}

View File

@@ -0,0 +1,7 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
.DS_Store

View File

@@ -0,0 +1,20 @@
[package]
name = "websys-gamepad"
version = "0.0.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
winit = { path = "../../../../", features = [ "web-sys" ] }
wasm-bindgen = "0.2.45"
wasm-bindgen-test = "0.3.8"
web-sys = { version = "0.3.22", features = [ "console" ] }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = "0.1.6"

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Gamepad</title>
</head>
<body>
<canvas id="my_id"></canvas>
<script>
window.Module = {
canvas: document.getElementById('my_id')
}
</script>
<script type="module">
import example_gamepad from "../pkg/websys_examples.js"
example_gamepad()
.then((m) => console.log('Success', m))
.catch((e) => console.log('Ar error occured', e))
</script>
</body>
</html>

View File

@@ -0,0 +1,78 @@
mod utils;
use wasm_bindgen::prelude::*;
use winit::{
event::{device::GamepadEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
/**
* Build example (from examples/gamepad/websys):
* wasm-pack build --target web
* Run web server (from examples/gamepad/websys):
* npx http-server
* Open your browser at http://localhost:8000/files/${EXAMPLE}.html
* Development (from project root):
* npx nodemon --watch src --watch examples/web/gamepad/websys/src -e rs --exec 'cd examples/web/gamepad/websys && wasm-pack build --target web'
*/
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&format_args!($($t)*).to_string().into()))
}
#[wasm_bindgen(start)]
pub fn example_gamepad() {
utils::set_panic_hook(); // needed for error stack trace
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Gamepad tests")
.build(&event_loop)
.unwrap();
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| {
match event {
Event::GamepadEvent(gamepad_handle, event) => {
match event {
GamepadEvent::Axis {
axis_id,
axis,
value,
stick,
} if value > deadzone => {
console_log!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick)
},
GamepadEvent::Stick {
x_id, y_id, x_value, y_value, side
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
console_log!("Stick {:#?} {:#?} {:#?} {:#?} {:#?}", x_id, y_id, x_value, y_value, side)
},
GamepadEvent::Button {
button_id,
button,
state,
} => {
console_log!("Button {:#?} {:#?} {:#?}", button_id, button, state)
},
GamepadEvent::Added => {
console_log!("[{:?}] {:#?}", gamepad_handle, event)
},
GamepadEvent::Removed => console_log!("[{:?}] {:#?}", gamepad_handle, event),
_ => {},
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
// #[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@@ -9,11 +9,12 @@ use std::path::PathBuf;
use crate::{
dpi::{LogicalPosition, LogicalSize},
platform_impl,
window::WindowId,
};
/// Describes a generic event.
pub mod device;
/// A generic event.
#[derive(Clone, Debug, PartialEq)]
pub enum Event<T> {
/// Emitted when the OS sends an event to a winit window.
@@ -21,12 +22,16 @@ pub enum Event<T> {
window_id: WindowId,
event: WindowEvent,
},
/// Emitted when the OS sends an event to a device.
DeviceEvent {
device_id: DeviceId,
event: DeviceEvent,
},
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
/// Emitted when a mouse device has generated input.
MouseEvent(device::MouseId, device::MouseEvent),
/// Emitted when a keyboard device has generated input.
KeyboardEvent(device::KeyboardId, device::KeyboardEvent),
HidEvent(device::HidId, device::HidEvent),
/// Emitted when a gamepad/joystick device has generated input.
GamepadEvent(device::GamepadHandle, device::GamepadEvent),
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](../event_loop/struct.EventLoopProxy.html#method.send_event)
UserEvent(T),
/// Emitted when new events arrive from the OS to be processed.
NewEvents(StartCause),
@@ -51,7 +56,10 @@ impl<T> Event<T> {
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
MouseEvent(id, event) => Ok(MouseEvent(id, event)),
KeyboardEvent(id, event) => Ok(KeyboardEvent(id, event)),
HidEvent(id, event) => Ok(HidEvent(id, event)),
GamepadEvent(id, event) => Ok(GamepadEvent(id, event)),
NewEvents(cause) => Ok(NewEvents(cause)),
EventsCleared => Ok(EventsCleared),
LoopDestroyed => Ok(LoopDestroyed),
@@ -61,7 +69,7 @@ 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)]
pub enum StartCause {
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
@@ -87,7 +95,7 @@ pub enum StartCause {
Init,
}
/// Describes an event from a `Window`.
/// An event from a `Window`.
#[derive(Clone, Debug, PartialEq)]
pub enum WindowEvent {
/// The size of the window has changed. Contains the client area's new dimensions.
@@ -129,15 +137,10 @@ pub enum WindowEvent {
Focused(bool),
/// An event from the keyboard has been received.
KeyboardInput {
device_id: DeviceId,
input: KeyboardInput,
},
KeyboardInput(KeyboardInput),
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
@@ -146,14 +149,13 @@ pub enum WindowEvent {
},
/// The cursor has entered the window.
CursorEntered { device_id: DeviceId },
CursorEntered,
/// The cursor has left the window.
CursorLeft { device_id: DeviceId },
CursorLeft,
/// A mouse wheel movement or touchpad scroll occurred.
MouseWheel {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
modifiers: ModifiersState,
@@ -161,7 +163,6 @@ pub enum WindowEvent {
/// An mouse button press has been received.
MouseInput {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
modifiers: ModifiersState,
@@ -172,18 +173,7 @@ pub enum WindowEvent {
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
/// is being pressed) and stage (integer representing the click level).
TouchpadPressure {
device_id: DeviceId,
pressure: f32,
stage: i64,
},
/// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion {
device_id: DeviceId,
axis: AxisId,
value: f64,
},
TouchpadPressure { pressure: f32, stage: i64 },
/// The OS or application has requested that the window be redrawn.
RedrawRequested,
@@ -203,80 +193,7 @@ pub enum WindowEvent {
HiDpiFactorChanged(f64),
}
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
/// Represents raw hardware events that are not associated with any particular window.
///
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
/// may not match.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug, PartialEq)]
pub enum DeviceEvent {
Added,
Removed,
/// Change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
MouseMotion {
/// (x, y) change in position in unspecified units.
///
/// Different devices may use different units.
delta: (f64, f64),
},
/// Physical scroll event
MouseWheel {
delta: MouseScrollDelta,
},
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
/// that winit supports on this platform, including mouse devices. If the device is a mouse
/// device then this will be reported alongside the MouseMotion event.
Motion {
axis: AxisId,
value: f64,
},
Button {
button: ButtonId,
state: ElementState,
},
Key(KeyboardInput),
/// Keyboard modifiers have changed
#[doc(hidden)]
ModifiersChanged {
modifiers: ModifiersState,
},
Text {
codepoint: char,
},
}
/// Describes a keyboard input event.
/// A keyboard input event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardInput {
@@ -302,13 +219,16 @@ pub struct KeyboardInput {
pub modifiers: ModifiersState,
}
/// Describes touch-screen input state.
/// Touch input state.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase {
Started,
Moved,
Ended,
/// The touch has been cancelled by the OS.
///
/// This can occur in a variety of situations, such as the window losing focus.
Cancelled,
}
@@ -330,7 +250,6 @@ pub enum TouchPhase {
/// device against their face.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
pub location: LogicalPosition,
/// 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+.
pub force: Option<Force>,
/// Unique identifier of a finger.
///
/// This may get reused by the system after the touch ends.
pub id: u64,
}
@@ -409,16 +330,16 @@ pub type AxisId = u32;
/// Identifier for a specific button on some device.
pub type ButtonId = u32;
/// Describes the input state of a key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
/// The input state of a key or button.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ElementState {
Pressed,
Released,
}
/// Describes a button of a mouse controller.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
/// A button on a mouse.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
@@ -427,7 +348,7 @@ pub enum MouseButton {
Other(u8),
}
/// Describes a difference in the mouse scroll wheel state.
/// A difference in the mouse scroll wheel state.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseScrollDelta {
@@ -446,7 +367,7 @@ pub enum MouseScrollDelta {
PixelDelta(LogicalPosition),
}
/// Symbolic name for a keyboard key.
/// Symbolic name of a keyboard key.
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -645,7 +566,7 @@ pub enum VirtualKeyCode {
Cut,
}
/// Represents the current state of the keyboard modifiers
/// The current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]

363
src/event/device.rs Normal file
View 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)
}
}

View File

@@ -138,6 +138,7 @@ pub mod event_loop;
mod icon;
pub mod monitor;
mod platform_impl;
mod util;
pub mod window;
pub mod platform;

View File

@@ -6,7 +6,7 @@ use libc;
use winapi::shared::windef::HWND;
use crate::{
event::DeviceId,
event::device::{GamepadHandle, KeyboardId, MouseId},
event_loop::EventLoop,
monitor::MonitorHandle,
platform_impl::EventLoop as WindowsEventLoop,
@@ -149,17 +149,49 @@ impl MonitorHandleExtWindows for MonitorHandle {
}
}
/// Additional methods on `DeviceId` that are specific to Windows.
pub trait DeviceIdExtWindows {
/// Additional methods on device types that are specific to Windows.
pub trait DeviceExtWindows {
/// Returns an identifier that persistently refers to this specific device.
///
/// Will return `None` if the device is no longer available.
fn persistent_identifier(&self) -> Option<String>;
/// Returns the handle of the device - `HANDLE`.
fn handle(&self) -> *mut c_void;
}
impl DeviceIdExtWindows for DeviceId {
impl DeviceExtWindows for MouseId {
#[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 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 _
}
}

View File

@@ -1,8 +0,0 @@
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub i32);
impl Id {
pub unsafe fn dummy() -> Self {
Id(0)
}
}

View File

@@ -0,0 +1,37 @@
use crate::event::device::{GamepadAxis, GamepadButton};
pub(crate) static BUTTONS: [GamepadButton; 16] = [
GamepadButton::South,
GamepadButton::East,
GamepadButton::West,
GamepadButton::North,
GamepadButton::LeftTrigger,
GamepadButton::RightTrigger,
GamepadButton::LeftShoulder,
GamepadButton::RightShoulder,
GamepadButton::Select,
GamepadButton::Start,
GamepadButton::LeftStick,
GamepadButton::RightStick,
GamepadButton::DPadUp,
GamepadButton::DPadDown,
GamepadButton::DPadLeft,
GamepadButton::DPadRight,
];
pub(crate) static AXES: [GamepadAxis; 6] = [
GamepadAxis::LeftStickX,
GamepadAxis::LeftStickY,
GamepadAxis::RightStickX,
GamepadAxis::RightStickY,
GamepadAxis::LeftTrigger,
GamepadAxis::RightTrigger,
];
pub(crate) fn button_code(index: usize) -> Option<GamepadButton> {
BUTTONS.get(index).map(|ev| ev.clone())
}
pub(crate) fn axis_code(index: usize) -> Option<GamepadAxis> {
AXES.get(index).map(|ev| ev.clone())
}

View File

@@ -0,0 +1,167 @@
use super::utils;
use crate::event::device;
use crate::platform_impl::platform::{backend, device::gamepad, GamepadHandle, event_loop::global};
use std::collections::VecDeque;
pub struct Manager {
pub(crate) gamepads: Vec<backend::gamepad::Gamepad>,
pub(crate) events: VecDeque<(backend::gamepad::Gamepad, device::GamepadEvent)>,
pub(crate) global_window: Option<global::Shared>,
}
impl Manager {
pub fn new() -> Self {
Self {
gamepads: Vec::new(),
events: VecDeque::new(),
global_window: None,
}
}
// Register global window to fetch gamepads.
// Due to Chrome issue, I prefer to use its gamepad list
pub fn set_global_window(&mut self, global_window: global::Shared) {
self.global_window.replace(global_window);
}
// Get an updated raw gamepad and generate a new mapping
pub fn collect_gamepads(&self) -> Option<Vec<backend::gamepad::Gamepad>> {
self.global_window.as_ref().map(|w| w.get_gamepads())
}
// Collect gamepad events (buttons/axes/sticks)
// dispatch to handler and update gamepads
pub fn collect_events<F>(&mut self, mut handler: F)
where
F: 'static + FnMut((device::GamepadHandle, device::GamepadEvent)),
{
let opt_new_gamepads = self.collect_gamepads();
if opt_new_gamepads.is_none() {
return;
}
let new_gamepads = opt_new_gamepads.unwrap();
let old_gamepads = &self.gamepads;
let mut old_index = 0;
let mut new_index = 0;
// Collect events
loop {
match (old_gamepads.get(old_index), new_gamepads.get(new_index)) {
(Some(old), Some(new)) if old.index() == new.index() => {
// Button events
let buttons = old.mapping.buttons().zip(new.mapping.buttons()).enumerate();
for (btn_index, (old_button, new_button)) in buttons {
match (old_button, new_button) {
(false, true) => {
self.events.push_back((new.clone(), utils::gamepad_button(btn_index, true)))
}
(true, false) => {
self.events.push_back((new.clone(), utils::gamepad_button(btn_index, false)))
}
_ => (),
}
}
// Axis events
let axes = old.mapping.axes().zip(new.mapping.axes()).enumerate();
for (axis_index, (old_axis, new_axis)) in axes {
if old_axis != new_axis {
self.events.push_back((new.clone(), utils::gamepad_axis(axis_index, new_axis)))
}
}
// Stick events
let mut old_axes = old.mapping.axes();
let mut new_axes = new.mapping.axes();
let old_left = (old_axes.next(), old_axes.next());
let new_left = (new_axes.next(), new_axes.next());
if old_left != new_left {
if let (Some(x), Some(y)) = (new_left.0, new_left.1) {
self.events.push_back((
new.clone(),
utils::gamepad_stick(0, 1, x, y, device::Side::Left),
));
}
}
let old_right = (old_axes.next(), old_axes.next());
let new_right = (new_axes.next(), new_axes.next());
if old_right != new_right {
if let (Some(x), Some(y)) = (new_right.0, new_right.1) {
self.events.push_back((
new.clone(),
utils::gamepad_stick(2, 3, x, y, device::Side::Right),
));
}
}
// Increment indices
old_index += 1;
new_index += 1;
},
// Connect
(None, Some(new)) => {
self.events.push_back((
new.clone(),
device::GamepadEvent::Added,
));
new_index += 1;
},
// Connect
(Some(old), Some(new)) if old.index > new.index => {
self.events.push_back((
new.clone(),
device::GamepadEvent::Added,
));
new_index += 1;
},
// Disconnect
(Some(old), Some(_new)) => {
self.events.push_back((
old.clone(),
device::GamepadEvent::Removed,
));
old_index += 1;
},
// Disconnect
(Some(old), None) => {
self.events.push_back((
old.clone(),
device::GamepadEvent::Removed,
));
old_index += 1;
},
// Break loop
(None, None) => {
break
}
}
}
// Dispatch events and drain events vec
loop {
if let Some((gamepad, event)) = self.events.pop_front() {
handler((
device::GamepadHandle(GamepadHandle {
id: gamepad.index,
gamepad: gamepad::Shared::Raw(gamepad),
}),
event,
));
} else {
break;
}
}
// Update gamepads
self.gamepads = new_gamepads;
}
}

View File

@@ -0,0 +1,23 @@
#[derive(Debug, Clone)]
pub enum Mapping {
Standard { buttons: [bool; 16], axes: [f64; 6] },
NoMapping { buttons: Vec<bool>, axes: Vec<f64> },
}
impl Mapping {
pub(crate) fn buttons<'a>(&'a self) -> impl Iterator<Item = bool> + 'a {
match self {
Mapping::Standard { buttons, .. } => buttons.iter(),
Mapping::NoMapping { buttons, .. } => buttons.iter(),
}
.cloned()
}
pub(crate) fn axes<'a>(&'a self) -> impl Iterator<Item = f64> + 'a {
match self {
Mapping::Standard { axes, .. } => axes.iter(),
Mapping::NoMapping { axes, .. } => axes.iter(),
}
.cloned()
}
}

View File

@@ -0,0 +1,99 @@
mod manager;
mod mapping;
mod utils;
pub mod constants;
pub use manager::Manager;
pub use mapping::Mapping;
use crate::event::device::{BatteryLevel, RumbleError};
use crate::platform_impl::platform::backend;
use std::fmt;
pub enum Shared {
Raw(backend::gamepad::Gamepad),
Dummy,
}
impl Shared {
// An integer that is auto-incremented to be unique for each device
// currently connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
pub fn id(&self) -> i32 {
match self {
Shared::Raw(g) => g.index() as i32,
Shared::Dummy => -1,
}
}
// A string containing some information about the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
pub fn info(&self) -> String {
match self {
Shared::Raw(g) => g.id(),
Shared::Dummy => String::new(),
}
}
// A boolean indicating whether the gamepad is still connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
pub fn connected(&self) -> bool {
match self {
Shared::Raw(g) => g.connected(),
Shared::Dummy => false,
}
}
// [EXPERIMENTAL] An array containing GamepadHapticActuator objects,
// each of which represents haptic feedback hardware available on the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators
pub fn rumble(&self, left_speed: f64, _right_speed: f64) -> Result<(), RumbleError> {
match self {
Shared::Dummy => Ok(()),
Shared::Raw(g) => {
g.vibrate(left_speed, 1000f64);
Ok(())
}
}
}
pub fn is_dummy(&self) -> bool {
match self {
Shared::Dummy => true,
_ => false,
}
}
pub fn port(&self) -> Option<u8> {
None
}
pub fn battery_level(&self) -> Option<BatteryLevel> {
None
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
match self {
Shared::Raw(g) => Shared::Raw(g.clone()),
Shared::Dummy => Shared::Dummy,
}
}
}
impl Default for Shared {
fn default() -> Self {
Shared::Dummy
}
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
if self.is_dummy() {
write!(f, "Gamepad (Dummy)")
} else {
write!(f, "Gamepad ({}#{})", self.id(), self.info())
}
}
}

View File

@@ -0,0 +1,44 @@
use crate::event::{ElementState, device};
use super::constants;
pub fn gamepad_button(code: usize, pressed: bool) -> device::GamepadEvent {
let button_id = code as u32;
let button = constants::button_code(code);
let state = if pressed {
ElementState::Pressed
} else {
ElementState::Released
};
device::GamepadEvent::Button {
button_id,
button,
state,
}
}
pub fn gamepad_axis(code: usize, value: f64) -> device::GamepadEvent {
let axis_id = code as u32;
let axis = constants::axis_code(code);
device::GamepadEvent::Axis {
axis_id,
axis,
value,
stick: true,
}
}
pub fn gamepad_stick(x_code: usize, y_code: usize, x_value: f64, y_value: f64, side: device::Side) -> device::GamepadEvent {
let x_id = x_code as u32;
let y_id = y_code as u32;
device::GamepadEvent::Stick {
x_id,
y_id,
x_value,
y_value,
side,
}
}

View File

@@ -0,0 +1,161 @@
pub mod gamepad;
use super::event_loop::EventLoop;
use crate::event::device;
use std::{
cmp::{Eq, Ordering, PartialEq, PartialOrd},
hash::{Hash, Hasher},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct MouseId(pub i32);
unsafe impl Send for MouseId {}
unsafe impl Sync for MouseId {}
impl MouseId {
pub unsafe fn dummy() -> Self {
Self(0)
}
pub fn is_connected(&self) -> bool {
false
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::MouseId> {
event_loop.mice()
}
}
impl From<MouseId> for device::MouseId {
fn from(platform_id: MouseId) -> Self {
Self(platform_id)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct KeyboardId(pub i32);
unsafe impl Send for KeyboardId {}
unsafe impl Sync for KeyboardId {}
impl KeyboardId {
pub unsafe fn dummy() -> Self {
Self(0)
}
pub fn is_connected(&self) -> bool {
false
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::KeyboardId> {
event_loop.keyboards()
}
}
impl From<KeyboardId> for device::KeyboardId {
fn from(platform_id: KeyboardId) -> Self {
Self(platform_id)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct HidId(pub i32);
unsafe impl Send for HidId {}
unsafe impl Sync for HidId {}
impl HidId {
pub unsafe fn dummy() -> Self {
Self(0)
}
pub fn is_connected(&self) -> bool {
false
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::HidId> {
event_loop.hids()
}
}
impl From<HidId> for device::HidId {
fn from(platform_id: HidId) -> Self {
Self(platform_id)
}
}
#[derive(Clone, Debug)]
pub(crate) struct GamepadHandle {
pub(crate) id: i32,
pub(crate) gamepad: gamepad::Shared,
}
unsafe impl Send for GamepadHandle {}
unsafe impl Sync for GamepadHandle {}
impl GamepadHandle {
pub unsafe fn dummy() -> Self {
Self {
id: -1,
gamepad: gamepad::Shared::default(),
}
}
pub fn is_connected(&self) -> bool {
self.gamepad.connected()
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::GamepadHandle> {
event_loop.gamepads()
}
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), device::RumbleError> {
self.gamepad.rumble(left_speed, right_speed)
}
pub fn port(&self) -> Option<u8> {
self.gamepad.port()
}
pub fn battery_level(&self) -> Option<device::BatteryLevel> {
self.gamepad.battery_level()
}
}
impl Eq for GamepadHandle {}
impl PartialEq for GamepadHandle {
#[inline(always)]
fn eq(&self, othr: &Self) -> bool {
self.id == othr.id
}
}
impl Ord for GamepadHandle {
#[inline(always)]
fn cmp(&self, othr: &Self) -> Ordering {
self.id.cmp(&othr.id)
}
}
impl PartialOrd for GamepadHandle {
#[inline(always)]
fn partial_cmp(&self, othr: &Self) -> Option<Ordering> {
self.id.partial_cmp(&othr.id)
}
}
impl Hash for GamepadHandle {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}

View File

@@ -0,0 +1,81 @@
use super::super::device::{gamepad, GamepadHandle};
use super::backend;
use crate::event::device;
use std::{cell::RefCell, rc::Rc, collections::HashSet};
#[derive(Debug)]
pub struct Window {
raw: RefCell<Option<backend::window::Shared>>,
gamepads: Rc<RefCell<HashSet<i32>>>,
}
#[derive(Debug)]
pub struct Shared(Rc<Window>);
impl Shared {
pub fn new() -> Self {
Self(Rc::new(Window {
raw: RefCell::new(None),
gamepads: Rc::new(RefCell::new(HashSet::new())),
}))
}
// Request window object and listen global events
pub fn register_events(&self) -> Result<(), crate::error::OsError> {
if (*self.0.raw.borrow()).is_none() {
let shared = backend::window::Shared::create()?;
let mut window = shared.0.borrow_mut();
let shared_gamepads = self.0.gamepads.clone();
window.on_gamepad_connected(move |gamepad: backend::gamepad::Gamepad| {
let mut gamepads = shared_gamepads.borrow_mut();
let index = gamepad.index();
if !gamepads.contains(&index) {
gamepads.insert(index);
}
});
let shared_gamepads = self.0.gamepads.clone();
window.on_gamepad_disconnected(move |gamepad: backend::gamepad::Gamepad| {
let mut gamepads = shared_gamepads.borrow_mut();
let index = gamepad.index();
if gamepads.contains(&index) {
gamepads.remove(&index);
}
});
self.0.raw.replace(Some(shared.clone()));
}
Ok(())
}
// Google Chrome create an array of [null, null, null, null].
// To fix that issue, I create my own list of gamepads
// by listening "gamepadconnected" and "gamepaddisconnected"
pub fn get_gamepads(&self) -> Vec<backend::gamepad::Gamepad> {
let gamepads = self.0.gamepads.borrow_mut();
backend::get_gamepads()
.filter(|g| gamepads.contains(&g.index()))
.collect()
}
// Return gamepads handles required for EventLoop::gamepads()
pub fn get_gamepad_handles(&self) -> Vec<device::GamepadHandle> {
self.get_gamepads()
.iter()
.map(|gamepad| {
device::GamepadHandle(GamepadHandle {
id: gamepad.index,
gamepad: gamepad::Shared::Raw(gamepad.clone()),
})
})
.collect()
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
Shared(self.0.clone())
}
}

View File

@@ -2,11 +2,12 @@ mod proxy;
mod runner;
mod state;
mod window_target;
pub(crate) mod global;
pub use self::proxy::Proxy;
pub use self::window_target::WindowTarget;
use super::{backend, device, monitor, window};
use super::{backend, monitor, window};
use crate::event::Event;
use crate::event_loop as root;
@@ -64,4 +65,20 @@ impl<T> EventLoop<T> {
pub fn window_target(&self) -> &root::EventLoopWindowTarget<T> {
&self.elw
}
pub fn mice(&self) -> impl '_ + Iterator<Item = crate::event::device::MouseId> {
std::iter::empty()
}
pub fn keyboards(&self) -> impl '_ + Iterator<Item = crate::event::device::KeyboardId> {
std::iter::empty()
}
pub fn hids(&self) -> impl '_ + Iterator<Item = crate::event::device::HidId> {
std::iter::empty()
}
pub fn gamepads(&self) -> impl '_ + Iterator<Item = crate::event::device::GamepadHandle> {
self.elw.p.collect_gamepads().into_iter()
}
}

View File

@@ -2,6 +2,7 @@ use super::{backend, state::State};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop as root;
use crate::window::WindowId;
use crate::platform_impl::platform::device::gamepad;
use instant::{Duration, Instant};
use std::{
@@ -24,6 +25,7 @@ pub struct Execution<T> {
events: RefCell<VecDeque<Event<T>>>,
id: RefCell<u32>,
redraw_pending: RefCell<HashSet<WindowId>>,
gamepad_manager: RefCell<gamepad::Manager>,
}
struct Runner<T> {
@@ -49,9 +51,14 @@ impl<T: 'static> Shared<T> {
events: RefCell::new(VecDeque::new()),
id: RefCell::new(0),
redraw_pending: RefCell::new(HashSet::new()),
gamepad_manager: RefCell::new(gamepad::Manager::new()),
}))
}
pub fn set_global_window(&self, global_window: super::global::Shared) {
self.0.gamepad_manager.borrow_mut().set_global_window(global_window);
}
// Set the event callback to use for the event loop runner
// This the event callback is a fairly thin layer over the user-provided callback that closes
// over a RootEventLoopWindowTarget reference
@@ -138,6 +145,14 @@ impl<T: 'static> Shared<T> {
&mut control,
);
}
// Collect all global events
let mut gamepad_manager = self.0.gamepad_manager.borrow_mut();
let instance = self.clone();
gamepad_manager.collect_events(move |(handle, event)| {
instance.handle_event(Event::GamepadEvent(handle, event), &mut control);
});
// Every events are cleared
self.handle_event(Event::EventsCleared, &mut control);
self.apply_control_flow(control);
// If the event loop is closed, it has been closed this iteration and now the closing

View File

@@ -1,18 +1,20 @@
use super::{backend, device, proxy::Proxy, runner, window};
use super::{backend, proxy::Proxy, runner, window, global};
use crate::dpi::LogicalSize;
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use crate::event::{device, ElementState, Event, KeyboardInput, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::platform_impl::platform::device::{KeyboardId, MouseId};
use crate::window::WindowId;
use std::clone::Clone;
pub struct WindowTarget<T: 'static> {
pub(crate) runner: runner::Shared<T>,
pub(crate) global_window: global::Shared,
}
impl<T> Clone for WindowTarget<T> {
fn clone(&self) -> Self {
WindowTarget {
runner: self.runner.clone(),
global_window: self.global_window.clone(),
}
}
}
@@ -21,6 +23,7 @@ impl<T> WindowTarget<T> {
pub fn new() -> Self {
WindowTarget {
runner: runner::Shared::new(),
global_window: global::Shared::new(),
}
}
@@ -29,6 +32,7 @@ impl<T> WindowTarget<T> {
}
pub fn run(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
self.runner.set_global_window(self.global_window.clone());
self.runner.set_listener(event_handler);
}
@@ -36,6 +40,14 @@ impl<T> WindowTarget<T> {
window::Id(self.runner.generate_id())
}
pub fn collect_gamepads(&self) -> Vec<crate::event::device::GamepadHandle> {
self.global_window.get_gamepad_handles()
}
pub fn register_global_events(&self) -> Result<(), crate::error::OsError> {
self.global_window.register_events()
}
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
let runner = self.runner.clone();
canvas.set_attribute("data-raw-handle", &id.0.to_string());
@@ -57,34 +69,28 @@ impl<T> WindowTarget<T> {
let runner = self.runner.clone();
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
device_id: DeviceId(unsafe { device::Id::dummy() }),
input: KeyboardInput {
scancode,
state: ElementState::Pressed,
virtual_keycode,
modifiers,
},
},
});
runner.send_event(Event::KeyboardEvent(
device::KeyboardId(unsafe { KeyboardId::dummy() }),
device::KeyboardEvent::Input(KeyboardInput {
scancode,
state: ElementState::Pressed,
virtual_keycode,
modifiers,
}),
));
});
let runner = self.runner.clone();
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
device_id: DeviceId(unsafe { device::Id::dummy() }),
input: KeyboardInput {
scancode,
state: ElementState::Released,
virtual_keycode,
modifiers,
},
},
});
runner.send_event(Event::KeyboardEvent(
device::KeyboardId(unsafe { KeyboardId::dummy() }),
device::KeyboardEvent::Input(KeyboardInput {
scancode,
state: ElementState::Released,
virtual_keycode,
modifiers,
}),
));
});
let runner = self.runner.clone();
@@ -96,31 +102,26 @@ impl<T> WindowTarget<T> {
});
let runner = self.runner.clone();
canvas.on_cursor_leave(move |pointer_id| {
canvas.on_cursor_leave(move || {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorLeft {
device_id: DeviceId(device::Id(pointer_id)),
},
event: WindowEvent::CursorLeft,
});
});
let runner = self.runner.clone();
canvas.on_cursor_enter(move |pointer_id| {
canvas.on_cursor_enter(move || {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorEntered {
device_id: DeviceId(device::Id(pointer_id)),
},
event: WindowEvent::CursorEntered,
});
});
let runner = self.runner.clone();
canvas.on_cursor_move(move |pointer_id, position, modifiers| {
canvas.on_cursor_move(move |position, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorMoved {
device_id: DeviceId(device::Id(pointer_id)),
position,
modifiers,
},
@@ -128,42 +129,33 @@ impl<T> WindowTarget<T> {
});
let runner = self.runner.clone();
canvas.on_mouse_press(move |pointer_id, button, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseInput {
device_id: DeviceId(device::Id(pointer_id)),
canvas.on_mouse_press(move |pointer_id, button| {
runner.send_event(Event::MouseEvent(
device::MouseId(MouseId(pointer_id)),
device::MouseEvent::Button {
state: ElementState::Pressed,
button,
modifiers,
},
});
));
});
let runner = self.runner.clone();
canvas.on_mouse_release(move |pointer_id, button, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseInput {
device_id: DeviceId(device::Id(pointer_id)),
canvas.on_mouse_release(move |pointer_id, button| {
runner.send_event(Event::MouseEvent(
device::MouseId(MouseId(pointer_id)),
device::MouseEvent::Button {
state: ElementState::Released,
button,
modifiers,
},
});
));
});
let runner = self.runner.clone();
canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseWheel {
device_id: DeviceId(device::Id(pointer_id)),
delta,
phase: TouchPhase::Moved,
modifiers,
},
});
canvas.on_mouse_wheel(move |pointer_id, delta| {
runner.send_event(Event::MouseEvent(
device::MouseId(MouseId(pointer_id)),
device::MouseEvent::Wheel(delta.0, delta.1),
));
});
let runner = self.runner.clone();

View File

@@ -19,7 +19,6 @@ mod backend;
#[cfg(not(any(feature = "web-sys", feature = "stdweb")))]
compile_error!("Please select a feature to build for web: `web-sys`, `stdweb`");
pub use self::device::Id as DeviceId;
pub use self::error::OsError;
pub use self::event_loop::{
EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
@@ -29,3 +28,5 @@ pub use self::window::{
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
Window,
};
pub(crate) use self::device::*;

View File

@@ -1,7 +1,7 @@
use super::event;
use super::utils;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use std::cell::RefCell;
@@ -129,9 +129,9 @@ impl Canvas {
{
self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
);
}));
}
@@ -142,9 +142,9 @@ impl Canvas {
{
self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
);
}));
}
@@ -159,75 +159,65 @@ impl Canvas {
// viable/compatible alternative as of now. `beforeinput` is still widely
// unsupported.
self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| {
handler(event::codepoint(&event));
handler(utils::codepoint(&event));
}));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
F: 'static + FnMut(),
{
self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
handler(event.pointer_id());
self.on_cursor_leave = Some(self.add_event(move |_event: PointerOutEvent| {
handler();
}));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
F: 'static + FnMut(),
{
self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
handler(event.pointer_id());
self.on_cursor_enter = Some(self.add_event(move |_event: PointerOverEvent| {
handler();
}));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
F: 'static + FnMut(i32, MouseButton),
{
self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
handler(event.pointer_id(), utils::mouse_button(&event));
}));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
F: 'static + FnMut(i32, MouseButton),
{
self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
handler(event.pointer_id(), utils::mouse_button(&event));
}));
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
F: 'static + FnMut(LogicalPosition, ModifiersState),
{
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event),
event::mouse_modifiers(&event),
utils::mouse_position(&event),
utils::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
F: 'static + FnMut(i32, (f64, f64)),
{
self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
}
let delta = utils::mouse_scroll_delta(&event);
handler(0, delta);
}));
}

View File

@@ -0,0 +1,82 @@
use std::{cmp::PartialEq};
use crate::platform_impl::platform::device;
use super::utils;
use stdweb::js;
#[derive(Debug)]
pub struct Gamepad {
pub(crate) index: i32,
pub(crate) raw: stdweb::web::Gamepad,
pub(crate) mapping: device::gamepad::Mapping,
}
impl Gamepad {
pub fn new(raw: stdweb::web::Gamepad) -> Self {
let mapping = utils::create_mapping(&raw);
Self {
index: raw.index(),
raw,
mapping,
}
}
// An integer that is auto-incremented to be unique for each device
// currently connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
pub fn index(&self) -> i32 {
self.raw.index()
}
// A string containing some information about the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
pub fn id(&self) -> String {
self.raw.id()
}
// A boolean indicating whether the gamepad is still connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
pub fn connected(&self) -> bool {
self.raw.connected()
}
// EXPERIMENTAL
#[allow(dead_code)]
pub fn vibrate(&self, value: f64, duration: f64) {
let index = self.index;
js! {
const gamepads = navigator.getGamepads();
let gamepad = null;
for (let i = 0; i < gamepads.length; i++) {
if (gamepads[i] && gamepads[i].index == @{index}) {
gamepad = gamepads[i];
break
}
}
if (!gamepad || !gamepad.hapticActuators) return;
for (let i = 0; i < gamepad.hapticActuators.length; i++) {
const actuator = gamepad.hapticActuators[i];
if (actuator && actuator.type === "vibration") {
actuator.pulse(@{value}, @{duration});
}
}
}
}
}
impl Clone for Gamepad {
fn clone(&self) -> Self {
Self {
index: self.index,
raw: self.raw.clone(),
mapping: self.mapping.clone(),
}
}
}
impl PartialEq for Gamepad {
#[inline(always)]
fn eq(&self, othr: &Self) -> bool {
self.raw.index() == othr.raw.index()
}
}

View File

@@ -1,6 +1,8 @@
mod canvas;
mod event;
pub mod gamepad;
mod timeout;
mod utils;
pub mod window;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
@@ -50,3 +52,9 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
None => false,
}
}
pub fn get_gamepads() -> impl Iterator<Item = gamepad::Gamepad> {
stdweb::web::Gamepad::get_all()
.into_iter()
.filter_map(|gamepad| gamepad.map(|gamepad| gamepad::Gamepad::new(gamepad)))
}

View File

@@ -1,7 +1,11 @@
use crate::dpi::LogicalPosition;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::platform_impl::platform::device::gamepad;
use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent};
use stdweb::web::{
event::{IKeyboardEvent, IMouseEvent, MouseWheelEvent},
Gamepad, GamepadMappingType,
};
use stdweb::{js, unstable::TryInto, JsSerialize};
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
@@ -30,15 +34,10 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition {
}
}
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> (f64, f64) {
let x = event.delta_x();
let y = event.delta_y();
match event.delta_mode() {
MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
MouseWheelDeltaMode::Page => None,
}
(x, y)
}
pub fn scan_code<T: JsSerialize>(event: &T) -> ScanCode {
@@ -227,3 +226,36 @@ pub fn codepoint(event: &impl IKeyboardEvent) -> char {
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
event.key().chars().next().unwrap()
}
pub fn create_mapping(raw: &Gamepad) -> gamepad::Mapping {
match raw.mapping() {
GamepadMappingType::Standard => {
let mut buttons = [false; 16];
let mut axes = [0.0; 6];
for (index, button) in raw
.buttons()
.into_iter()
.enumerate()
.take(buttons.len())
{
buttons[index] = button.pressed();
}
for (index, axis) in raw.axes().into_iter().enumerate().take(axes.len()) {
axes[index] = axis;
}
gamepad::Mapping::Standard { buttons, axes }
}
_ => {
let buttons = raw
.buttons()
.into_iter()
.map(|button| button.pressed())
.collect();
let axes = raw.axes();
gamepad::Mapping::NoMapping { buttons, axes }
}
}
}

View File

@@ -0,0 +1,77 @@
use super::gamepad;
use crate::error::OsError as RootOE;
use std::{cell::RefCell, rc::Rc};
use stdweb::web;
use stdweb::web::{IEventTarget, event::IGamepadEvent};
#[derive(Debug)]
pub struct Shared(pub Rc<RefCell<Window>>);
#[derive(Debug)]
pub struct Window {
raw: web::Window,
on_gamepad_connected: Option<web::EventListenerHandle>,
on_gamepad_disconnected: Option<web::EventListenerHandle>,
}
impl Shared {
pub fn create() -> Result<Self, RootOE> {
let global = Window::create()?;
Ok(Shared(Rc::new(RefCell::new(global))))
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
Shared(self.0.clone())
}
}
impl Window {
pub fn create() -> Result<Self, RootOE> {
let raw = stdweb::web::window();
Ok(Window {
raw,
on_gamepad_connected: None,
on_gamepad_disconnected: None,
})
}
pub fn on_gamepad_connected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_connected = Some(self.add_event(
move |event: stdweb::web::event::GamepadConnectedEvent| {
let gamepad = event.gamepad();
handler(gamepad::Gamepad::new(gamepad));
},
));
}
pub fn on_gamepad_disconnected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_connected = Some(self.add_event(
move |event: stdweb::web::event::GamepadDisconnectedEvent| {
let gamepad = event.gamepad();
handler(gamepad::Gamepad::new(gamepad));
},
));
}
fn add_event<E, F>(&self, mut handler: F) -> web::EventListenerHandle
where
E: web::event::ConcreteEvent,
F: 'static + FnMut(E),
{
self.raw.add_event_listener(move |event: E| {
event.stop_propagation();
event.cancel_bubble();
handler(event);
})
}
}

View File

@@ -1,14 +1,16 @@
use super::event;
use super::utils;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
use web_sys::{
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent,
};
pub struct Canvas {
raw: HtmlCanvasElement,
@@ -107,46 +109,56 @@ impl Canvas {
where
F: 'static + FnMut(),
{
self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| {
handler();
}));
self.on_blur = Some(self.add_event(
"blur",
move |_: FocusEvent| {
handler();
},
));
}
pub fn on_focus<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| {
handler();
}));
self.on_focus = Some(self.add_event(
"focus",
move |_: FocusEvent| {
handler();
},
));
}
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_release =
Some(self.add_user_event("keyup", move |event: KeyboardEvent| {
self.on_keyboard_release = Some(self.add_user_event(
"keyup",
move |event: KeyboardEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
);
}));
},
));
}
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_press =
Some(self.add_user_event("keydown", move |event: KeyboardEvent| {
self.on_keyboard_press = Some(self.add_user_event(
"keydown",
move |event: KeyboardEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
);
}));
},
));
}
pub fn on_received_character<F>(&mut self, mut handler: F)
@@ -161,83 +173,85 @@ impl Canvas {
self.on_received_character = Some(self.add_user_event(
"keypress",
move |event: KeyboardEvent| {
handler(event::codepoint(&event));
handler(utils::codepoint(&event));
},
));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| {
handler(event.pointer_id());
}));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| {
handler(event.pointer_id());
}));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
F: 'static + FnMut(i32, MouseButton),
{
self.on_mouse_release = Some(self.add_user_event(
self.on_mouse_release = Some(self.add_event(
"pointerup",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
handler(event.pointer_id(), utils::mouse_button(&event));
},
));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
F: 'static + FnMut(i32, MouseButton),
{
self.on_mouse_press = Some(self.add_user_event(
self.on_mouse_press = Some(self.add_event(
"pointerdown",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
handler(event.pointer_id(), utils::mouse_button(&event));
},
));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, (f64, f64)),
{
self.on_mouse_wheel = Some(self.add_event(
"wheel",
move |event: WheelEvent| {
let delta = utils::mouse_scroll_delta(&event);
handler(0, delta);
},
));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_cursor_leave = Some(self.add_event(
"pointerout",
move |_event: PointerEvent| {
handler();
},
));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_cursor_enter = Some(self.add_event(
"pointerover",
move |_event: PointerEvent| {
handler();
},
));
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
F: 'static + FnMut(LogicalPosition, ModifiersState),
{
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{
self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| {
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
}
}));
self.on_cursor_move = Some(self.add_event(
"pointermove",
move |event: PointerEvent| {
handler(
utils::mouse_position(&event),
utils::mouse_modifiers(&event),
);
},
));
}
pub fn on_fullscreen_change<F>(&mut self, mut handler: F)
@@ -248,7 +262,11 @@ impl Canvas {
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
}
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
fn add_event<E, F>(
&self,
event_name: &str,
mut handler: F,
) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
@@ -273,7 +291,11 @@ impl Canvas {
// The difference between add_event and add_user_event is that the latter has a special meaning
// for browser security. A user event is a deliberate action by the user (like a mouse or key
// press) and is the only time things like a fullscreen request may be successfully completed.)
fn add_user_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
fn add_user_event<E, F>(
&self,
event_name: &str,
mut handler: F,
) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
@@ -281,16 +303,19 @@ impl Canvas {
let wants_fullscreen = self.wants_fullscreen.clone();
let canvas = self.raw.clone();
self.add_event(event_name, move |event: E| {
handler(event);
self.add_event(
event_name,
move |event: E| {
handler(event);
if *wants_fullscreen.borrow() {
canvas
.request_fullscreen()
.expect("Failed to enter fullscreen");
*wants_fullscreen.borrow_mut() = false;
}
})
if *wants_fullscreen.borrow() {
canvas
.request_fullscreen()
.expect("Failed to enter fullscreen");
*wants_fullscreen.borrow_mut() = false;
}
},
)
}
pub fn request_fullscreen(&self) {

View File

@@ -0,0 +1,75 @@
use super::utils;
use crate::platform_impl::platform::device;
use std::cmp::PartialEq;
#[derive(Debug)]
pub struct Gamepad {
pub(crate) index: i32,
pub(crate) raw: web_sys::Gamepad,
pub(crate) mapping: device::gamepad::Mapping,
}
impl Gamepad {
pub fn new(raw: web_sys::Gamepad) -> Self {
let mapping = utils::create_mapping(&raw);
Self {
index: raw.index() as i32,
raw,
mapping,
}
}
// An integer that is auto-incremented to be unique for each device
// currently connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
pub fn index(&self) -> i32 {
self.raw.index() as i32
}
// A string containing some information about the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
pub fn id(&self) -> String {
self.raw.id()
}
// A boolean indicating whether the gamepad is still connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
pub fn connected(&self) -> bool {
self.raw.connected()
}
// An array containing GamepadHapticActuator objects,
// each of which represents haptic feedback hardware available on the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators
pub fn vibrate(&self, value: f64, duration: f64) {
for actuator in self.raw.haptic_actuators().values() {
actuator.ok().and_then(|a| {
let actuator: web_sys::GamepadHapticActuator = a.into();
match actuator.type_() {
web_sys::GamepadHapticActuatorType::Vibration => {
actuator.pulse(value, duration).ok()
}
_ => None,
}
});
}
}
}
impl Clone for Gamepad {
fn clone(&self) -> Self {
Self {
index: self.index,
raw: self.raw.clone(),
mapping: self.mapping.clone(),
}
}
}
impl PartialEq for Gamepad {
#[inline(always)]
fn eq(&self, othr: &Self) -> bool {
self.raw.index() == othr.raw.index()
}
}

View File

@@ -1,9 +1,11 @@
mod canvas;
mod event;
pub mod gamepad;
mod timeout;
mod utils;
pub mod window;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
pub use canvas::Canvas;
pub use timeout::Timeout;
use crate::dpi::LogicalSize;
use crate::platform::web::WindowExtWebSys;
@@ -68,3 +70,16 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
None => false,
}
}
pub fn get_gamepads() -> impl Iterator<Item = gamepad::Gamepad> {
let mut gamepads: Vec<gamepad::Gamepad> = Vec::new();
let web_gamepads = web_sys::window().unwrap().navigator().get_gamepads().ok().unwrap();
for index in 0..web_gamepads.length() {
let jsvalue = web_gamepads.get(index);
if !jsvalue.is_null() {
let gamepad: web_sys::Gamepad = jsvalue.into();
gamepads.push(gamepad::Gamepad::new(gamepad));
}
}
gamepads.into_iter()
}

View File

@@ -1,8 +1,9 @@
use crate::dpi::LogicalPosition;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::platform_impl::platform;
use std::convert::TryInto;
use web_sys::{KeyboardEvent, MouseEvent, WheelEvent};
use web_sys::{Gamepad, GamepadButton, GamepadMappingType, KeyboardEvent, MouseEvent, WheelEvent};
pub fn mouse_button(event: &MouseEvent) -> MouseButton {
match event.button() {
@@ -29,15 +30,10 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition {
}
}
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
pub fn mouse_scroll_delta(event: &WheelEvent) -> (f64, f64) {
let x = event.delta_x();
let y = event.delta_y();
match event.delta_mode() {
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
_ => None,
}
(x, y)
}
pub fn scan_code(event: &KeyboardEvent) -> ScanCode {
@@ -225,3 +221,44 @@ pub fn codepoint(event: &KeyboardEvent) -> char {
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
event.key().chars().next().unwrap()
}
pub fn create_mapping(raw: &Gamepad) -> platform::device::gamepad::Mapping {
match raw.mapping() {
GamepadMappingType::Standard => {
let mut buttons = [false; 16];
let mut axes = [0.0; 6];
let gbuttons = raw.buttons();
for index in 0..buttons.len() {
let button: GamepadButton = gbuttons.get(index as u32).into();
buttons[index] = button.pressed();
}
let gaxes = raw.axes();
for index in 0..axes.len() {
let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0);
axes[index] = axe;
}
platform::device::gamepad::Mapping::Standard { buttons, axes }
}
_ => {
let mut buttons: Vec<bool> = Vec::new();
let mut axes: Vec<f64> = Vec::new();
let gbuttons = raw.buttons();
for index in 0..gbuttons.length() {
let button: GamepadButton = gbuttons.get(index as u32).into();
buttons.push(button.pressed());
}
let gaxes = raw.axes();
for index in 0..gaxes.length() {
let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0);
axes.push(axe);
}
platform::device::gamepad::Mapping::NoMapping { buttons, axes }
}
}
}

View File

@@ -0,0 +1,88 @@
use super::gamepad;
use crate::error::OsError as RootOE;
use crate::platform_impl::OsError;
use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::GamepadEvent;
#[derive(Debug)]
pub struct Shared(pub Rc<RefCell<Window>>);
#[derive(Debug)]
pub struct Window {
raw: web_sys::Window,
on_gamepad_connected: Option<Closure<dyn FnMut(GamepadEvent)>>,
on_gamepad_disconnected: Option<Closure<dyn FnMut(GamepadEvent)>>,
}
impl Shared {
pub fn create() -> Result<Self, RootOE> {
let global = Window::create()?;
Ok(Shared(Rc::new(RefCell::new(global))))
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
Shared(self.0.clone())
}
}
impl Window {
pub fn create() -> Result<Self, RootOE> {
let raw =
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
Ok(Window {
raw,
on_gamepad_connected: None,
on_gamepad_disconnected: None,
})
}
pub fn on_gamepad_connected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_connected = Some(self.add_event(
"gamepadconnected",
move |event: GamepadEvent| {
let gamepad = event
.gamepad()
.expect("[gamepadconnected] expected gamepad");
handler(gamepad::Gamepad::new(gamepad));
},
))
}
pub fn on_gamepad_disconnected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_disconnected = Some(self.add_event(
"gamepaddisconnected",
move |event: GamepadEvent| {
let gamepad = event
.gamepad()
.expect("[gamepaddisconnected] expected gamepad");
handler(gamepad::Gamepad::new(gamepad));
},
))
}
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
{
let closure = Closure::wrap(Box::new(move |event: E| {
handler(event);
}) as Box<dyn FnMut(E)>);
self.raw
.add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref())
.expect("Failed to add event listener with callback");
closure
}
}

View File

@@ -34,6 +34,7 @@ impl Window {
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
target.register_global_events()?;
target.register(&mut canvas, id);
let window = Window {

View File

@@ -17,7 +17,7 @@ use parking_lot::Mutex;
use std::{
any::Any,
cell::RefCell,
collections::VecDeque,
collections::{HashMap, VecDeque},
marker::PhantomData,
mem, panic, ptr,
rc::Rc,
@@ -45,8 +45,11 @@ use winapi::{
};
use crate::{
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
event::{DeviceEvent, Event, Force, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent},
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
event::{
device::{GamepadEvent, HidEvent, KeyboardEvent, MouseEvent},
Event, Force, KeyboardInput, MouseButton, StartCause, Touch, TouchPhase, WindowEvent,
},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::platform::{
dpi::{
@@ -55,11 +58,12 @@ use crate::{
drop_handler::FileDropHandler,
event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey},
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,
window::adjust_size,
window_state::{CursorFlags, WindowFlags, WindowState},
wrap_device_id, WindowId, DEVICE_ID,
GamepadHandle, HidId, KeyboardId, MouseId, WindowId,
},
window::{Fullscreen, WindowId as RootWindowId},
};
@@ -99,24 +103,32 @@ lazy_static! {
pub(crate) struct SubclassInput<T> {
pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: EventLoopRunnerShared<T>,
pub shared_data: Rc<SubclassSharedData<T>>,
pub file_drop_handler: FileDropHandler,
}
impl<T> SubclassInput<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> {
event_loop_runner: EventLoopRunnerShared<T>,
shared_data: Rc<SubclassSharedData<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> {
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,
trigger_newevents_on_redraw: Arc<AtomicBool>,
thread_msg_target: HWND,
pub(crate) runner_shared: EventLoopRunnerShared<T>,
pub(crate) shared_data: Rc<SubclassSharedData<T>>,
}
macro_rules! main_thread_check {
@@ -167,12 +179,15 @@ impl<T: 'static> EventLoop<T> {
pub fn new_dpi_unaware_any_thread() -> EventLoop<T> {
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
let runner_shared = Rc::new(ELRShared {
runner: RefCell::new(None),
buffer: RefCell::new(VecDeque::new()),
let shared_data = Rc::new(SubclassSharedData {
runner_shared: ELRShared {
runner: RefCell::new(None),
buffer: RefCell::new(VecDeque::new()),
},
active_device_ids: RefCell::new(HashMap::default()),
});
let (thread_msg_target, thread_msg_sender) =
thread_event_target_window(runner_shared.clone());
thread_event_target_window(shared_data.clone());
EventLoop {
thread_msg_sender,
@@ -181,7 +196,7 @@ impl<T: 'static> EventLoop<T> {
thread_id,
trigger_newevents_on_redraw: Arc::new(AtomicBool::new(true)),
thread_msg_target,
runner_shared,
shared_data,
},
_marker: PhantomData,
},
@@ -212,10 +227,10 @@ impl<T: 'static> EventLoop<T> {
})
};
{
let runner_shared = self.window_target.p.runner_shared.clone();
let mut runner_ref = runner_shared.runner.borrow_mut();
let shared_data = self.window_target.p.shared_data.clone();
let mut runner_ref = shared_data.runner_shared.runner.borrow_mut();
loop {
let event = runner_shared.buffer.borrow_mut().pop_front();
let event = shared_data.runner_shared.buffer.borrow_mut().pop_front();
match event {
Some(e) => {
runner.process_event(e);
@@ -230,6 +245,7 @@ impl<T: 'static> EventLoop<T> {
() => {
self.window_target
.p
.shared_data
.runner_shared
.runner
.borrow_mut()
@@ -280,7 +296,13 @@ impl<T: 'static> EventLoop<T> {
}
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> {
@@ -289,6 +311,70 @@ impl<T: 'static> EventLoop<T> {
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> {
@@ -317,7 +403,10 @@ fn main_thread_id() -> DWORD {
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> {
runner: RefCell<Option<EventLoopRunner<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 {
let window = winuser::CreateWindowExW(
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 subclass_input = ThreadMsgTargetSubclassInput {
event_loop_runner,
shared_data,
user_event_receiver: rx,
};
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);
// Set up raw input
raw_input::register_for_raw_input(window);
(window, tx)
}
}
@@ -926,14 +1018,14 @@ unsafe extern "system" fn public_window_callback<T>(
match msg {
winuser::WM_ENTERSIZEMOVE => {
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
if let Some(ref mut runner) = *runner {
runner.in_modal_loop = true;
}
0
}
winuser::WM_EXITSIZEMOVE => {
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
if let Some(ref mut runner) = *runner {
runner.in_modal_loop = false;
}
@@ -982,7 +1074,7 @@ unsafe extern "system" fn public_window_callback<T>(
_ if msg == *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID => {
use crate::event::WindowEvent::RedrawRequested;
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
let mut runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
subclass_input.window_state.lock().queued_out_of_band_redraw = false;
if let Some(ref mut runner) = *runner {
// 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 {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorEntered {
device_id: DEVICE_ID,
},
event: CursorEntered,
});
// Calling TrackMouseEvent in order to receive mouse leave events.
@@ -1193,7 +1283,6 @@ unsafe extern "system" fn public_window_callback<T>(
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorMoved {
device_id: DEVICE_ID,
position,
modifiers: event::get_key_mods(),
},
@@ -1213,9 +1302,7 @@ unsafe extern "system" fn public_window_callback<T>(
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorLeft {
device_id: DEVICE_ID,
},
event: CursorLeft,
});
0
@@ -1231,7 +1318,6 @@ unsafe extern "system" fn public_window_callback<T>(
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta: LineDelta(0.0, value),
phase: TouchPhase::Moved,
modifiers: event::get_key_mods(),
@@ -1251,7 +1337,6 @@ unsafe extern "system" fn public_window_callback<T>(
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta: LineDelta(value, 0.0),
phase: TouchPhase::Moved,
modifiers: event::get_key_mods(),
@@ -1269,15 +1354,12 @@ unsafe extern "system" fn public_window_callback<T>(
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: Pressed,
scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
},
},
event: WindowEvent::KeyboardInput(KeyboardInput {
state: Pressed,
scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
}),
});
// Windows doesn't emit a delete character by default, but in order to make it
// consistent with the other platforms we'll emit a delete character here.
@@ -1297,31 +1379,27 @@ unsafe extern "system" fn public_window_callback<T>(
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: Released,
scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
},
},
event: WindowEvent::KeyboardInput(KeyboardInput {
state: Released,
scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
}),
});
}
0
}
winuser::WM_LBUTTONDOWN => {
use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput};
use crate::event::{ElementState::Pressed, WindowEvent::MouseInput};
capture_mouse(window, &mut *subclass_input.window_state.lock());
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Pressed,
button: Left,
button: MouseButton::Left,
modifiers: event::get_key_mods(),
},
});
@@ -1329,18 +1407,15 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_LBUTTONUP => {
use crate::event::{
ElementState::Released, MouseButton::Left, WindowEvent::MouseInput,
};
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
release_mouse(&mut *subclass_input.window_state.lock());
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Released,
button: Left,
button: MouseButton::Left,
modifiers: event::get_key_mods(),
},
});
@@ -1348,18 +1423,15 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_RBUTTONDOWN => {
use crate::event::{
ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput,
};
use crate::event::{ElementState::Pressed, WindowEvent::MouseInput};
capture_mouse(window, &mut *subclass_input.window_state.lock());
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Pressed,
button: Right,
button: MouseButton::Right,
modifiers: event::get_key_mods(),
},
});
@@ -1367,18 +1439,15 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_RBUTTONUP => {
use crate::event::{
ElementState::Released, MouseButton::Right, WindowEvent::MouseInput,
};
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
release_mouse(&mut *subclass_input.window_state.lock());
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Released,
button: Right,
button: MouseButton::Right,
modifiers: event::get_key_mods(),
},
});
@@ -1386,18 +1455,15 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_MBUTTONDOWN => {
use crate::event::{
ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput,
};
use crate::event::{ElementState::Pressed, WindowEvent::MouseInput};
capture_mouse(window, &mut *subclass_input.window_state.lock());
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Pressed,
button: Middle,
button: MouseButton::Middle,
modifiers: event::get_key_mods(),
},
});
@@ -1405,18 +1471,15 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_MBUTTONUP => {
use crate::event::{
ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput,
};
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
release_mouse(&mut *subclass_input.window_state.lock());
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Released,
button: Middle,
button: MouseButton::Middle,
modifiers: event::get_key_mods(),
},
});
@@ -1424,9 +1487,7 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_XBUTTONDOWN => {
use crate::event::{
ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput,
};
use crate::event::{ElementState::Pressed, WindowEvent::MouseInput};
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
capture_mouse(window, &mut *subclass_input.window_state.lock());
@@ -1434,9 +1495,8 @@ unsafe extern "system" fn public_window_callback<T>(
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Pressed,
button: Other(xbutton as u8),
button: MouseButton::Other(xbutton as u8),
modifiers: event::get_key_mods(),
},
});
@@ -1444,9 +1504,7 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_XBUTTONUP => {
use crate::event::{
ElementState::Released, MouseButton::Other, WindowEvent::MouseInput,
};
use crate::event::{ElementState::Released, WindowEvent::MouseInput};
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
release_mouse(&mut *subclass_input.window_state.lock());
@@ -1454,129 +1512,14 @@ unsafe extern "system" fn public_window_callback<T>(
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
device_id: DEVICE_ID,
state: Released,
button: Other(xbutton as u8),
button: MouseButton::Other(xbutton as u8),
modifiers: event::get_key_mods(),
},
});
0
}
winuser::WM_INPUT_DEVICE_CHANGE => {
let event = match wparam as _ {
winuser::GIDC_ARRIVAL => DeviceEvent::Added,
winuser::GIDC_REMOVAL => DeviceEvent::Removed,
_ => unreachable!(),
};
subclass_input.send_event(Event::DeviceEvent {
device_id: wrap_device_id(lparam as _),
event,
});
0
}
winuser::WM_INPUT => {
use crate::event::{
DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel},
ElementState::{Pressed, Released},
MouseScrollDelta::LineDelta,
};
if let Some(data) = get_raw_input_data(lparam as _) {
let device_id = wrap_device_id(data.header.hDevice as _);
if data.header.dwType == winuser::RIM_TYPEMOUSE {
let mouse = data.data.mouse();
if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) {
let x = mouse.lLastX as f64;
let y = mouse.lLastY as f64;
if x != 0.0 {
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: Motion { axis: 0, value: x },
});
}
if y != 0.0 {
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: Motion { axis: 1, value: y },
});
}
if x != 0.0 || y != 0.0 {
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: MouseMotion { delta: (x, y) },
});
}
}
if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) {
let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA;
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: MouseWheel {
delta: LineDelta(0.0, delta as f32),
},
});
}
let button_state = get_raw_mouse_button_state(mouse.usButtonFlags);
// Left, middle, and right, respectively.
for (index, state) in button_state.iter().enumerate() {
if let Some(state) = *state {
// This gives us consistency with X11, since there doesn't
// seem to be anything else reasonable to do for a mouse
// button ID.
let button = (index + 1) as _;
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: Button { button, state },
});
}
}
} else if data.header.dwType == winuser::RIM_TYPEKEYBOARD {
let keyboard = data.data.keyboard();
let pressed = keyboard.Message == winuser::WM_KEYDOWN
|| keyboard.Message == winuser::WM_SYSKEYDOWN;
let released = keyboard.Message == winuser::WM_KEYUP
|| keyboard.Message == winuser::WM_SYSKEYUP;
if pressed || released {
let state = if pressed { Pressed } else { Released };
let scancode = keyboard.MakeCode as _;
let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _)
| util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _);
if let Some((vkey, scancode)) =
handle_extended_keys(keyboard.VKey as _, scancode, extended)
{
let virtual_keycode = vkey_to_winit_vkey(vkey);
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: Key(KeyboardInput {
scancode,
state,
virtual_keycode,
modifiers: event::get_key_mods(),
}),
});
}
}
}
}
commctrl::DefSubclassProc(window, msg, wparam, lparam)
}
winuser::WM_TOUCH => {
let pcount = LOWORD(wparam as DWORD) as usize;
let mut inputs = Vec::with_capacity(pcount);
@@ -1618,7 +1561,6 @@ unsafe extern "system" fn public_window_callback<T>(
location,
force: None, // WM_TOUCH doesn't support pressure information
id: input.dwID as u64,
device_id: DEVICE_ID,
}),
});
}
@@ -1758,7 +1700,6 @@ unsafe extern "system" fn public_window_callback<T>(
location,
force,
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 runner = subclass_input.event_loop_runner.runner.borrow_mut();
let runner = subclass_input.shared_data.runner_shared.runner.borrow_mut();
if let Some(ref runner) = *runner {
runner.in_modal_loop
} else {
@@ -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 {
runner.events_cleared();
match runner.control_flow {
@@ -2026,6 +1967,230 @@ unsafe extern "system" fn thread_event_target_callback<T>(
}
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 let Ok(event) = subclass_input.user_event_receiver.recv() {
subclass_input.send_event(Event::UserEvent(event));

View 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()),
}
}
}

View File

@@ -1,14 +1,35 @@
#![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::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
gamepad::GamepadShared,
monitor::{MonitorHandle, VideoMode},
window::Window,
};
use crate::{event::DeviceId as RootDeviceId, window::Icon};
use crate::{event::device, window::Icon};
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {
@@ -26,34 +47,6 @@ pub struct Cursor(pub *const winapi::ctypes::wchar_t);
unsafe impl Send 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)]
pub struct WindowId(HWND);
unsafe impl Send for WindowId {}
@@ -61,20 +54,142 @@ unsafe impl Sync for WindowId {}
impl WindowId {
pub unsafe fn dummy() -> Self {
use std::ptr::null_mut;
WindowId(null_mut())
WindowId(ptr::null_mut())
}
}
#[macro_use]
mod util;
mod dpi;
mod drop_handler;
mod event;
mod event_loop;
mod icon;
mod monitor;
mod raw_input;
mod window;
mod window_state;
macro_rules! device_id {
($name:ident, $enumerate:ident) => {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct $name(HANDLE);
unsafe impl Send for $name {}
unsafe impl Sync for $name {}
impl $name {
pub unsafe fn dummy() -> Self {
Self(ptr::null_mut())
}
pub fn persistent_identifier(&self) -> Option<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);
}
}

View File

@@ -1,27 +1,43 @@
use std::{
cmp::max,
fmt,
mem::{self, size_of},
ptr,
ptr, slice,
};
use winapi::{
ctypes::wchar_t,
shared::{
hidusage::{HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC},
minwindef::{TRUE, UINT, USHORT},
hidpi::{
HidP_GetButtonCaps, HidP_GetCaps, HidP_GetScaledUsageValue, HidP_GetUsageValue,
HidP_GetUsagesEx, HidP_GetValueCaps, HidP_Input, HIDP_STATUS_SUCCESS, HIDP_VALUE_CAPS,
PHIDP_PREPARSED_DATA,
},
hidusage::{
HID_USAGE_GENERIC_GAMEPAD, HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_GENERIC_KEYBOARD,
HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC,
},
minwindef::{INT, TRUE, UINT, USHORT},
windef::HWND,
},
um::{
winnt::HANDLE,
winnt::{HANDLE, PCHAR},
winuser::{
self, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER,
RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO,
RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT,
RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RIDI_PREPARSEDDATA,
RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE,
RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
},
},
};
use crate::{event::ElementState, platform_impl::platform::util};
use crate::{
event::{
device::{GamepadAxis, GamepadEvent},
ElementState,
},
platform_impl::platform::util,
};
#[allow(dead_code)]
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
@@ -52,7 +68,6 @@ pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
Some(buffer)
}
#[allow(dead_code)]
pub enum RawDeviceInfo {
Mouse(RID_DEVICE_INFO_MOUSE),
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> {
let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() };
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
info.cbSize = info_size;
let mut minimum_size = 0;
let mut data_size = info_size;
let status = unsafe {
winuser::GetRawInputDeviceInfoW(
handle,
RIDI_DEVICEINFO,
&mut info as *mut _ as _,
&mut minimum_size,
&mut data_size,
)
};
} as INT;
if status == UINT::max_value() || status == 0 {
if status <= 0 {
return None;
}
debug_assert_eq!(info_size, status);
debug_assert_eq!(info_size, status as _);
Some(info.into())
}
@@ -130,6 +144,43 @@ pub fn get_raw_input_device_name(handle: HANDLE) -> Option<String> {
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 {
let device_size = size_of::<RAWINPUTDEVICE>() as UINT;
@@ -140,12 +191,12 @@ pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
success == TRUE
}
pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool {
// RIDEV_DEVNOTIFY: receive hotplug events
// RIDEV_INPUTSINK: receive events even if we're not in the foreground
pub fn register_for_raw_input(window_handle: HWND) -> bool {
// `RIDEV_DEVNOTIFY`: receive hotplug events
// `RIDEV_INPUTSINK`: receive events even if we're not in the foreground
let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
let devices: [RAWINPUTDEVICE; 2] = [
let devices: [RAWINPUTDEVICE; 5] = [
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_MOUSE,
@@ -158,27 +209,182 @@ pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> boo
dwFlags: flags,
hwndTarget: window_handle,
},
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_JOYSTICK,
dwFlags: flags,
hwndTarget: window_handle,
},
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_GAMEPAD,
dwFlags: flags,
hwndTarget: window_handle,
},
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: 0x08, // multi-axis
dwFlags: flags,
hwndTarget: window_handle,
},
];
register_raw_input_devices(&devices)
}
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RAWINPUT> {
let mut data: RAWINPUT = unsafe { mem::zeroed() };
let mut data_size = size_of::<RAWINPUT>() as UINT;
pub enum RawInputData {
Mouse {
device_handle: HANDLE,
raw_mouse: winuser::RAWMOUSE,
},
Keyboard {
device_handle: HANDLE,
raw_keyboard: winuser::RAWKEYBOARD,
},
Hid {
device_handle: HANDLE,
raw_hid: RawHidData,
},
}
pub struct RawHidData {
pub hid_input_size: u32,
pub hid_input_count: u32,
pub raw_input: Box<[u8]>,
}
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RawInputData> {
let mut data_size = 0;
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(
handle,
RID_INPUT,
&mut data as *mut _ as _,
ptr::null_mut(),
&mut data_size,
header_size,
)
};
if status == UINT::max_value() || status == 0 {
let (status, data): (INT, RawInputData);
if data_size <= size_of::<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;
}
@@ -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,
@@ -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_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.
// }
}

View File

@@ -1,6 +1,5 @@
use std::{
io, mem,
ops::BitAnd,
os::raw::c_void,
ptr, slice,
sync::atomic::{AtomicBool, Ordering},
@@ -21,6 +20,8 @@ use winapi::{
},
};
pub use crate::util::*;
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
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 {
String::from_utf16_lossy(wchar).to_string()
}

View File

@@ -41,9 +41,7 @@ use crate::{
REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID,
},
icon::{self, IconType, WinIcon},
monitor,
raw_input::register_all_mice_and_keyboards_for_raw_input,
util,
monitor, util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId,
},
@@ -86,12 +84,12 @@ impl Window {
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
}
let file_drop_runner = event_loop.runner_shared.clone();
let shared_data = event_loop.shared_data.clone();
let file_drop_handler = FileDropHandler::new(
win.window.0,
Box::new(move |event| {
if let Ok(e) = event.map_nonuser_event() {
file_drop_runner.send_event(e)
shared_data.runner_shared.send_event(e)
}
}),
);
@@ -107,7 +105,7 @@ impl Window {
let subclass_input = event_loop::SubclassInput {
window_state: win.window_state.clone(),
event_loop_runner: event_loop.runner_shared.clone(),
shared_data: event_loop.shared_data.clone(),
file_drop_handler,
};
@@ -839,9 +837,6 @@ unsafe fn init<T: 'static>(
WindowWrapper(handle)
};
// Set up raw input
register_all_mice_and_keyboards_for_raw_input(real_window.0);
// Register for touch events if applicable
{
let digitizer = winuser::GetSystemMetrics(winuser::SM_DIGITIZER) as u32;

View 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
View 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
}

View File

@@ -22,6 +22,8 @@ fn window_send() {
fn ids_send() {
// ensures that the various `..Id` types implement `Send`
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>();
}