Skip to content

Commit

Permalink
Windows: Implement DeviceEvents (#482)
Browse files Browse the repository at this point in the history
Fixes #467

All variants other than Text have been implemented. While Text can
be implemented using ToUnicode, that doesn't play nice with dead
keys, IME, etc.

Most of the mouse DeviceEvents were already implemented, but due
to the flags that were used when registering for raw input events,
they only worked when the window was in the foreground.

This is also a step forward for #338, as DeviceIds are no longer
useless on Windows. On DeviceEvents, the DeviceId contains that
device's handle. While that handle could ostensibly be used by
developers to query device information, my actual reason for
choosing it is because it's simply a very easy way to handle this.
As a fun bonus, this enabled me to create this method:
  DevideIdExt::get_persistent_identifier() -> Option<String>
Using this gives you a unique identifier for the device that
persists across replugs/reboots/etc., so it's ideal for something
like device-specific configuration.

There's a notable caveat to the new DeviceIds, which is that the
value will always be 0 for a WindowEvent. There doesn't seem to be
any straightforward way around this limitation.

I was concerned that multi-window applications would receive n
copies of every DeviceEvent, but Windows only sends them to one
window per application.

Lastly, there's a chance that these additions will cause
antivirus/etc. software to detect winit applications as keyloggers.
I don't know how likely that is to actually happen to people, but
if it does become an issue, the raw input code is neatly
sequestered and would be easy to make optional during compilation.
  • Loading branch information
francesca64 authored Apr 28, 2018
1 parent 3407a8d commit fe2d37f
Show file tree
Hide file tree
Showing 9 changed files with 445 additions and 87 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- Corrected `get_position` on Windows to be relative to the screen rather than to the taskbar.
- Corrected `Moved` event on Windows to use position values equivalent to those returned by `get_position`. It previously supplied client area positions instead of window positions, and would additionally interpret negative values as being very large (around `u16::MAX`).
- Implemented `Moved` event on macOS.
- On Windows, implemented all variants of `DeviceEvent` other than `Text`. Mouse `DeviceEvent`s are now received even if the window isn't in the foreground.
- `DeviceId` on Windows is no longer a unit struct, and now contains a `u32`. For `WindowEvent`s, this will always be 0, but on `DeviceEvent`s it will be the handle to that device. `DeviceIdExt::get_persistent_identifier` can be used to acquire a unique identifier for that device that persists across replugs/reboots/etc.

# Version 0.13.1 (2018-04-26)

Expand Down
16 changes: 16 additions & 0 deletions src/os/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::os::raw::c_void;
use libc;
use MonitorId;
use DeviceId;
use Window;
use WindowBuilder;
use winapi::shared::windef::HWND;
Expand Down Expand Up @@ -56,3 +57,18 @@ impl MonitorIdExt for MonitorId {
self.inner.get_hmonitor() as *mut _
}
}

/// Additional methods on `DeviceId` that are specific to Windows.
pub trait DeviceIdExt {
/// Returns an identifier that persistently refers to this specific device.
///
/// Will return `None` if the device is no longer available.
fn get_persistent_identifier(&self) -> Option<String>;
}

impl DeviceIdExt for DeviceId {
#[inline]
fn get_persistent_identifier(&self) -> Option<String> {
self.0.get_persistent_identifier()
}
}
67 changes: 42 additions & 25 deletions src/platform/windows/event.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::char;
use std::os::raw::c_int;

use events::VirtualKeyCode;
use events::ModifiersState;

use winapi::shared::minwindef::{WPARAM, LPARAM};
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT};
use winapi::um::winuser;

use ScanCode;
use std::char;

const MAPVK_VK_TO_CHAR: u32 = 2;
const MAPVK_VSC_TO_VK_EX: u32 = 3;

pub fn get_key_mods() -> ModifiersState {
let mut mods = ModifiersState::default();
Expand All @@ -29,18 +28,9 @@ pub fn get_key_mods() -> ModifiersState {
mods
}

pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<VirtualKeyCode>) {
let scancode = ((lparam >> 16) & 0xff) as u32;
let extended = (lparam & 0x01000000) != 0;
let vk = match wparam as i32 {
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(scancode, MAPVK_VSC_TO_VK_EX) as i32 },
winuser::VK_CONTROL => if extended { winuser::VK_RCONTROL } else { winuser::VK_LCONTROL },
winuser::VK_MENU => if extended { winuser::VK_RMENU } else { winuser::VK_LMENU },
other => other
};

pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
// VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
(scancode, match vk {
match vkey {
//winuser::VK_LBUTTON => Some(VirtualKeyCode::Lbutton),
//winuser::VK_RBUTTON => Some(VirtualKeyCode::Rbutton),
//winuser::VK_CANCEL => Some(VirtualKeyCode::Cancel),
Expand Down Expand Up @@ -191,13 +181,13 @@ pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<
winuser::VK_OEM_COMMA => Some(VirtualKeyCode::Comma),
winuser::VK_OEM_MINUS => Some(VirtualKeyCode::Minus),
winuser::VK_OEM_PERIOD => Some(VirtualKeyCode::Period),
winuser::VK_OEM_1 => map_text_keys(vk),
winuser::VK_OEM_2 => map_text_keys(vk),
winuser::VK_OEM_3 => map_text_keys(vk),
winuser::VK_OEM_4 => map_text_keys(vk),
winuser::VK_OEM_5 => map_text_keys(vk),
winuser::VK_OEM_6 => map_text_keys(vk),
winuser::VK_OEM_7 => map_text_keys(vk),
winuser::VK_OEM_1 => map_text_keys(vkey),
winuser::VK_OEM_2 => map_text_keys(vkey),
winuser::VK_OEM_3 => map_text_keys(vkey),
winuser::VK_OEM_4 => map_text_keys(vkey),
winuser::VK_OEM_5 => map_text_keys(vkey),
winuser::VK_OEM_6 => map_text_keys(vkey),
winuser::VK_OEM_7 => map_text_keys(vkey),
/*winuser::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */
winuser::VK_OEM_102 => Some(VirtualKeyCode::OEM102),
/*winuser::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey),
Expand All @@ -212,13 +202,40 @@ pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<
winuser::VK_PA1 => Some(VirtualKeyCode::Pa1),
winuser::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/
_ => None
})
}
}

pub fn vkey_left_right(vkey: c_int, scancode: UINT, extended: bool) -> c_int {
match vkey {
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(
scancode,
winuser::MAPVK_VSC_TO_VK_EX,
) as _ },
winuser::VK_CONTROL => if extended {
winuser::VK_RCONTROL
} else {
winuser::VK_LCONTROL
},
winuser::VK_MENU => if extended {
winuser::VK_RMENU
} else {
winuser::VK_LMENU
},
_ => vkey,
}
}

pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<VirtualKeyCode>) {
let scancode = ((lparam >> 16) & 0xff) as UINT;
let extended = (lparam & 0x01000000) != 0;
let vkey = vkey_left_right(wparam as _, scancode, extended);
(scancode, vkey_to_winit_vkey(vkey))
}

// This is needed as windows doesn't properly distinguish
// some virtual key codes for different keyboard layouts
fn map_text_keys(win_virtual_key: i32) -> Option<VirtualKeyCode> {
let char_key = unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, MAPVK_VK_TO_CHAR) } & 0x7FFF;
let char_key = unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, winuser::MAPVK_VK_TO_CHAR) } & 0x7FFF;
match char::from_u32(char_key) {
Some(';') => Some(VirtualKeyCode::Semicolon),
Some('/') => Some(VirtualKeyCode::Slash),
Expand Down
147 changes: 108 additions & 39 deletions src/platform/windows/events_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ use winapi::shared::minwindef::{LOWORD, HIWORD, DWORD, WPARAM, LPARAM, INT, UINT
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::shared::windowsx;
use winapi::um::{winuser, shellapi, processthreadsapi};
use winapi::um::winnt::LONG;
use winapi::um::winnt::{LONG, SHORT};

use platform::platform::event;
use events::DeviceEvent;
use platform::platform::{event, Cursor, WindowId, DEVICE_ID, wrap_device_id, util};
use platform::platform::event::{vkey_to_winit_vkey, vkey_left_right};
use platform::platform::raw_input::*;
use platform::platform::window::adjust_size;
use platform::platform::Cursor;
use platform::platform::WindowId;
use platform::platform::DEVICE_ID;

use ControlFlow;
use CursorState;
Expand Down Expand Up @@ -567,7 +567,6 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT,
},

winuser::WM_MOUSEWHEEL => {
use events::{DeviceEvent, WindowEvent};
use events::MouseScrollDelta::LineDelta;
use events::TouchPhase;

Expand All @@ -580,11 +579,6 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT,
event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, modifiers: event::get_key_mods() },
});

send_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel { delta: LineDelta(0.0, value) },
});

0
},

Expand Down Expand Up @@ -751,46 +745,121 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT,
0
},

winuser::WM_INPUT_DEVICE_CHANGE => {
let event = match wparam as _ {
winuser::GIDC_ARRIVAL => DeviceEvent::Added,
winuser::GIDC_REMOVAL => DeviceEvent::Removed,
_ => unreachable!(),
};

send_event(Event::DeviceEvent {
device_id: wrap_device_id(lparam as _),
event,
});

winuser::DefWindowProcW(window, msg, wparam, lparam)
},

winuser::WM_INPUT => {
use events::DeviceEvent::{Motion, MouseMotion};
let mut data: winuser::RAWINPUT = mem::uninitialized();
let mut data_size = mem::size_of::<winuser::RAWINPUT>() as UINT;
winuser::GetRawInputData(mem::transmute(lparam), winuser::RID_INPUT,
mem::transmute(&mut data), &mut data_size,
mem::size_of::<winuser::RAWINPUTHEADER>() as UINT);

if data.header.dwType == winuser::RIM_TYPEMOUSE {
let mouse = data.data.mouse();
if mouse.usFlags & winuser::MOUSE_MOVE_RELATIVE == winuser::MOUSE_MOVE_RELATIVE {
let x = mouse.lLastX as f64;
let y = mouse.lLastY as f64;

if x != 0.0 {
send_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: Motion { axis: 0, value: x }
});
use events::DeviceEvent::{Motion, MouseMotion, MouseWheel, Button, Key};
use events::MouseScrollDelta::LineDelta;
use events::ElementState::{Pressed, Released};

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 {
send_event(Event::DeviceEvent {
device_id,
event: Motion { axis: 0, value: x }
});
}

if y != 0.0 {
send_event(Event::DeviceEvent {
device_id,
event: Motion { axis: 1, value: y }
});
}

if x != 0.0 || y != 0.0 {
send_event(Event::DeviceEvent {
device_id,
event: MouseMotion { delta: (x, y) }
});
}
}

if y != 0.0 {
if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) {
let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA;
send_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: Motion { axis: 1, value: y }
device_id,
event: MouseWheel { delta: LineDelta(0.0, delta as f32) }
});
}

if x != 0.0 || y != 0.0 {
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 _;
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 _);
let vkey = vkey_left_right(
keyboard.VKey as _,
scancode,
extended,
);
let virtual_keycode = vkey_to_winit_vkey(vkey);

send_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: MouseMotion { delta: (x, y) }
device_id,
event: Key(KeyboardInput {
scancode,
state,
virtual_keycode,
modifiers: event::get_key_mods(),
}),
});
}
}

0
} else {
winuser::DefWindowProcW(window, msg, wparam, lparam)
}

winuser::DefWindowProcW(window, msg, wparam, lparam)
},

winuser::WM_TOUCH => {
Expand Down
23 changes: 20 additions & 3 deletions src/platform/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,25 @@ unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
// TODO: document what this means
pub type Cursor = *const winapi::ctypes::wchar_t;

// Constant device ID, to be removed when this backend is updated to report real device IDs.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
pub struct DeviceId(u32);

impl DeviceId {
pub fn get_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: ::DeviceId = ::DeviceId(DeviceId(0));

fn wrap_device_id(id: u32) -> ::DeviceId {
::DeviceId(DeviceId(id))
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(HWND);
Expand All @@ -31,4 +46,6 @@ unsafe impl Sync for WindowId {}
mod event;
mod events_loop;
mod monitor;
mod raw_input;
mod util;
mod window;
Loading

0 comments on commit fe2d37f

Please sign in to comment.