Auto merge of #21881 - pyfisch:keyboard-types, r=paulrouget

Use keyboard-types crate

Have embedders send DOM keys to servo and use a strongly typed KeyboardEvent
from the W3C UI Events spec. All keyboard handling now uses the new types.

Introduce a ShortcutMatcher to recognize key bindings. Shortcuts are now
recognized in a uniform way.

Updated the winit port.
Updated webdriver integration.

part of #20331

What this PR does:

* allow the use non-ASCII keyboards for text input
* decouple keyboard event "key" from "code" (key meaning vs location)

What this PR does not do:

* completely improve keyboard events send from winit and webdriver
* add support for CompositionEvent or IME

Notes:

* The winit embedder does not send keyup events for printable keys (this is a regression)
* keyboard-types is on crates.io because I believe it to be useful outside of servo. If you prefer I can add a copy in this repo.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21881)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-10-17 05:36:08 -04:00 committed by GitHub
commit 9a0404ac5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 747 additions and 1673 deletions

View file

@ -408,7 +408,7 @@ impl ServoGlue {
EmbedderMsg::SelectFiles(..) |
EmbedderMsg::MoveTo(..) |
EmbedderMsg::ResizeTo(..) |
EmbedderMsg::KeyEvent(..) |
EmbedderMsg::Keyboard(..) |
EmbedderMsg::SetCursor(..) |
EmbedderMsg::NewFavicon(..) |
EmbedderMsg::HeadParsed |

View file

@ -40,6 +40,7 @@ crossbeam-channel = "0.2"
euclid = "0.19"
gleam = "0.6"
glutin = "0.18"
keyboard-types = {version = "0.4.2-servo", features = ["serde"]}
lazy_static = "1"
libservo = {path = "../../components/servo"}
log = "0.4"

View file

@ -5,10 +5,11 @@
use euclid::{TypedPoint2D, TypedVector2D};
use glutin_app::keyutils::{CMD_OR_CONTROL, CMD_OR_ALT};
use glutin_app::window::{Window, LINE_HEIGHT};
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent};
use servo::embedder_traits::{EmbedderMsg, FilterPattern};
use servo::msg::constellation_msg::{Key, TopLevelBrowsingContextId as BrowserId};
use servo::msg::constellation_msg::{KeyModifiers, KeyState, TraversalDirection};
use servo::msg::constellation_msg::{TopLevelBrowsingContextId as BrowserId};
use servo::msg::constellation_msg::TraversalDirection;
use servo::net_traits::pub_domains::is_reg_domain;
use servo::script_traits::TouchEventType;
use servo::servo_config::opts;
@ -70,8 +71,8 @@ impl Browser {
pub fn handle_window_events(&mut self, events: Vec<WindowEvent>) {
for event in events {
match event {
WindowEvent::KeyEvent(ch, key, state, mods) => {
self.handle_key_from_window(ch, key, state, mods);
WindowEvent::Keyboard(key_event) => {
self.handle_key_from_window(key_event);
},
event => {
self.event_queue.push(event);
@ -85,22 +86,14 @@ impl Browser {
}
/// Handle key events before sending them to Servo.
fn handle_key_from_window(
&mut self,
ch: Option<char>,
key: Key,
state: KeyState,
mods: KeyModifiers,
) {
let pressed = state == KeyState::Pressed;
// We don't match the state in the parent `match` because we don't want to do anything
// on KeyState::Released when it's a combo that we handle on Pressed. For example,
// if we catch Alt-Left on pressed, we don't want the Release event to be sent to Servo.
match (mods, ch, key, self.browser_id) {
(CMD_OR_CONTROL, _, Key::R, Some(id)) => if pressed {
self.event_queue.push(WindowEvent::Reload(id));
},
(CMD_OR_CONTROL, _, Key::L, Some(id)) => if pressed {
fn handle_key_from_window(&mut self, key_event: KeyboardEvent) {
ShortcutMatcher::from_event(key_event.clone())
.shortcut(CMD_OR_CONTROL, 'R', || {
if let Some(id) = self.browser_id {
self.event_queue.push(WindowEvent::Reload(id));
}
})
.shortcut(CMD_OR_CONTROL, 'L', || {
let url: String = if let Some(ref current_url) = self.current_url {
current_url.to_string()
} else {
@ -110,163 +103,130 @@ impl Browser {
let input = tinyfiledialogs::input_box(title, title, &url);
if let Some(input) = input {
if let Some(url) = sanitize_url(&input) {
self.event_queue.push(WindowEvent::LoadUrl(id, url));
if let Some(id) = self.browser_id {
self.event_queue.push(WindowEvent::LoadUrl(id, url));
}
}
}
},
(CMD_OR_CONTROL, _, Key::Q, _) => if pressed {
})
.shortcut(CMD_OR_CONTROL, 'Q', || {
self.event_queue.push(WindowEvent::Quit);
},
(_, Some('3'), _, _) if mods ^ KeyModifiers::CONTROL == KeyModifiers::SHIFT => {
if pressed {
self.event_queue.push(WindowEvent::CaptureWebRender);
})
.shortcut(Modifiers::CONTROL, Key::F9, || {
self.event_queue.push(WindowEvent::CaptureWebRender)
})
.shortcut(Modifiers::CONTROL, Key::F10, || {
self.event_queue.push(WindowEvent::ToggleWebRenderDebug(
WebRenderDebugOption::RenderTargetDebug,
));
})
.shortcut(Modifiers::CONTROL, Key::F11, || {
self.event_queue.push(WindowEvent::ToggleWebRenderDebug(
WebRenderDebugOption::TextureCacheDebug,
));
})
.shortcut(Modifiers::CONTROL, Key::F12, || {
self.event_queue.push(WindowEvent::ToggleWebRenderDebug(
WebRenderDebugOption::Profiler,
));
})
.shortcut(CMD_OR_ALT, Key::ArrowRight, || {
if let Some(id) = self.browser_id {
let event = WindowEvent::Navigation(id, TraversalDirection::Forward(1));
self.event_queue.push(event);
}
},
(KeyModifiers::CONTROL, None, Key::F10, _) => if pressed {
let event =
WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::RenderTargetDebug);
self.event_queue.push(event);
},
(KeyModifiers::CONTROL, None, Key::F11, _) => if pressed {
let event =
WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::TextureCacheDebug);
self.event_queue.push(event);
},
(KeyModifiers::CONTROL, None, Key::F12, _) => if pressed {
let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::Profiler);
self.event_queue.push(event);
},
(CMD_OR_ALT, None, Key::Right, Some(id)) |
(KeyModifiers::NONE, None, Key::NavigateForward, Some(id)) => if pressed {
let event = WindowEvent::Navigation(id, TraversalDirection::Forward(1));
self.event_queue.push(event);
},
(CMD_OR_ALT, None, Key::Left, Some(id)) |
(KeyModifiers::NONE, None, Key::NavigateBackward, Some(id)) => if pressed {
let event = WindowEvent::Navigation(id, TraversalDirection::Back(1));
self.event_queue.push(event);
},
(KeyModifiers::NONE, None, Key::Escape, _) => if pressed {
})
.shortcut(CMD_OR_ALT, Key::ArrowLeft, || {
if let Some(id) = self.browser_id {
let event = WindowEvent::Navigation(id, TraversalDirection::Back(1));
self.event_queue.push(event);
}
})
.shortcut(Modifiers::empty(), Key::Escape, || {
self.event_queue.push(WindowEvent::Quit);
},
_ => {
self.platform_handle_key(ch, key, mods, state);
},
}
})
.otherwise(|| self.platform_handle_key(key_event));
}
#[cfg(not(target_os = "win"))]
fn platform_handle_key(
&mut self,
ch: Option<char>,
key: Key,
mods: KeyModifiers,
state: KeyState,
) {
let pressed = state == KeyState::Pressed;
match (mods, key, self.browser_id) {
(CMD_OR_CONTROL, Key::LeftBracket, Some(id)) => if pressed {
let event = WindowEvent::Navigation(id, TraversalDirection::Back(1));
self.event_queue.push(event);
},
(CMD_OR_CONTROL, Key::RightBracket, Some(id)) => if pressed {
let event = WindowEvent::Navigation(id, TraversalDirection::Back(1));
self.event_queue.push(event);
},
_ => {
self.event_queue
.push(WindowEvent::KeyEvent(ch, key, state, mods));
},
fn platform_handle_key(&mut self, key_event: KeyboardEvent) {
if let Some(id) = self.browser_id {
if let Some(event) = ShortcutMatcher::from_event(key_event.clone())
.shortcut(CMD_OR_CONTROL, '[', || {
WindowEvent::Navigation(id, TraversalDirection::Back(1))
})
.shortcut(CMD_OR_CONTROL, ']', || {
WindowEvent::Navigation(id, TraversalDirection::Forward(1))
})
.otherwise(|| WindowEvent::Keyboard(key_event))
{
self.event_queue.push(event)
}
}
}
#[cfg(target_os = "win")]
fn platform_handle_key(
&mut self,
_ch: Option<char>,
_key: Key,
_mods: KeyModifiers,
_state: KeyState,
) {
}
fn platform_handle_key(&mut self, _key_event: KeyboardEvent) {}
/// Handle key events after they have been handled by Servo.
fn handle_key_from_servo(
&mut self,
_: Option<BrowserId>,
ch: Option<char>,
key: Key,
state: KeyState,
mods: KeyModifiers,
) {
if state == KeyState::Released {
return;
}
match (mods, ch, key) {
(CMD_OR_CONTROL, Some('='), _) | (CMD_OR_CONTROL, Some('+'), _) => {
self.event_queue.push(WindowEvent::Zoom(1.1));
},
(_, Some('='), _) if mods == (CMD_OR_CONTROL | KeyModifiers::SHIFT) => {
self.event_queue.push(WindowEvent::Zoom(1.1));
},
(CMD_OR_CONTROL, Some('-'), _) => {
self.event_queue.push(WindowEvent::Zoom(1.0 / 1.1));
},
(CMD_OR_CONTROL, Some('0'), _) => {
self.event_queue.push(WindowEvent::ResetZoom);
},
(KeyModifiers::NONE, None, Key::PageDown) => {
fn handle_key_from_servo(&mut self, _: Option<BrowserId>, event: KeyboardEvent) {
ShortcutMatcher::from_event(event)
.shortcut(CMD_OR_CONTROL, '=', || {
self.event_queue.push(WindowEvent::Zoom(1.1))
})
.shortcut(CMD_OR_CONTROL, '+', || {
self.event_queue.push(WindowEvent::Zoom(1.1))
})
.shortcut(CMD_OR_CONTROL, '-', || {
self.event_queue.push(WindowEvent::Zoom(1.0 / 1.1))
})
.shortcut(CMD_OR_CONTROL, '0', || {
self.event_queue.push(WindowEvent::ResetZoom)
})
.shortcut(Modifiers::empty(), Key::PageDown, || {
let scroll_location = ScrollLocation::Delta(TypedVector2D::new(
0.0,
-self.window.page_height() + 2.0 * LINE_HEIGHT,
));
self.scroll_window_from_key(scroll_location, TouchEventType::Move);
},
(KeyModifiers::NONE, None, Key::PageUp) => {
})
.shortcut(Modifiers::empty(), Key::PageUp, || {
let scroll_location = ScrollLocation::Delta(TypedVector2D::new(
0.0,
self.window.page_height() - 2.0 * LINE_HEIGHT,
));
self.scroll_window_from_key(scroll_location, TouchEventType::Move);
},
(KeyModifiers::NONE, None, Key::Home) => {
})
.shortcut(Modifiers::empty(), Key::Home, || {
self.scroll_window_from_key(ScrollLocation::Start, TouchEventType::Move);
},
(KeyModifiers::NONE, None, Key::End) => {
})
.shortcut(Modifiers::empty(), Key::End, || {
self.scroll_window_from_key(ScrollLocation::End, TouchEventType::Move);
},
(KeyModifiers::NONE, None, Key::Up) => {
})
.shortcut(Modifiers::empty(), Key::ArrowUp, || {
self.scroll_window_from_key(
ScrollLocation::Delta(TypedVector2D::new(0.0, 3.0 * LINE_HEIGHT)),
TouchEventType::Move,
);
},
(KeyModifiers::NONE, None, Key::Down) => {
})
.shortcut(Modifiers::empty(), Key::ArrowDown, || {
self.scroll_window_from_key(
ScrollLocation::Delta(TypedVector2D::new(0.0, -3.0 * LINE_HEIGHT)),
TouchEventType::Move,
);
},
(KeyModifiers::NONE, None, Key::Left) => {
})
.shortcut(Modifiers::empty(), Key::ArrowLeft, || {
self.scroll_window_from_key(
ScrollLocation::Delta(TypedVector2D::new(LINE_HEIGHT, 0.0)),
TouchEventType::Move,
);
},
(KeyModifiers::NONE, None, Key::Right) => {
})
.shortcut(Modifiers::empty(), Key::ArrowRight, || {
self.scroll_window_from_key(
ScrollLocation::Delta(TypedVector2D::new(-LINE_HEIGHT, 0.0)),
TouchEventType::Move,
);
},
_ => {},
}
});
}
fn scroll_window_from_key(&mut self, scroll_location: ScrollLocation, phase: TouchEventType) {
@ -311,7 +271,8 @@ impl Browser {
&message,
MessageBoxIcon::Warning,
);
}).unwrap()
})
.unwrap()
.join()
.expect("Thread spawning failed");
}
@ -350,8 +311,8 @@ impl Browser {
self.event_queue
.push(WindowEvent::SelectBrowser(new_browser_id));
},
EmbedderMsg::KeyEvent(ch, key, state, modified) => {
self.handle_key_from_servo(browser_id, ch, key, state, modified);
EmbedderMsg::Keyboard(key_event) => {
self.handle_key_from_servo(browser_id, key_event);
},
EmbedderMsg::SetCursor(cursor) => {
self.window.set_cursor(cursor);
@ -438,7 +399,8 @@ fn platform_get_selected_devices(devices: Vec<String>) -> Option<String> {
},
None => None,
}
}).unwrap()
})
.unwrap()
.join()
.expect("Thread spawning failed")
}
@ -480,7 +442,8 @@ fn get_selected_files(patterns: Vec<FilterPattern>, multiple_files: bool) -> Opt
let file = tinyfiledialogs::open_file_dialog(picker_name, "", filter_opt);
file.map(|x| vec![x])
}
}).unwrap()
})
.unwrap()
.join()
.expect("Thread spawning failed")
}
@ -495,7 +458,8 @@ fn sanitize_url(request: &str) -> Option<ServoUrl> {
} else {
None
}
}).or_else(|| {
})
.or_else(|| {
PREFS
.get("shell.searchpage")
.as_string()

View file

@ -2,215 +2,34 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use servo::msg::constellation_msg::{self, Key, KeyModifiers};
use winit::VirtualKeyCode;
use keyboard_types::{Code, Key, KeyboardEvent, KeyState, Modifiers, Location};
use winit::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode};
// Some shortcuts use Cmd on Mac and Control on other systems.
#[cfg(target_os = "macos")]
pub const CMD_OR_CONTROL: KeyModifiers = KeyModifiers::SUPER;
pub const CMD_OR_CONTROL: Modifiers = Modifiers::META;
#[cfg(not(target_os = "macos"))]
pub const CMD_OR_CONTROL: KeyModifiers = KeyModifiers::CONTROL;
pub const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL;
// Some shortcuts use Cmd on Mac and Alt on other systems.
#[cfg(target_os = "macos")]
pub const CMD_OR_ALT: KeyModifiers = KeyModifiers::SUPER;
pub const CMD_OR_ALT: Modifiers = Modifiers::META;
#[cfg(not(target_os = "macos"))]
pub const CMD_OR_ALT: KeyModifiers = KeyModifiers::ALT;
pub const CMD_OR_ALT: Modifiers = Modifiers::ALT;
pub fn char_to_script_key(c: char) -> Option<constellation_msg::Key> {
match c {
' ' => Some(Key::Space),
'"' => Some(Key::Apostrophe),
'\'' => Some(Key::Apostrophe),
'<' => Some(Key::Comma),
',' => Some(Key::Comma),
'_' => Some(Key::Minus),
'-' => Some(Key::Minus),
'>' => Some(Key::Period),
'.' => Some(Key::Period),
'?' => Some(Key::Slash),
'/' => Some(Key::Slash),
'~' => Some(Key::GraveAccent),
'`' => Some(Key::GraveAccent),
')' => Some(Key::Num0),
'0' => Some(Key::Num0),
'!' => Some(Key::Num1),
'1' => Some(Key::Num1),
'@' => Some(Key::Num2),
'2' => Some(Key::Num2),
'#' => Some(Key::Num3),
'3' => Some(Key::Num3),
'$' => Some(Key::Num4),
'4' => Some(Key::Num4),
'%' => Some(Key::Num5),
'5' => Some(Key::Num5),
'^' => Some(Key::Num6),
'6' => Some(Key::Num6),
'&' => Some(Key::Num7),
'7' => Some(Key::Num7),
'*' => Some(Key::Num8),
'8' => Some(Key::Num8),
'(' => Some(Key::Num9),
'9' => Some(Key::Num9),
':' => Some(Key::Semicolon),
';' => Some(Key::Semicolon),
'+' => Some(Key::Equal),
'=' => Some(Key::Equal),
'A' => Some(Key::A),
'a' => Some(Key::A),
'B' => Some(Key::B),
'b' => Some(Key::B),
'C' => Some(Key::C),
'c' => Some(Key::C),
'D' => Some(Key::D),
'd' => Some(Key::D),
'E' => Some(Key::E),
'e' => Some(Key::E),
'F' => Some(Key::F),
'f' => Some(Key::F),
'G' => Some(Key::G),
'g' => Some(Key::G),
'H' => Some(Key::H),
'h' => Some(Key::H),
'I' => Some(Key::I),
'i' => Some(Key::I),
'J' => Some(Key::J),
'j' => Some(Key::J),
'K' => Some(Key::K),
'k' => Some(Key::K),
'L' => Some(Key::L),
'l' => Some(Key::L),
'M' => Some(Key::M),
'm' => Some(Key::M),
'N' => Some(Key::N),
'n' => Some(Key::N),
'O' => Some(Key::O),
'o' => Some(Key::O),
'P' => Some(Key::P),
'p' => Some(Key::P),
'Q' => Some(Key::Q),
'q' => Some(Key::Q),
'R' => Some(Key::R),
'r' => Some(Key::R),
'S' => Some(Key::S),
's' => Some(Key::S),
'T' => Some(Key::T),
't' => Some(Key::T),
'U' => Some(Key::U),
'u' => Some(Key::U),
'V' => Some(Key::V),
'v' => Some(Key::V),
'W' => Some(Key::W),
'w' => Some(Key::W),
'X' => Some(Key::X),
'x' => Some(Key::X),
'Y' => Some(Key::Y),
'y' => Some(Key::Y),
'Z' => Some(Key::Z),
'z' => Some(Key::Z),
'{' => Some(Key::LeftBracket),
'[' => Some(Key::LeftBracket),
'|' => Some(Key::Backslash),
'\\' => Some(Key::Backslash),
'}' => Some(Key::RightBracket),
']' => Some(Key::RightBracket),
_ => None,
}
}
pub fn winit_key_to_script_key(key: VirtualKeyCode) -> Result<constellation_msg::Key, ()> {
fn get_servo_key_from_winit_key(key: Option<VirtualKeyCode>) -> Key {
use winit::VirtualKeyCode::*;
// TODO(negge): add more key mappings
Ok(match key {
A => Key::A,
B => Key::B,
C => Key::C,
D => Key::D,
E => Key::E,
F => Key::F,
G => Key::G,
H => Key::H,
I => Key::I,
J => Key::J,
K => Key::K,
L => Key::L,
M => Key::M,
N => Key::N,
O => Key::O,
P => Key::P,
Q => Key::Q,
R => Key::R,
S => Key::S,
T => Key::T,
U => Key::U,
V => Key::V,
W => Key::W,
X => Key::X,
Y => Key::Y,
Z => Key::Z,
Numpad0 => Key::Kp0,
Numpad1 => Key::Kp1,
Numpad2 => Key::Kp2,
Numpad3 => Key::Kp3,
Numpad4 => Key::Kp4,
Numpad5 => Key::Kp5,
Numpad6 => Key::Kp6,
Numpad7 => Key::Kp7,
Numpad8 => Key::Kp8,
Numpad9 => Key::Kp9,
Key0 => Key::Num0,
Key1 => Key::Num1,
Key2 => Key::Num2,
Key3 => Key::Num3,
Key4 => Key::Num4,
Key5 => Key::Num5,
Key6 => Key::Num6,
Key7 => Key::Num7,
Key8 => Key::Num8,
Key9 => Key::Num9,
Return => Key::Enter,
Space => Key::Space,
// TODO: figure out how to map NavigateForward, NavigateBackward
// TODO: map the remaining keys if possible
let key = if let Some(key) = key {
key
} else {
return Key::Unidentified;
};
match key {
// printable: Key1 to Key0
// printable: A to Z
Escape => Key::Escape,
Equals => Key::Equal,
Minus => Key::Minus,
Back => Key::Backspace,
PageDown => Key::PageDown,
PageUp => Key::PageUp,
Insert => Key::Insert,
Home => Key::Home,
Delete => Key::Delete,
End => Key::End,
Left => Key::Left,
Up => Key::Up,
Right => Key::Right,
Down => Key::Down,
LShift => Key::LeftShift,
LControl => Key::LeftControl,
LAlt => Key::LeftAlt,
LWin => Key::LeftSuper,
RShift => Key::RightShift,
RControl => Key::RightControl,
RAlt => Key::RightAlt,
RWin => Key::RightSuper,
Apostrophe => Key::Apostrophe,
Backslash => Key::Backslash,
Comma => Key::Comma,
Grave => Key::GraveAccent,
LBracket => Key::LeftBracket,
Period => Key::Period,
RBracket => Key::RightBracket,
Semicolon => Key::Semicolon,
Slash => Key::Slash,
Tab => Key::Tab,
Subtract => Key::Minus,
F1 => Key::F1,
F2 => Key::F2,
F3 => Key::F3,
@ -223,23 +42,226 @@ pub fn winit_key_to_script_key(key: VirtualKeyCode) -> Result<constellation_msg:
F10 => Key::F10,
F11 => Key::F11,
F12 => Key::F12,
NavigateBackward => Key::NavigateBackward,
NavigateForward => Key::NavigateForward,
_ => return Err(()),
})
}
pub fn is_printable(key_code: VirtualKeyCode) -> bool {
use winit::VirtualKeyCode::*;
match key_code {
Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 |
F15 | Snapshot | Scroll | Pause | Insert | Home | Delete | End | PageDown | PageUp |
Left | Up | Right | Down | Back | LAlt | LControl | LShift | LWin | Mail |
MediaSelect | MediaStop | Mute | MyComputer | NavigateForward | NavigateBackward |
NextTrack | NoConvert | PlayPause | Power | PrevTrack | RAlt | RControl | RShift |
RWin | Sleep | Stop | VolumeDown | VolumeUp | Wake | WebBack | WebFavorites |
WebForward | WebHome | WebRefresh | WebSearch | WebStop => false,
_ => true,
// F13 to F15 are not mapped
Snapshot => Key::PrintScreen,
// Scroll not mapped
Pause => Key::Pause,
Insert => Key::Insert,
Home => Key::Home,
Delete => Key::Delete,
End => Key::End,
PageDown => Key::PageDown,
PageUp => Key::PageUp,
Left => Key::ArrowLeft,
Up => Key::ArrowUp,
Right => Key::ArrowRight,
Down => Key::ArrowDown,
Back => Key::Backspace,
Return => Key::Enter,
// printable: Space
Compose => Key::Compose,
// Caret not mapped
Numlock => Key::NumLock,
// printable: Numpad0 to Numpad9
// AbntC1 and AbntC2 not mapped
// printable: Add, Apostrophe,
// Apps, At, Ax not mapped
// printable: Backslash,
Calculator => Key::LaunchApplication2,
Capital => Key::CapsLock,
// printable: Colon, Comma,
Convert => Key::Convert,
// not mapped: Decimal,
// printable: Divide, Equals, Grave,
Kana => Key::KanaMode,
Kanji => Key::KanjiMode,
LAlt => Key::Alt,
// printable: LBracket,
LControl => Key::Control,
LShift => Key::Shift,
LWin => Key::Meta,
Mail => Key::LaunchMail,
// not mapped: MediaSelect,
MediaStop => Key::MediaStop,
// printable: Minus, Multiply,
Mute => Key::AudioVolumeMute,
MyComputer => Key::LaunchApplication1,
// not mapped: NavigateForward, NavigateBackward
NextTrack => Key::MediaTrackNext,
NoConvert => Key::NonConvert,
// printable: NumpadComma, NumpadEnter, NumpadEquals,
// not mapped: OEM102,
// printable: Period,
PlayPause => Key::MediaPlayPause,
Power => Key::Power,
PrevTrack => Key::MediaTrackPrevious,
RAlt => Key::Alt,
// printable RBracket
RControl => Key::Control,
RShift => Key::Shift,
RWin => Key::Meta,
// printable Semicolon, Slash
Sleep => Key::Standby,
// not mapped: Stop,
// printable Subtract,
// not mapped: Sysrq,
Tab => Key::Tab,
// printable: Underline,
// not mapped: Unlabeled,
VolumeDown => Key::AudioVolumeDown,
VolumeUp => Key::AudioVolumeUp,
Wake => Key::WakeUp,
WebBack => Key::BrowserBack,
WebFavorites => Key::BrowserFavorites,
WebForward => Key::BrowserForward,
WebHome => Key::BrowserHome,
WebRefresh => Key::BrowserRefresh,
WebSearch => Key::BrowserSearch,
WebStop => Key::BrowserStop,
// printable Yen,
Copy => Key::Copy,
Paste => Key::Paste,
Cut => Key::Cut,
_ => Key::Unidentified,
}
}
fn get_servo_location_from_winit_key(key: Option<VirtualKeyCode>) -> Location {
use winit::VirtualKeyCode::*;
// TODO: add more numpad keys
let key = if let Some(key) = key {
key
} else {
return Location::Standard;
};
match key {
LShift | LControl | LAlt | LWin => Location::Left,
RShift | RControl | RAlt | RWin => Location::Right,
Numpad0 | Numpad1 | Numpad2 | Numpad3 | Numpad4 | Numpad5 | Numpad6 | Numpad7 |
Numpad8 | Numpad9 => Location::Numpad,
NumpadComma | NumpadEnter | NumpadEquals => Location::Numpad,
_ => Location::Standard,
}
}
#[cfg(target_os = "linux")]
fn get_servo_code_from_scancode(scancode: u32) -> Code {
// TODO: Map more codes
use keyboard_types::Code::*;
match scancode {
1 => Escape,
2 => Digit1,
3 => Digit2,
4 => Digit3,
5 => Digit4,
6 => Digit5,
7 => Digit6,
8 => Digit7,
9 => Digit8,
10 => Digit9,
11 => Digit0,
14 => Backspace,
15 => Tab,
16 => KeyQ,
17 => KeyW,
18 => KeyE,
19 => KeyR,
20 => KeyT,
21 => KeyY,
22 => KeyU,
23 => KeyI,
24 => KeyO,
25 => KeyP,
26 => BracketLeft,
27 => BracketRight,
28 => Enter,
30 => KeyA,
31 => KeyS,
32 => KeyD,
33 => KeyF,
34 => KeyG,
35 => KeyH,
36 => KeyJ,
37 => KeyK,
38 => KeyL,
39 => Semicolon,
40 => Quote,
42 => ShiftLeft,
43 => Backslash,
44 => KeyZ,
45 => KeyX,
46 => KeyC,
47 => KeyV,
48 => KeyB,
49 => KeyN,
50 => KeyM,
51 => Comma,
52 => Period,
53 => Slash,
54 => ShiftRight,
57 => Space,
59 => F1,
60 => F2,
61 => F3,
62 => F4,
63 => F5,
64 => F6,
65 => F7,
66 => F8,
67 => F9,
68 => F10,
87 => F11,
88 => F12,
103 => ArrowUp,
104 => PageUp,
105 => ArrowLeft,
106 => ArrowRight,
102 => Home,
107 => End,
108 => ArrowDown,
109 => PageDown,
110 => Insert,
111 => Delete,
_ => Unidentified,
}
}
#[cfg(not(target_os = "linux"))]
fn get_servo_code_from_scancode(_scancode: u32) -> Code {
// TODO: Implement for Windows and Mac OS
Code::Unidentified
}
fn get_modifiers(mods: ModifiersState) -> Modifiers {
let mut modifiers = Modifiers::empty();
modifiers.set(Modifiers::CONTROL, mods.ctrl);
modifiers.set(Modifiers::SHIFT, mods.shift);
modifiers.set(Modifiers::ALT, mods.alt);
modifiers.set(Modifiers::META, mods.logo);
modifiers
}
pub fn keyboard_event_from_winit(input: KeyboardInput) -> KeyboardEvent {
info!("winit keyboard input: {:?}", input);
KeyboardEvent {
state: match input.state {
ElementState::Pressed => KeyState::Down,
ElementState::Released => KeyState::Up,
},
key: get_servo_key_from_winit_key(input.virtual_keycode),
code: get_servo_code_from_scancode(input.scancode),
location: get_servo_location_from_winit_key(input.virtual_keycode),
modifiers: get_modifiers(input.modifiers),
repeat: false,
is_composing: false,
}
}

View file

@ -9,12 +9,12 @@ use euclid::{Length, TypedPoint2D, TypedVector2D, TypedScale, TypedSize2D};
use gdi32;
use gleam::gl;
use glutin::{Api, ContextBuilder, GlContext, GlRequest, GlWindow};
use keyboard_types::{Key, KeyboardEvent, KeyState};
#[cfg(any(target_os = "linux", target_os = "macos"))]
use osmesa_sys;
use servo::compositing::windowing::{AnimationState, MouseWindowEvent, WindowEvent};
use servo::compositing::windowing::{EmbedderCoordinates, WindowMethods};
use servo::embedder_traits::EventLoopWaker;
use servo::msg::constellation_msg::{Key, KeyState, KeyModifiers};
use servo::script_traits::TouchEventType;
use servo::servo_config::opts;
use servo::servo_geometry::DeviceIndependentPixel;
@ -31,13 +31,13 @@ use std::rc::Rc;
use std::sync::Arc;
use std::thread;
use std::time;
use super::keyutils;
use super::keyutils::keyboard_event_from_winit;
#[cfg(target_os = "windows")]
use user32;
#[cfg(target_os = "windows")]
use winapi;
use winit;
use winit::{ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
use winit::{ElementState, Event, MouseButton, MouseScrollDelta, TouchPhase, KeyboardInput};
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
#[cfg(target_os = "macos")]
use winit::os::macos::{ActivationPolicy, WindowBuilderExt};
@ -150,8 +150,7 @@ pub struct Window {
mouse_down_point: Cell<TypedPoint2D<i32, DevicePixel>>,
event_queue: RefCell<Vec<WindowEvent>>,
mouse_pos: Cell<TypedPoint2D<i32, DevicePixel>>,
key_modifiers: Cell<KeyModifiers>,
last_pressed_key: Cell<Option<Key>>,
last_pressed: Cell<Option<KeyboardEvent>>,
animation_state: Cell<AnimationState>,
fullscreen: Cell<bool>,
gl: Rc<gl::Gl>,
@ -276,11 +275,8 @@ impl Window {
event_queue: RefCell::new(vec![]),
mouse_down_button: Cell::new(None),
mouse_down_point: Cell::new(TypedPoint2D::new(0, 0)),
mouse_pos: Cell::new(TypedPoint2D::new(0, 0)),
key_modifiers: Cell::new(KeyModifiers::empty()),
last_pressed_key: Cell::new(None),
last_pressed: Cell::new(None),
gl: gl.clone(),
animation_state: Cell::new(AnimationState::Idle),
fullscreen: Cell::new(false),
@ -408,56 +404,46 @@ impl Window {
GlRequest::Specific(Api::OpenGlEs, (3, 0))
}
fn handle_received_character(&self, ch: char) {
let last_key = if let Some(key) = self.last_pressed_key.get() {
key
fn handle_received_character(&self, mut ch: char) {
info!("winit received character: {:?}", ch);
if ch.is_control() {
if ch as u8 >= 32 {
return;
}
// shift ASCII control characters to lowercase
ch = (ch as u8 + 96) as char;
}
let mut event = if let Some(event) = self.last_pressed.replace(None) {
event
} else if ch.is_ascii() {
// Some keys like Backspace emit a control character in winit
// but they are already dealt with in handle_keyboard_input
// so just ignore the character.
return
} else {
return;
// For combined characters like the letter e with an acute accent
// no keyboard event is emitted. A dummy event is created in this case.
KeyboardEvent::default()
};
self.last_pressed_key.set(None);
let (key, ch) = if let Some(key) = keyutils::char_to_script_key(ch) {
(key, Some(ch))
} else {
(last_key, None)
};
let modifiers = self.key_modifiers.get();
let event = WindowEvent::KeyEvent(ch, key, KeyState::Pressed, modifiers);
self.event_queue.borrow_mut().push(event);
}
fn toggle_keyboard_modifiers(&self, mods: ModifiersState) {
self.toggle_modifier(KeyModifiers::CONTROL, mods.ctrl);
self.toggle_modifier(KeyModifiers::SHIFT, mods.shift);
self.toggle_modifier(KeyModifiers::ALT, mods.alt);
self.toggle_modifier(KeyModifiers::SUPER, mods.logo);
event.key = Key::Character(ch.to_string());
self.event_queue
.borrow_mut()
.push(WindowEvent::Keyboard(event));
}
fn handle_keyboard_input(
&self,
element_state: ElementState,
code: VirtualKeyCode,
mods: ModifiersState,
input: KeyboardInput,
) {
self.toggle_keyboard_modifiers(mods);
if let Ok(key) = keyutils::winit_key_to_script_key(code) {
let state = match element_state {
ElementState::Pressed => KeyState::Pressed,
ElementState::Released => KeyState::Released,
};
if element_state == ElementState::Pressed && keyutils::is_printable(code) {
// If pressed and printable, we expect a ReceivedCharacter event.
self.last_pressed_key.set(Some(key));
} else {
self.last_pressed_key.set(None);
let modifiers = self.key_modifiers.get();
self.event_queue
.borrow_mut()
.push(WindowEvent::KeyEvent(None, key, state, modifiers));
}
let event = keyboard_event_from_winit(input);
if event.state == KeyState::Down && event.key == Key::Unidentified {
// If pressed and probably printable, we expect a ReceivedCharacter event.
self.last_pressed.set(Some(event));
} else if event.key != Key::Unidentified {
self.last_pressed.set(None);
self.event_queue
.borrow_mut()
.push(WindowEvent::Keyboard(event));
}
}
@ -470,17 +456,11 @@ impl Window {
Event::WindowEvent {
event:
winit::WindowEvent::KeyboardInput {
input:
winit::KeyboardInput {
state,
virtual_keycode: Some(virtual_keycode),
modifiers,
..
},
input,
..
},
..
} => self.handle_keyboard_input(state, virtual_keycode, modifiers),
} => self.handle_keyboard_input(input),
Event::WindowEvent {
event: winit::WindowEvent::MouseInput { state, button, .. },
..
@ -584,16 +564,6 @@ impl Window {
}
}
fn toggle_modifier(&self, modifier: KeyModifiers, pressed: bool) {
let mut modifiers = self.key_modifiers.get();
if pressed {
modifiers.insert(modifier);
} else {
modifiers.remove(modifier);
}
self.key_modifiers.set(modifiers);
}
/// Helper function to handle a click
fn handle_mouse(
&self,

View file

@ -8,6 +8,7 @@ extern crate euclid;
extern crate gdi32;
extern crate gleam;
extern crate glutin;
extern crate keyboard_types;
#[macro_use]
extern crate lazy_static;
#[cfg(any(target_os = "linux", target_os = "macos"))]