Compare commits

...

21 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
Charles Helmich
07bdd3e218 Fix ReceivedCharacter not working with Alt held on Windows (#1282)
* Fix ReceivedCharacter with Alt held on Windows

* Update CHANGELOG.md
2019-11-29 15:49:43 -05:00
Héctor Ramón
35a11ae24f Stop emitting corporate characters in macOS (#1254) 2019-11-27 12:14:36 +03:00
Ryan G
3d28283a81 Only use 'extern crate stdweb' on web targets (#1291)
The 'extern crate' declaration shouldn't be there even if the stdweb feature is on, unless the crate is being compiled for web.
2019-11-27 00:38:18 -05:00
daxpedda
aec5a9fa09 Stop refocusing window when switching from fullscreen to windowed (#1285)
* Stop refocusing window when switching from fullscreen to windowed

* Update Changelog.
2019-11-26 22:49:14 -05:00
Manish Goregaokar
0f94f62025 Bump parking_lot to 0.10 (#1287) 2019-11-25 18:05:44 -07:00
Murarth
a95ebc5ee6 X11: Fix incorrect modifiers when events are missed (#1279)
* X11: Fix incorrect modifiers when events are missed

* Syncs modifier state with state data in X key/button/motion events.
* Fixes modifier state in XWayland, as xinput2 raw input events will
  not be received when a window does not have focus.
* Removes `impl From<_> for ModifiersState` on X11/Wayland API types,
  replacing them with `pub(crate)` methods.

* Cleanup modifier state update using a macro

* Remove keys from modifier state when updating
2019-11-22 17:11:30 -07:00
Murarth
a70ac1531e X11: Fix window creation hangs when another application is fullscreen (#1248)
* X11: Fix window creation hangs when another application is fullscreen

Previously, the X11 backend would block until a `VisibilityNotify` event
is received when creating a Window that is visible or when calling
`set_visible(true)` on a Window that is not currently visible. This
could cause winit to hang in situations where the WM does not quickly
send this event to the application, such as another window being
fullscreen at the time.

This behavior existed to prevent an X protocol error caused by setting
fullscreen state on an invisible window. This fix instead stores desired
fullscreen state when `set_fullscreen` is called (iff the window is not
visible or not yet visible) and issues X commands to set fullscreen
state when a `VisibilityNotify` event is received through the normal
processing of events in the event loop.

* Add window_debug example to facilitate testing

* Add a CHANGELOG entry

* Call `XUnmapWindow` if `VisibilityNotify` is received on an invisible window
2019-11-22 17:11:04 -07:00
ariesfluctus
b6e8dd0d8a Fixed typo (#1273) 2019-11-14 00:10:31 -05:00
Murarth
af80ce842d Fix cargo doc on nightly builds (#1274) 2019-11-12 16:51:46 -07:00
Osspial
08bae037f0 Use paths for intra-doc links, and verify that links are valid in CI (#1244)
* Use paths to generate intra-doc links

* Add entry to PR checklist
2019-11-11 16:05:59 -07:00
Thom Chiovoloni
cd39327ea2 Fix invalid_value lint triggering on mem::zeroed of CFRunLoopSourceContext (#1271) 2019-11-11 15:50:31 -07:00
Murarth
9828f368d6 X11: Fix misreporting DPI factor at startup (#1252)
* X11: Fix misreporting DPI factor at startup

* Add a CHANGELOG entry
2019-11-10 13:55:29 -07:00
Murarth
1ed15c7ec7 X11: Fix events not being reported when using run_return (#1245)
* X11: Fix events not being reported using `run_return`

* Adapt examples to be more practical

* Add CHANGELOG entry
2019-11-10 11:24:43 -07:00
Murarth
c66784995d X11: Fix modifiers being reported after release (#1262)
* X11: Fix modifiers being reported after release

* Moves `ModifiersChanged` variant from `WindowEvent` to `DeviceEvent`

* Add CHANGELOG entry
2019-11-10 00:16:44 -07:00
Murarth
dba21c06ed Run cargo fmt on Rust stable 1.39.0 (#1264) 2019-11-07 13:48:34 -07:00
Kirill Chibisov
72fc6a74ec on Wayland, drop resize events equal to the current window size (#1249)
* on Wayland, drop resize events equal to the current window size

* Add changelog entry
2019-10-31 20:45:45 -04:00
Michael Tang
f916311744 Add error message when building for web without selecting a feature. (#1253) 2019-10-31 12:45:12 -04:00
David Sinclair
05a1f4280c Miscellaneous small changes (#1238)
* Use a slice instead of a new object

* Remove unnecessary 'into_iter'

* Use 'and_then' instead of 'map_or'
2019-10-23 19:45:25 -04:00
Bogaevsky
6608a0241d macOS: fixes app termination (#1234)
* fixes app termination

* applies fmt

* fmt all

* updates changelog

* keeps formating consistent

* fixes misstype
2019-10-23 17:24:50 +03:00
80 changed files with 4269 additions and 1133 deletions

View File

@@ -1,6 +1,7 @@
- [ ] Tested on all platforms changed
- [ ] Compilation warnings were addressed
- [ ] `cargo fmt` has been run on this branch
- [ ] `cargo doc` builds successfully
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality

1
.gitignore vendored
View File

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

View File

@@ -81,6 +81,8 @@ install:
script:
- cargo +stable fmt --all -- --check
# Ensure that the documentation builds properly.
- cargo doc --no-deps
# Install cargo-web to build stdweb
- if [[ $WEB = "web" ]]; then cargo install -f cargo-web; fi
# Build without serde then with serde

View File

@@ -1,5 +1,14 @@
# Unreleased
- On macOS, fix application termination on `ControlFlow::Exit`
- On Windows, fix missing `ReceivedCharacter` events when Alt is held.
- On macOS, stop emitting private corporate characters in `ReceivedCharacter` events.
- On X11, fix misreporting DPI factor at startup.
- On X11, fix events not being reported when using `run_return`.
- On X11, fix key modifiers being incorrectly reported.
- On X11, fix window creation hanging when another window is fullscreen.
- On Windows, fix focusing unfocused windows when switching from fullscreen to windowed.
# 0.20.0 Alpha 4 (2019-10-18)
- Add web support via the 'stdweb' or 'web-sys' features
@@ -41,6 +50,7 @@
- This is because some platforms cannot run the event loop outside the main thread. Preventing this
reduces the potential for cross-platform compatibility gotchyas.
- On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread.
- On Wayland, drop resize events identical to the current window size.
# 0.20.0 Alpha 3 (2019-08-14)
@@ -140,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]
@@ -83,7 +86,7 @@ x11-dl = "2.18.3"
percent-encoding = "2.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
version = "0.9"
version = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
@@ -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

@@ -32,3 +32,4 @@ build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo doc --no-deps

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

121
examples/window_debug.rs Normal file
View File

@@ -0,0 +1,121 @@
// This example is used by developers to test various window functions.
use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Fullscreen, WindowBuilder},
};
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::from((100, 100)))
.build(&event_loop)
.unwrap();
eprintln!("debugging keys:");
eprintln!(" (E) Enter exclusive fullscreen");
eprintln!(" (F) Toggle borderless fullscreen");
#[cfg(waiting_for_set_minimized)]
eprintln!(" (M) Toggle minimized");
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
#[cfg(waiting_for_set_minimized)]
let mut minimized = false;
let mut maximized = false;
let mut visible = true;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::DeviceEvent {
event:
DeviceEvent::Key(KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
}),
..
} => match key {
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
}
}
VirtualKeyCode::V => {
if !visible {
visible = !visible;
window.set_visible(visible);
}
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => match input {
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize) -> f64 {
size.width * size.height
}
let monitor = window.current_monitor();
if let Some(mode) = monitor.video_modes().max_by(|a, b| {
area(a.size())
.partial_cmp(&area(b.size()))
.expect("NaN in video mode size")
}) {
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
}
VirtualKeyCode::F => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
}
VirtualKeyCode::Q => {
*control_flow = ControlFlow::Exit;
}
VirtualKeyCode::V => {
visible = !visible;
window.set_visible(visible);
}
VirtualKeyCode::X => {
maximized = !maximized;
window.set_maximized(maximized);
}
_ => (),
},
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -9,6 +9,7 @@
target_os = "openbsd"
))]
fn main() {
use std::{thread::sleep, time::Duration};
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -17,36 +18,38 @@ fn main() {
};
let mut event_loop = EventLoop::new();
let window = WindowBuilder::new()
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
println!("Close the window to continue.");
event_loop.run_return(|event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
});
drop(window);
let mut quit = false;
let _window_2 = WindowBuilder::new()
.with_title("A second, fantasticer window!")
.build(&event_loop)
.unwrap();
while !quit {
event_loop.run_return(|event, _, control_flow| {
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{:?}", event);
}
println!("Wa ha ha! You thought that closing the window would finish this?!");
event_loop.run_return(|event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
});
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
quit = true;
*control_flow = ControlFlow::Exit;
}
Event::EventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => *control_flow = ControlFlow::Wait,
}
});
println!("Okay we're done now for real.");
// Sleep for 1/60 second to simulate rendering
sleep(Duration::from_millis(16));
}
}
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]

View File

@@ -28,18 +28,18 @@
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
//!
//! There are two ways to get the DPI factor:
//! - You can track the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) event of your
//! - You can track the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) event of your
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorHandle::hidpi_factor`](../monitor/struct.MonitorHandle.html#method.hidpi_factor), or the
//! [`MonitorHandle::hidpi_factor`](crate::monitor::MonitorHandle::hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::hidpi_factor`](../window/struct.Window.html#method.hidpi_factor), which is roughly equivalent
//! [`Window::hidpi_factor`](crate::window::Window::hidpi_factor), which is roughly equivalent
//! to `window.current_monitor().hidpi_factor()`.
//!
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,
//! the most robust way is to monitor the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged)
//! the most robust way is to monitor the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged)
//! event and dynamically adapt your drawing logic to follow the DPI factor.
//!
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
@@ -49,7 +49,7 @@
//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0.
//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any
//! display to use that 2.0 DPI factor, given the use of the command line.
//! - **X11:** On X11, we calcuate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! - **X11:** On X11, we calculate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
@@ -59,21 +59,21 @@
//!
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a
//! [`Resized`](../event/enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs,
//! [`Resized`](crate::event::WindowEvent::Resized) event, even on platforms where no resize actually occurs,
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
//! [`HiDpiFactorChanged`](../event/enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size.
//! [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) if you're only listening for size.
//!
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
//! framebuffer's size should be in physical pixels.
//!
//! `winit` will send [`Resized`](../enum.WindowEvent.html#variant.Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events
//! `winit` will send [`Resized`](crate::event::WindowEvent::Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
//! these events and process them at the end of the queue.
//!
//! If you never received any [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events,
//! If you never received any [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events,
//! then your window's DPI factor is 1.
/// Checks that the DPI factor is a normal positive `f64`.

View File

@@ -3,17 +3,18 @@
//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get
//! processed and used to modify the program state. For more details, see the root-level documentation.
//!
//! [event_loop_run]: ../event_loop/struct.EventLoop.html#method.run
//! [event_loop_run]: crate::event_loop::EventLoop::run
use instant::Instant;
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,11 +22,15 @@ 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 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.
@@ -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,19 +137,10 @@ pub enum WindowEvent {
Focused(bool),
/// An event from the keyboard has been received.
KeyboardInput {
device_id: DeviceId,
input: KeyboardInput,
},
/// Keyboard modifiers have changed
#[doc(hidden)]
ModifiersChanged { modifiers: ModifiersState },
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.
@@ -150,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,
@@ -165,7 +163,6 @@ pub enum WindowEvent {
/// An mouse button press has been received.
MouseInput {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
modifiers: ModifiersState,
@@ -176,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,76 +189,11 @@ pub enum WindowEvent {
/// * Changing the display's DPI factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different DPI factor.
///
/// For more information about DPI in general, see the [`dpi`](../dpi/index.html) module.
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
HiDpiFactorChanged(f64),
}
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
/// Represents raw hardware events that are not associated with any particular window.
///
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
/// may not match.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug, PartialEq)]
pub enum DeviceEvent {
Added,
Removed,
/// Change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
MouseMotion {
/// (x, y) change in position in unspecified units.
///
/// Different devices may use different units.
delta: (f64, f64),
},
/// Physical scroll event
MouseWheel {
delta: MouseScrollDelta,
},
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
/// that winit supports on this platform, including mouse devices. If the device is a mouse
/// device then this will be reported alongside the MouseMotion event.
Motion {
axis: AxisId,
value: f64,
},
Button {
button: ButtonId,
state: ElementState,
},
Key(KeyboardInput),
Text {
codepoint: char,
},
}
/// Describes a keyboard input event.
/// A keyboard input event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardInput {
@@ -298,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,
}
@@ -326,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
@@ -337,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,
}
@@ -405,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,
@@ -423,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 {
@@ -442,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))]
@@ -641,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

@@ -6,9 +6,9 @@
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
//!
//! [create_proxy]: ./struct.EventLoop.html#method.create_proxy
//! [event_loop_proxy]: ./struct.EventLoopProxy.html
//! [send_event]: ./struct.EventLoopProxy.html#method.send_event
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
use instant::Instant;
use std::ops::Deref;
use std::{error, fmt};
@@ -68,7 +68,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
/// the control flow to `Poll`.
///
/// [events_cleared]: ../event/enum.Event.html#variant.EventsCleared
/// [events_cleared]: crate::event::Event::EventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -139,7 +139,7 @@ impl<T> EventLoop<T> {
///
/// Any values not passed to this function will *not* be dropped.
///
/// [`ControlFlow`]: ./enum.ControlFlow.html
/// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
where

View File

@@ -92,25 +92,26 @@
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the [`Window`].
//!
//! [`EventLoop`]: ./event_loop/struct.EventLoop.html
//! [`EventLoop::new()`]: ./event_loop/struct.EventLoop.html#method.new
//! [event_loop_run]: ./event_loop/struct.EventLoop.html#method.run
//! [`ControlFlow`]: ./event_loop/enum.ControlFlow.html
//! [`Exit`]: ./event_loop/enum.ControlFlow.html#variant.Exit
//! [`Window`]: ./window/struct.Window.html
//! [`WindowBuilder`]: ./window/struct.WindowBuilder.html
//! [window_new]: ./window/struct.Window.html#method.new
//! [window_builder_new]: ./window/struct.WindowBuilder.html#method.new
//! [window_builder_build]: ./window/struct.WindowBuilder.html#method.build
//! [window_id_fn]: ./window/struct.Window.html#method.id
//! [`Event`]: ./event/enum.Event.html
//! [`WindowEvent`]: ./event/enum.WindowEvent.html
//! [`DeviceEvent`]: ./event/enum.DeviceEvent.html
//! [`UserEvent`]: ./event/enum.Event.html#variant.UserEvent
//! [`LoopDestroyed`]: ./event/enum.Event.html#variant.LoopDestroyed
//! [`platform`]: ./platform/index.html
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`Window`]: window::Window
//! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new
//! [window_builder_new]: window::WindowBuilder::new
//! [window_builder_build]: window::WindowBuilder::build
//! [window_id_fn]: window::Window::id
//! [`Event`]: event::Event
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`platform`]: platform
#![deny(rust_2018_idioms)]
#![deny(intra_doc_link_resolution_failure)]
#[allow(unused_imports)]
#[macro_use]
@@ -126,7 +127,7 @@ extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(feature = "std_web")]
#[cfg(all(target_arch = "wasm32", feature = "std_web"))]
extern crate std_web as stdweb;
pub mod dpi;
@@ -137,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

@@ -1,14 +1,14 @@
//! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_id]
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
//! type. This is retreived from one of the following methods, which return an iterator of
//! [`MonitorHandle`][monitor_id]:
//! [`MonitorHandle`][monitor_handle]:
//! - [`EventLoop::available_monitors`][loop_get]
//! - [`Window::available_monitors`][window_get].
//!
//! [monitor_id]: ./struct.MonitorHandle.html
//! [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors
//! [window_get]: ../window/struct.Window.html#method.available_monitors
//! [monitor_handle]: crate::monitor::MonitorHandle
//! [loop_get]: crate::event_loop::EventLoop::available_monitors
//! [window_get]: crate::window::Window::available_monitors
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
@@ -19,7 +19,7 @@ use crate::{
/// Can be acquired with:
/// - [`MonitorHandle::video_modes`][monitor_get].
///
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
/// [monitor_get]: crate::monitor::MonitorHandle::video_modes
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) video_mode: platform_impl::VideoMode,
@@ -108,7 +108,7 @@ impl std::fmt::Display for VideoMode {
///
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
///
/// [`Window`]: ../window/struct.Window.html
/// [`Window`]: crate::window::Window
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle,
@@ -150,7 +150,7 @@ impl MonitorHandle {
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](../dpi/index.html) module for more information.
/// See the [`dpi`](crate::dpi) module for more information.
///
/// ## Platform-specific
///

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

@@ -500,7 +500,9 @@ pub unsafe fn create_window(
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()],
Some(Fullscreen::Borderless(ref monitor)) => {
msg_send![window, setScreen:monitor.ui_screen()]
}
None => (),
}

View File

@@ -54,6 +54,10 @@ impl<T> WindowEventsSink<T> {
}
}
pub fn send_event(&mut self, evt: crate::event::Event<T>) {
self.buffer.push_back(evt);
}
pub fn send_window_event(&mut self, evt: crate::event::WindowEvent, wid: WindowId) {
self.buffer.push_back(crate::event::Event::WindowEvent {
event: evt,
@@ -240,9 +244,7 @@ pub struct EventLoop<T: 'static> {
// Utility for grabbing the cursor and changing visibility
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
user_sender: ::calloop::channel::Sender<T>,
_kbd_source: ::calloop::Source<
::calloop::channel::Channel<(crate::event::WindowEvent, super::WindowId)>,
>,
_kbd_source: ::calloop::Source<::calloop::channel::Channel<crate::event::Event<()>>>,
window_target: RootELW<T>,
}
@@ -296,13 +298,14 @@ impl<T: 'static> EventLoop<T> {
let inner_loop = ::calloop::EventLoop::new().unwrap();
let (kbd_sender, kbd_channel) = ::calloop::channel::channel();
let (kbd_sender, kbd_channel) = ::calloop::channel::channel::<crate::event::Event<()>>();
let kbd_sink = sink.clone();
let kbd_source = inner_loop
.handle()
.insert_source(kbd_channel, move |evt, &mut ()| {
if let ::calloop::channel::Event::Msg((evt, wid)) = evt {
kbd_sink.lock().unwrap().send_window_event(evt, wid);
if let ::calloop::channel::Event::Msg(evt) = evt {
let evt = evt.map_nonuser_event().ok().unwrap();
kbd_sink.lock().unwrap().send_event(evt);
}
})
.unwrap();
@@ -673,15 +676,22 @@ impl<T> EventLoop<T> {
wid,
frame| {
if let Some(frame) = frame {
if let Some((w, h)) = newsize {
frame.resize(w, h);
frame.refresh();
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
sink.send_window_event(
crate::event::WindowEvent::Resized(logical_size),
wid,
);
*size = (w, h);
if let Some(newsize) = newsize {
// Drop resize events equaled to the current size
if newsize != *size {
let (w, h) = newsize;
frame.resize(w, h);
frame.refresh();
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
sink.send_window_event(
crate::event::WindowEvent::Resized(logical_size),
wid,
);
*size = (w, h);
} else {
// Refresh csd, etc, otherwise
frame.refresh();
}
} else if frame_refresh {
frame.refresh();
if !refresh {
@@ -719,7 +729,7 @@ struct SeatManager<T: 'static> {
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
kbd_sender: ::calloop::channel::Sender<crate::event::Event<()>>,
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
pointer_constraints_proxy: Arc<Mutex<Option<ZwpPointerConstraintsV1>>>,
cursor_manager: Arc<Mutex<CursorManager>>,
@@ -764,7 +774,7 @@ impl<T: 'static> SeatManager<T> {
struct SeatData<T> {
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
kbd_sender: ::calloop::channel::Sender<crate::event::Event<()>>,
pointer: Option<wl_pointer::WlPointer>,
relative_pointer: Option<ZwpRelativePointerV1>,
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,

View File

@@ -8,11 +8,13 @@ use smithay_client_toolkit::{
reexports::client::protocol::{wl_keyboard, wl_seat},
};
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
use crate::event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
};
pub fn init_keyboard(
seat: &wl_seat::WlSeat,
sink: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
sink: ::calloop::channel::Sender<crate::event::Event<()>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> wl_keyboard::WlKeyboard {
// { variables to be captured by the closures
@@ -29,12 +31,22 @@ pub fn init_keyboard(
match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
*target.lock().unwrap() = Some(wid);
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
*target.lock().unwrap() = None;
}
KbEvent::Key {
@@ -52,11 +64,10 @@ pub fn init_keyboard(
};
let vkcode = key_to_vkey(rawkey, keysym);
my_sink
.send((
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: rawkey,
@@ -64,8 +75,7 @@ pub fn init_keyboard(
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
},
wid,
))
})
.unwrap();
// send char event only on key press, not release
if let ElementState::Released = state {
@@ -74,7 +84,10 @@ pub fn init_keyboard(
if let Some(txt) = utf8 {
for chr in txt.chars() {
my_sink
.send((WindowEvent::ReceivedCharacter(chr), wid))
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
@@ -84,15 +97,16 @@ pub fn init_keyboard(
KbEvent::Modifiers {
modifiers: event_modifiers,
} => {
let modifiers = event_modifiers.into();
let modifiers = ModifiersState::from_wayland(event_modifiers);
*modifiers_tracker.lock().unwrap() = modifiers;
if let Some(wid) = *target.lock().unwrap() {
my_sink
.send((WindowEvent::ModifiersChanged { modifiers }, wid))
.unwrap();
}
my_sink
.send(Event::DeviceEvent {
device_id: device_id(),
event: DeviceEvent::ModifiersChanged { modifiers },
})
.unwrap();
}
}
},
@@ -101,11 +115,10 @@ pub fn init_keyboard(
let state = ElementState::Pressed;
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
repeat_sink
.send((
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: repeat_event.rawkey,
@@ -113,13 +126,15 @@ pub fn init_keyboard(
modifiers: my_modifiers.lock().unwrap().clone(),
},
},
wid,
))
})
.unwrap();
if let Some(txt) = repeat_event.utf8 {
for chr in txt.chars() {
repeat_sink
.send((WindowEvent::ReceivedCharacter(chr), wid))
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
@@ -147,12 +162,22 @@ pub fn init_keyboard(
move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
target = Some(wid);
}
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
target = None;
}
wl_keyboard::Event::Key { key, state, .. } => {
@@ -163,11 +188,10 @@ pub fn init_keyboard(
_ => unreachable!(),
};
my_sink
.send((
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: key,
@@ -175,8 +199,7 @@ pub fn init_keyboard(
modifiers: ModifiersState::default(),
},
},
wid,
))
})
.unwrap();
}
}
@@ -376,8 +399,8 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
}
}
impl From<keyboard::ModifiersState> for ModifiersState {
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
impl ModifiersState {
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
ModifiersState {
shift: mods.shift,
ctrl: mods.ctrl,
@@ -386,3 +409,11 @@ impl From<keyboard::ModifiersState> for ModifiersState {
}
}
}
fn device_id() -> crate::event::DeviceId {
crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId))
}
fn mk_root_wid(wid: crate::platform_impl::wayland::WindowId) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid))
}

View File

@@ -25,7 +25,6 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) target: Rc<RootELW<T>>,
pub(super) mod_keymap: ModifierKeymap,
pub(super) device_mod_state: ModifierKeyState,
pub(super) window_mod_state: ModifierKeyState,
}
impl<T: 'static> EventProcessor<T> {
@@ -67,6 +66,13 @@ impl<T: 'static> EventProcessor<T> {
self.with_window(window_id, |_| ()).is_some()
}
pub(super) fn poll(&self) -> bool {
let wt = get_xtarget(&self.target);
let result = unsafe { (wt.xconn.xlib.XPending)(wt.xconn.display) };
result != 0
}
pub(super) unsafe fn poll_one_event(&mut self, event_ptr: *mut ffi::XEvent) -> bool {
let wt = get_xtarget(&self.target);
// This function is used to poll and remove a single event
@@ -114,6 +120,26 @@ impl<T: 'static> EventProcessor<T> {
return;
}
// We can't call a `&mut self` method because of the above borrow,
// so we use this macro for repeated modifier state updates.
macro_rules! update_modifiers {
( $state:expr , $modifier:expr ) => {{
match ($state, $modifier) {
(state, modifier) => {
if let Some(modifiers) =
self.device_mod_state.update_state(&state, modifier)
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged { modifiers },
});
}
}
}
}};
}
let event_type = xev.get_type();
match event_type {
ffi::MappingNotify => {
@@ -130,8 +156,7 @@ impl<T: 'static> EventProcessor<T> {
.expect("Failed to call XRefreshKeyboardMapping");
self.mod_keymap.reset_from_x_connection(&wt.xconn);
self.device_mod_state.update(&self.mod_keymap);
self.window_mod_state.update(&self.mod_keymap);
self.device_mod_state.update_keymap(&self.mod_keymap);
}
}
@@ -377,14 +402,8 @@ impl<T: 'static> EventProcessor<T> {
let (width, height) = shared_state_lock
.dpi_adjusted
.unwrap_or_else(|| (xev.width as f64, xev.height as f64));
let last_hidpi_factor =
shared_state_lock.guessed_dpi.take().unwrap_or_else(|| {
shared_state_lock
.last_monitor
.as_ref()
.map(|last_monitor| last_monitor.hidpi_factor)
.unwrap_or(1.0)
});
let last_hidpi_factor = shared_state_lock.last_monitor.hidpi_factor;
let new_hidpi_factor = {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
@@ -392,7 +411,7 @@ impl<T: 'static> EventProcessor<T> {
// Avoid caching an invalid dummy monitor handle
if monitor.id != 0 {
shared_state_lock.last_monitor = Some(monitor.clone());
shared_state_lock.last_monitor = monitor.clone();
}
new_hidpi_factor
};
@@ -498,6 +517,13 @@ impl<T: 'static> EventProcessor<T> {
});
}
ffi::VisibilityNotify => {
let xev: &ffi::XVisibilityEvent = xev.as_ref();
let xwindow = xev.window;
self.with_window(xwindow, |window| window.visibility_notify());
}
ffi::Expose => {
let xev: &ffi::XExposeEvent = xev.as_ref();
@@ -547,7 +573,12 @@ impl<T: 'static> EventProcessor<T> {
};
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.window_mod_state.modifiers();
update_modifiers!(
ModifiersState::from_x11_mask(xkev.state),
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
);
let modifiers = self.device_mod_state.modifiers();
callback(Event::WindowEvent {
window_id,
@@ -561,27 +592,6 @@ impl<T: 'static> EventProcessor<T> {
},
},
});
if let Some(modifier) =
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
{
self.window_mod_state.key_event(
state,
xkev.keycode as ffi::KeyCode,
modifier,
);
let new_modifiers = self.window_mod_state.modifiers();
if modifiers != new_modifiers {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged {
modifiers: new_modifiers,
},
});
}
}
}
if state == Pressed {
@@ -633,7 +643,8 @@ impl<T: 'static> EventProcessor<T> {
return;
}
let modifiers = ModifiersState::from(xev.mods);
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
let state = if xev.evtype == ffi::XI_ButtonPress {
Pressed
@@ -709,7 +720,8 @@ impl<T: 'static> EventProcessor<T> {
let window_id = mkwid(xev.event);
let new_cursor_pos = (xev.event_x, xev.event_y);
let modifiers = ModifiersState::from(xev.mods);
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
let cursor_moved = self.with_window(xev.event, |window| {
let mut shared_state_lock = window.shared_state.lock();
@@ -894,21 +906,6 @@ impl<T: 'static> EventProcessor<T> {
event: Focused(true),
});
// When focus is gained, send any existing modifiers
// to the window in a ModifiersChanged event. This is
// done to compensate for modifier keys that may be
// changed while a window is out of focus.
if !self.device_mod_state.is_empty() {
self.window_mod_state = self.device_mod_state.clone();
let modifiers = self.window_mod_state.modifiers();
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged { modifiers },
});
}
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
@@ -927,7 +924,7 @@ impl<T: 'static> EventProcessor<T> {
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers: ModifiersState::from(xev.mods),
modifiers: ModifiersState::from_x11(&xev.mods),
},
});
}
@@ -941,21 +938,6 @@ impl<T: 'static> EventProcessor<T> {
.unfocus(xev.event)
.expect("Failed to unfocus input context");
// When focus is lost, send a ModifiersChanged event
// containing no modifiers set. This is done to compensate
// for modifier keys that may be changed while a window
// is out of focus.
if !self.window_mod_state.is_empty() {
self.window_mod_state.clear();
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: WindowEvent::ModifiersChanged {
modifiers: ModifiersState::default(),
},
});
}
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: Focused(false),
@@ -1068,7 +1050,7 @@ impl<T: 'static> EventProcessor<T> {
_ => unreachable!(),
};
let device_id = xev.sourceid;
let device_id = mkdid(xev.sourceid);
let keycode = xev.detail;
if keycode < 8 {
return;
@@ -1088,6 +1070,18 @@ impl<T: 'static> EventProcessor<T> {
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.device_mod_state.modifiers();
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::Key(KeyboardInput {
scancode,
virtual_keycode,
state,
modifiers,
}),
});
if let Some(modifier) =
self.mod_keymap.get_modifier(keycode as ffi::KeyCode)
{
@@ -1096,19 +1090,18 @@ impl<T: 'static> EventProcessor<T> {
keycode as ffi::KeyCode,
modifier,
);
let new_modifiers = self.device_mod_state.modifiers();
if modifiers != new_modifiers {
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged {
modifiers: new_modifiers,
},
});
}
}
let modifiers = self.device_mod_state.modifiers();
callback(Event::DeviceEvent {
device_id: mkdid(device_id),
event: DeviceEvent::Key(KeyboardInput {
scancode,
virtual_keycode,
state,
modifiers,
}),
});
}
ffi::XI_HierarchyChanged => {

View File

@@ -26,6 +26,7 @@ use std::{
rc::Rc,
slice,
sync::{mpsc, Arc, Mutex, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
@@ -38,7 +39,7 @@ use self::{
};
use crate::{
error::OsError as RootOsError,
event::{Event, WindowEvent},
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
window::WindowAttributes,
@@ -192,7 +193,6 @@ impl<T: 'static> EventLoop<T> {
xi2ext,
mod_keymap,
device_mod_state: Default::default(),
window_mod_state: Default::default(),
};
// Register for device hotplug events
@@ -263,6 +263,8 @@ impl<T: 'static> EventLoop<T> {
);
loop {
self.drain_events();
// Empty the event buffer
{
let mut guard = self.pending_events.borrow_mut();
@@ -310,69 +312,58 @@ impl<T: 'static> EventLoop<T> {
);
}
let start = Instant::now();
let (mut cause, deadline, mut timeout);
match control_flow {
ControlFlow::Exit => break,
ControlFlow::Poll => {
// non-blocking dispatch
self.inner_loop
.dispatch(Some(::std::time::Duration::from_millis(0)), &mut ())
.unwrap();
callback(
crate::event::Event::NewEvents(crate::event::StartCause::Poll),
&self.target,
&mut control_flow,
);
cause = StartCause::Poll;
deadline = None;
timeout = Some(Duration::from_millis(0));
}
ControlFlow::Wait => {
self.inner_loop.dispatch(None, &mut ()).unwrap();
callback(
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
start: ::std::time::Instant::now(),
requested_resume: None,
}),
&self.target,
&mut control_flow,
);
}
ControlFlow::WaitUntil(deadline) => {
let start = ::std::time::Instant::now();
// compute the blocking duration
let duration = if deadline > start {
deadline - start
} else {
::std::time::Duration::from_millis(0)
cause = StartCause::WaitCancelled {
start,
requested_resume: None,
};
self.inner_loop.dispatch(Some(duration), &mut ()).unwrap();
let now = std::time::Instant::now();
if now < deadline {
callback(
crate::event::Event::NewEvents(
crate::event::StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
},
),
&self.target,
&mut control_flow,
);
deadline = None;
timeout = None;
}
ControlFlow::WaitUntil(wait_deadline) => {
cause = StartCause::ResumeTimeReached {
start,
requested_resume: wait_deadline,
};
timeout = if wait_deadline > start {
Some(wait_deadline - start)
} else {
callback(
crate::event::Event::NewEvents(
crate::event::StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
},
),
&self.target,
&mut control_flow,
);
}
Some(Duration::from_millis(0))
};
deadline = Some(wait_deadline);
}
}
// If the user callback had any interaction with the X server,
// it may have received and buffered some user input events.
self.drain_events();
if self.events_waiting() {
timeout = Some(Duration::from_millis(0));
}
self.inner_loop.dispatch(timeout, &mut ()).unwrap();
if let Some(deadline) = deadline {
if deadline > Instant::now() {
cause = StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
};
}
}
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
}
callback(
@@ -396,6 +387,10 @@ impl<T: 'static> EventLoop<T> {
drain_events(&mut processor, &mut pending_events);
}
fn events_waiting(&self) -> bool {
!self.pending_events.borrow().is_empty() || self.event_processor.borrow().poll()
}
}
fn drain_events<T: 'static>(

View File

@@ -130,7 +130,7 @@ impl MonitorHandle {
})
}
fn dummy() -> Self {
pub fn dummy() -> Self {
MonitorHandle {
id: 0,
name: "<dummy monitor>".into(),

View File

@@ -11,14 +11,17 @@ pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
// To test if `lookup_utf8` works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
impl From<ffi::XIModifierState> for ModifiersState {
fn from(mods: ffi::XIModifierState) -> Self {
let state = mods.effective as c_uint;
impl ModifiersState {
pub(crate) fn from_x11(state: &ffi::XIModifierState) -> Self {
ModifiersState::from_x11_mask(state.effective as c_uint)
}
pub(crate) fn from_x11_mask(mask: c_uint) -> Self {
ModifiersState {
alt: state & ffi::Mod1Mask != 0,
shift: state & ffi::ShiftMask != 0,
ctrl: state & ffi::ControlMask != 0,
logo: state & ffi::Mod4Mask != 0,
alt: mask & ffi::Mod1Mask != 0,
shift: mask & ffi::ShiftMask != 0,
ctrl: mask & ffi::ControlMask != 0,
logo: mask & ffi::Mod4Mask != 0,
}
}
}
@@ -40,7 +43,7 @@ pub struct PointerState<'a> {
impl<'a> PointerState<'a> {
pub fn get_modifier_state(&self) -> ModifiersState {
self.modifiers.into()
ModifiersState::from_x11(&self.modifiers)
}
}

View File

@@ -35,6 +35,7 @@ pub struct ModifierKeymap {
pub struct ModifierKeyState {
// Contains currently pressed modifier keys and their corresponding modifiers
keys: HashMap<ffi::KeyCode, Modifier>,
state: ModifiersState,
}
impl ModifierKeymap {
@@ -94,15 +95,7 @@ impl ModifierKeymap {
}
impl ModifierKeyState {
pub fn clear(&mut self) {
self.keys.clear();
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub fn update(&mut self, mods: &ModifierKeymap) {
pub fn update_keymap(&mut self, mods: &ModifierKeymap) {
self.keys.retain(|k, v| {
if let Some(m) = mods.get_modifier(*k) {
*v = m;
@@ -111,16 +104,36 @@ impl ModifierKeyState {
false
}
});
self.reset_state();
}
pub fn update_state(
&mut self,
state: &ModifiersState,
except: Option<Modifier>,
) -> Option<ModifiersState> {
let mut new_state = *state;
match except {
Some(Modifier::Alt) => new_state.alt = self.state.alt,
Some(Modifier::Ctrl) => new_state.ctrl = self.state.ctrl,
Some(Modifier::Shift) => new_state.shift = self.state.shift,
Some(Modifier::Logo) => new_state.logo = self.state.logo,
None => (),
}
if self.state == new_state {
None
} else {
self.keys.retain(|_k, v| get_modifier(&new_state, *v));
self.state = new_state;
Some(new_state)
}
}
pub fn modifiers(&self) -> ModifiersState {
let mut state = ModifiersState::default();
for &m in self.keys.values() {
set_modifier(&mut state, m);
}
state
self.state
}
pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) {
@@ -132,18 +145,43 @@ impl ModifierKeyState {
pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) {
self.keys.insert(keycode, modifier);
set_modifier(&mut self.state, modifier, true);
}
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
self.keys.remove(&keycode);
if let Some(modifier) = self.keys.remove(&keycode) {
if self.keys.values().find(|&&m| m == modifier).is_none() {
set_modifier(&mut self.state, modifier, false);
}
}
}
fn reset_state(&mut self) {
let mut new_state = ModifiersState::default();
for &m in self.keys.values() {
set_modifier(&mut new_state, m, true);
}
self.state = new_state;
}
}
fn set_modifier(state: &mut ModifiersState, modifier: Modifier) {
fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool {
match modifier {
Modifier::Alt => state.alt = true,
Modifier::Ctrl => state.ctrl = true,
Modifier::Shift => state.shift = true,
Modifier::Logo => state.logo = true,
Modifier::Alt => state.alt,
Modifier::Ctrl => state.ctrl,
Modifier::Shift => state.shift,
Modifier::Logo => state.logo,
}
}
fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) {
match modifier {
Modifier::Alt => state.alt = value,
Modifier::Ctrl => state.ctrl = value,
Modifier::Shift => state.shift = value,
Modifier::Logo => state.logo = value,
}
}

View File

@@ -28,27 +28,18 @@ use crate::{
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
unsafe extern "C" fn visibility_predicate(
_display: *mut ffi::Display,
event: *mut ffi::XEvent,
arg: ffi::XPointer, // We populate this with the window ID (by value) when we call XIfEvent
) -> ffi::Bool {
let event: &ffi::XAnyEvent = (*event).as_ref();
let window = arg as ffi::Window;
(event.window == window && event.type_ == ffi::VisibilityNotify) as _
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct SharedState {
pub cursor_pos: Option<(f64, f64)>,
pub size: Option<(u32, u32)>,
pub position: Option<(i32, i32)>,
pub inner_position: Option<(i32, i32)>,
pub inner_position_rel_parent: Option<(i32, i32)>,
pub guessed_dpi: Option<f64>,
pub last_monitor: Option<X11MonitorHandle>,
pub last_monitor: X11MonitorHandle,
pub dpi_adjusted: Option<(f64, f64)>,
pub fullscreen: Option<Fullscreen>,
// Set when application calls `set_fullscreen` when window is not visible
pub desired_fullscreen: Option<Option<Fullscreen>>,
// Used to restore position after exiting fullscreen
pub restore_position: Option<(i32, i32)>,
// Used to restore video mode after exiting fullscreen
@@ -56,15 +47,43 @@ pub struct SharedState {
pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>,
pub is_visible: bool,
pub visibility: Visibility,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Visibility {
No,
Yes,
// Waiting for VisibilityNotify
YesWait,
}
impl SharedState {
fn new(dpi_factor: f64, is_visible: bool) -> Mutex<Self> {
let mut shared_state = SharedState::default();
shared_state.guessed_dpi = Some(dpi_factor);
shared_state.is_visible = is_visible;
Mutex::new(shared_state)
fn new(last_monitor: X11MonitorHandle, is_visible: bool) -> Mutex<Self> {
let visibility = if is_visible {
Visibility::YesWait
} else {
Visibility::No
};
Mutex::new(SharedState {
last_monitor,
visibility,
cursor_pos: None,
size: None,
position: None,
inner_position: None,
inner_position_rel_parent: None,
dpi_adjusted: None,
fullscreen: None,
desired_fullscreen: None,
restore_position: None,
desktop_video_mode: None,
frame_extents: None,
min_inner_size: None,
max_inner_size: None,
})
}
}
@@ -93,34 +112,27 @@ impl UnownedWindow {
let xconn = &event_loop.xconn;
let root = event_loop.root;
let monitors = xconn.available_monitors();
let dpi_factor = if !monitors.is_empty() {
let mut dpi_factor = Some(monitors[0].hidpi_factor());
for monitor in &monitors {
if Some(monitor.hidpi_factor()) != dpi_factor {
dpi_factor = None;
}
}
dpi_factor.unwrap_or_else(|| {
xconn
.query_pointer(root, util::VIRTUAL_CORE_POINTER)
.ok()
.and_then(|pointer_state| {
let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64);
let mut dpi_factor = None;
for monitor in &monitors {
if monitor.rect.contains_point(x, y) {
dpi_factor = Some(monitor.hidpi_factor());
break;
}
}
dpi_factor
})
.unwrap_or(1.0)
})
let mut monitors = xconn.available_monitors();
let guessed_monitor = if monitors.is_empty() {
X11MonitorHandle::dummy()
} else {
1.0
xconn
.query_pointer(root, util::VIRTUAL_CORE_POINTER)
.ok()
.and_then(|pointer_state| {
let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64);
for i in 0..monitors.len() {
if monitors[i].rect.contains_point(x, y) {
return Some(monitors.swap_remove(i));
}
}
None
})
.unwrap_or_else(|| monitors.swap_remove(0))
};
let dpi_factor = guessed_monitor.hidpi_factor();
info!("Guessed window DPI factor: {}", dpi_factor);
@@ -232,7 +244,7 @@ impl UnownedWindow {
cursor_grabbed: Mutex::new(false),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
shared_state: SharedState::new(dpi_factor, window_attrs.visible),
shared_state: SharedState::new(guessed_monitor, window_attrs.visible),
pending_redraws: event_loop.pending_redraws.clone(),
};
@@ -355,8 +367,6 @@ impl UnownedWindow {
unsafe {
(xconn.xlib.XMapRaised)(xconn.display, window.xwindow);
} //.queue();
window.wait_for_visibility_notify();
}
// Attempt to make keyboard input repeat detectable
@@ -414,8 +424,7 @@ impl UnownedWindow {
if window_attrs.fullscreen.is_some() {
window
.set_fullscreen_inner(window_attrs.fullscreen.clone())
.unwrap()
.queue();
.map(|flusher| flusher.queue());
}
if window_attrs.always_on_top {
window
@@ -572,9 +581,13 @@ impl UnownedWindow {
fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
let mut shared_state_lock = self.shared_state.lock();
if !shared_state_lock.is_visible {
match shared_state_lock.visibility {
// Setting fullscreen on a window that is not visible will generate an error.
return None;
Visibility::No | Visibility::YesWait => {
shared_state_lock.desired_fullscreen = Some(fullscreen);
return None;
}
Visibility::Yes => (),
}
let old_fullscreen = shared_state_lock.fullscreen.clone();
@@ -685,7 +698,12 @@ impl UnownedWindow {
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
self.shared_state.lock().fullscreen.clone()
let shared_state = self.shared_state.lock();
shared_state
.desired_fullscreen
.clone()
.unwrap_or_else(|| shared_state.fullscreen.clone())
}
#[inline]
@@ -698,24 +716,29 @@ impl UnownedWindow {
}
}
fn get_rect(&self) -> util::AaRect {
// TODO: This might round-trip more times than needed.
let position = self.outer_position_physical();
let size = self.outer_size_physical();
util::AaRect::new(position, size)
// Called by EventProcessor when a VisibilityNotify event is received
pub(crate) fn visibility_notify(&self) {
let mut shared_state = self.shared_state.lock();
match shared_state.visibility {
Visibility::No => unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
},
Visibility::Yes => (),
Visibility::YesWait => {
shared_state.visibility = Visibility::Yes;
if let Some(fullscreen) = shared_state.desired_fullscreen.take() {
drop(shared_state);
self.set_fullscreen(fullscreen);
}
}
}
}
#[inline]
pub fn current_monitor(&self) -> X11MonitorHandle {
let monitor = self.shared_state.lock().last_monitor.as_ref().cloned();
monitor.unwrap_or_else(|| {
let monitor = self.xconn.get_monitor_for_window(Some(self.get_rect()));
// Avoid caching an invalid dummy monitor handle
if monitor.id != 0 {
self.shared_state.lock().last_monitor = Some(monitor.clone());
}
monitor
})
self.shared_state.lock().last_monitor.clone()
}
pub fn available_monitors(&self) -> Vec<X11MonitorHandle> {
@@ -848,44 +871,31 @@ impl UnownedWindow {
#[inline]
pub fn set_visible(&self, visible: bool) {
let is_visible = self.shared_state.lock().is_visible;
let mut shared_state = self.shared_state.lock();
if visible == is_visible {
return;
match (visible, shared_state.visibility) {
(true, Visibility::Yes) | (true, Visibility::YesWait) | (false, Visibility::No) => {
return
}
_ => (),
}
match visible {
true => unsafe {
if visible {
unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
self.xconn
.flush_requests()
.expect("Failed to call XMapRaised");
// Some X requests may generate an error if the window is not
// visible, so we must wait until the window becomes visible.
self.wait_for_visibility_notify();
},
false => unsafe {
}
self.xconn
.flush_requests()
.expect("Failed to call XMapRaised");
shared_state.visibility = Visibility::YesWait;
} else {
unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
self.xconn
.flush_requests()
.expect("Failed to call XUnmapWindow");
},
}
self.shared_state.lock().is_visible = visible;
}
fn wait_for_visibility_notify(&self) {
unsafe {
let mut event = MaybeUninit::uninit();
(self.xconn.xlib.XIfEvent)(
self.xconn.display,
event.as_mut_ptr(),
Some(visibility_predicate),
self.xwindow as _,
);
}
self.xconn
.flush_requests()
.expect("Failed to call XUnmapWindow");
shared_state.visibility = Visibility::No;
}
}
@@ -982,17 +992,6 @@ impl UnownedWindow {
self.logicalize_size(self.inner_size_physical())
}
pub(crate) fn outer_size_physical(&self) -> (u32, u32) {
let extents = self.shared_state.lock().frame_extents.clone();
if let Some(extents) = extents {
let (w, h) = self.inner_size_physical();
extents.inner_size_to_outer(w, h)
} else {
self.update_cached_frame_extents();
self.outer_size_physical()
}
}
#[inline]
pub fn outer_size(&self) -> LogicalSize {
let extents = self.shared_state.lock().frame_extents.clone();

View File

@@ -275,7 +275,7 @@ impl AppState {
HANDLER.set_in_callback(false);
}
if HANDLER.should_exit() {
let _: () = unsafe { msg_send![NSApp(), stop: nil] };
let _: () = unsafe { msg_send![NSApp(), terminate: nil] };
return;
}
HANDLER.update_start_time();

View File

@@ -208,7 +208,7 @@ pub fn scancode_to_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
// While F1-F20 have scancodes we can match on, we have to check against UTF-16
// constants for the rest.
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ
pub fn check_function_keys(string: &String) -> Option<VirtualKeyCode> {
pub fn check_function_keys(string: &str) -> Option<VirtualKeyCode> {
if let Some(ch) = string.encode_utf16().next() {
return Some(match ch {
0xf718 => VirtualKeyCode::F21,

View File

@@ -132,7 +132,7 @@ impl<T> Proxy<T> {
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = event_loop_proxy_handler;
context.perform = Some(event_loop_proxy_handler);
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);

View File

@@ -248,7 +248,6 @@ impl MonitorHandle {
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.into_iter()
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);

View File

@@ -93,14 +93,14 @@ pub enum CFRunLoopTimerContext {}
pub struct CFRunLoopSourceContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub perform: extern "C" fn(*mut c_void),
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
pub release: Option<extern "C" fn(*const c_void)>,
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean>,
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub perform: Option<extern "C" fn(*mut c_void)>,
}
// begin is queued with the highest priority to ensure it is processed before other observers

View File

@@ -452,7 +452,7 @@ extern "C" fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_ran
//let event: id = msg_send![NSApp(), currentEvent];
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars() {
for character in string.chars().filter(|c| !is_corporate_character(*c)) {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ReceivedCharacter(character),
@@ -484,7 +484,10 @@ extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
} else {
let raw_characters = state.raw_characters.take();
if let Some(raw_characters) = raw_characters {
for character in raw_characters.chars() {
for character in raw_characters
.chars()
.filter(|c| !is_corporate_character(*c))
{
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ReceivedCharacter(character),
@@ -515,15 +518,27 @@ fn get_characters(event: id, ignore_modifiers: bool) -> String {
}
}
// As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
fn is_corporate_character(c: char) -> bool {
match c {
'\u{F700}'..='\u{F747}'
| '\u{F802}'..='\u{F84F}'
| '\u{F850}'
| '\u{F85C}'
| '\u{F85D}'
| '\u{F85F}'
| '\u{F860}'..='\u{F86B}'
| '\u{F870}'..='\u{F8FF}' => true,
_ => false,
}
}
// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: id) -> Option<VirtualKeyCode> {
#[inline]
fn get_code(ev: id, raw: bool) -> Option<VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters
.chars()
.next()
.map_or(None, |c| char_to_keycode(c))
characters.chars().next().and_then(|c| char_to_keycode(c))
}
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
@@ -574,7 +589,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
AppState::queue_event(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down {
for character in characters.chars() {
for character in characters.chars().filter(|c| !is_corporate_character(*c)) {
AppState::queue_event(Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),

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

@@ -16,7 +16,9 @@ mod backend;
#[path = "stdweb/mod.rs"]
mod backend;
pub use self::device::Id as DeviceId;
#[cfg(not(any(feature = "web-sys", feature = "stdweb")))]
compile_error!("Please select a feature to build for web: `web-sys`, `stdweb`");
pub use self::error::OsError;
pub use self::event_loop::{
EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
@@ -26,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`
@@ -1112,7 +1204,7 @@ unsafe extern "system" fn public_window_callback<T>(
0
}
winuser::WM_CHAR => {
winuser::WM_CHAR | winuser::WM_SYSCHAR => {
use crate::event::WindowEvent::ReceivedCharacter;
use std::char;
let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF;
@@ -1145,12 +1237,6 @@ unsafe extern "system" fn public_window_callback<T>(
0
}
// Prevents default windows menu hotkeys playing unwanted
// "ding" sounds. Alternatively could check for WM_SYSCOMMAND
// with wparam being SC_KEYMENU, but this may prevent some
// other unwanted default hotkeys as well.
winuser::WM_SYSCHAR => 0,
winuser::WM_SYSCOMMAND => {
if wparam == winuser::SC_SCREENSAVE {
let window_state = subclass_input.window_state.lock();
@@ -1177,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.
@@ -1199,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(),
},
@@ -1219,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
@@ -1237,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(),
@@ -1257,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(),
@@ -1275,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.
@@ -1303,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(),
},
});
@@ -1335,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(),
},
});
@@ -1354,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(),
},
});
@@ -1373,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(),
},
});
@@ -1392,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(),
},
});
@@ -1411,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(),
},
});
@@ -1430,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());
@@ -1440,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(),
},
});
@@ -1450,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());
@@ -1460,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);
@@ -1624,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,
}),
});
}
@@ -1764,7 +1700,6 @@ unsafe extern "system" fn public_window_callback<T>(
location,
force,
id: pointer_info.pointerId as u64,
device_id: DEVICE_ID,
}),
});
}
@@ -1978,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 {
@@ -2012,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 {
@@ -2032,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,
};
@@ -202,7 +200,10 @@ impl Window {
y as c_int,
0,
0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOSIZE,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOSIZE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(self.window.0);
}
@@ -615,7 +616,9 @@ impl Window {
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(window.0);
}
@@ -834,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

@@ -255,7 +255,10 @@ impl WindowFlags {
0,
0,
0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOMOVE
| winuser::SWP_NOSIZE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(window);
}

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

@@ -195,7 +195,7 @@ impl WindowBuilder {
///
/// See [`Window::set_inner_size`] for details.
///
/// [`Window::set_inner_size`]: struct.Window.html#method.set_inner_size
/// [`Window::set_inner_size`]: crate::window::Window::set_inner_size
#[inline]
pub fn with_inner_size(mut self, size: LogicalSize) -> Self {
self.window.inner_size = Some(size);
@@ -206,7 +206,7 @@ impl WindowBuilder {
///
/// See [`Window::set_min_inner_size`] for details.
///
/// [`Window::set_min_inner_size`]: struct.Window.html#method.set_min_inner_size
/// [`Window::set_min_inner_size`]: crate::window::Window::set_min_inner_size
#[inline]
pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> Self {
self.window.min_inner_size = Some(min_size);
@@ -217,7 +217,7 @@ impl WindowBuilder {
///
/// See [`Window::set_max_inner_size`] for details.
///
/// [`Window::set_max_inner_size`]: struct.Window.html#method.set_max_inner_size
/// [`Window::set_max_inner_size`]: crate::window::Window::set_max_inner_size
#[inline]
pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> Self {
self.window.max_inner_size = Some(max_size);
@@ -228,7 +228,7 @@ impl WindowBuilder {
///
/// See [`Window::set_resizable`] for details.
///
/// [`Window::set_resizable`]: struct.Window.html#method.set_resizable
/// [`Window::set_resizable`]: crate::window::Window::set_resizable
#[inline]
pub fn with_resizable(mut self, resizable: bool) -> Self {
self.window.resizable = resizable;
@@ -239,7 +239,7 @@ impl WindowBuilder {
///
/// See [`Window::set_title`] for details.
///
/// [`Window::set_title`]: struct.Window.html#method.set_title
/// [`Window::set_title`]: crate::window::Window::set_title
#[inline]
pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
self.window.title = title.into();
@@ -250,7 +250,7 @@ impl WindowBuilder {
///
/// See [`Window::set_fullscreen`] for details.
///
/// [`Window::set_fullscreen`]: struct.Window.html#method.set_fullscreen
/// [`Window::set_fullscreen`]: crate::window::Window::set_fullscreen
#[inline]
pub fn with_fullscreen(mut self, monitor: Option<Fullscreen>) -> Self {
self.window.fullscreen = monitor;
@@ -261,7 +261,7 @@ impl WindowBuilder {
///
/// See [`Window::set_maximized`] for details.
///
/// [`Window::set_maximized`]: struct.Window.html#method.set_maximized
/// [`Window::set_maximized`]: crate::window::Window::set_maximized
#[inline]
pub fn with_maximized(mut self, maximized: bool) -> Self {
self.window.maximized = maximized;
@@ -272,7 +272,7 @@ impl WindowBuilder {
///
/// See [`Window::set_visible`] for details.
///
/// [`Window::set_visible`]: struct.Window.html#method.set_visible
/// [`Window::set_visible`]: crate::window::Window::set_visible
#[inline]
pub fn with_visible(mut self, visible: bool) -> Self {
self.window.visible = visible;
@@ -290,7 +290,7 @@ impl WindowBuilder {
///
/// See [`Window::set_decorations`] for details.
///
/// [`Window::set_decorations`]: struct.Window.html#method.set_decorations
/// [`Window::set_decorations`]: crate::window::Window::set_decorations
#[inline]
pub fn with_decorations(mut self, decorations: bool) -> Self {
self.window.decorations = decorations;
@@ -301,7 +301,7 @@ impl WindowBuilder {
///
/// See [`Window::set_always_on_top`] for details.
///
/// [`Window::set_always_on_top`]: struct.Window.html#method.set_always_on_top
/// [`Window::set_always_on_top`]: crate::window::Window::set_always_on_top
#[inline]
pub fn with_always_on_top(mut self, always_on_top: bool) -> Self {
self.window.always_on_top = always_on_top;
@@ -312,7 +312,7 @@ impl WindowBuilder {
///
/// See [`Window::set_window_icon`] for details.
///
/// [`Window::set_window_icon`]: struct.Window.html#method.set_window_icon
/// [`Window::set_window_icon`]: crate::window::Window::set_window_icon
#[inline]
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> Self {
self.window.window_icon = window_icon;
@@ -353,7 +353,7 @@ impl Window {
/// - **Web**: The window is created but not inserted into the web page automatically. Please
/// see the web platform module for more information.
///
/// [`WindowBuilder::new().build(event_loop)`]: struct.WindowBuilder.html#method.build
/// [`WindowBuilder::new().build(event_loop)`]: crate::window::WindowBuilder::build
#[inline]
pub fn new<T: 'static>(event_loop: &EventLoopWindowTarget<T>) -> Result<Window, OsError> {
let builder = WindowBuilder::new();
@@ -368,7 +368,7 @@ impl Window {
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](../dpi/index.html) module for more information.
/// See the [`dpi`](crate::dpi) module for more information.
///
/// Note that this value can change depending on user action (for example if the window is
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
@@ -595,16 +595,13 @@ impl Window {
///
/// `Fullscreen::Borderless` provides a borderless fullscreen window on a
/// separate space. This is the idiomatic way for fullscreen games to work
/// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if
/// on macOS. See `WindowExtMacOs::set_simple_fullscreen` if
/// separate spaces are not preferred.
///
/// The dock and the menu bar are always disabled in fullscreen mode.
/// - **iOS:** Can only be called on the main thread.
/// - **Wayland:** Does not support exclusive fullscreen mode.
/// - **Windows:** Screen saver is disabled in fullscreen mode.
///
/// [simple]:
/// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.window.set_fullscreen(fullscreen)

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