aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2016-07-06 02:51:50 -0700
committerGitHub <noreply@github.com>2016-07-06 02:51:50 -0700
commit68fb9ebc413f9cfc1ad4ca578d904c164836db74 (patch)
treead09aee288d37a96a8f3b8fec61bc8f1eba93b91
parent23f5264e1c0b48ed0b04defdaa070a4336781d95 (diff)
parent6496d73210ed26b919b7910400d5a624e11bf646 (diff)
downloadservo-68fb9ebc413f9cfc1ad4ca578d904c164836db74.tar.gz
servo-68fb9ebc413f9cfc1ad4ca578d904c164836db74.zip
Auto merge of #11950 - jdm:keylayout2, r=emilio
Support non-QWERTY keyboards Using the ReceivedCharacter event from glutin, we can obtain the actual key characters that the user is pressing and releasing. This gets passed to the script thread along with the physical key data, since KeyboardEvent needs both pieces of information, where they get merged into a single logical key that gets processed by clients like TextInput without any special changes. Tested by switching my macbook keyboard to dvorak and looking at the output of keypress/keyup/keydown event listeners, as well as playing with tests/html/textarea.html. Non-content keybindings like reload work as expected, too - the remapped keybinding triggers the reload action. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #4144 - [X] These changes do not require tests because I can't think of a way to test remapped keyboard input Fixes #11991. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/11950) <!-- Reviewable:end -->
-rw-r--r--components/compositing/compositor.rs12
-rw-r--r--components/compositing/compositor_thread.rs2
-rw-r--r--components/compositing/windowing.rs4
-rw-r--r--components/constellation/constellation.rs16
-rw-r--r--components/script/dom/bindings/str.rs11
-rw-r--r--components/script/dom/bindings/trace.rs2
-rw-r--r--components/script/dom/document.rs9
-rw-r--r--components/script/dom/keyboardevent.rs42
-rw-r--r--components/script/script_thread.rs4
-rw-r--r--components/script/textinput.rs68
-rw-r--r--components/script_traits/lib.rs4
-rw-r--r--components/script_traits/script_msg.rs2
-rw-r--r--components/servo/Cargo.lock4
-rw-r--r--ports/cef/Cargo.lock4
-rw-r--r--ports/cef/browser_host.rs4
-rw-r--r--ports/cef/window.rs2
-rw-r--r--ports/geckolib/Cargo.lock4
-rw-r--r--ports/glutin/window.rs159
-rw-r--r--tests/unit/script/textinput.rs2
19 files changed, 233 insertions, 122 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs
index 270a6b656d0..8041fd7f1df 100644
--- a/components/compositing/compositor.rs
+++ b/components/compositing/compositor.rs
@@ -701,9 +701,9 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.composition_request = CompositionRequest::CompositeNow(reason)
}
- (Msg::KeyEvent(key, state, modified), ShutdownState::NotShuttingDown) => {
+ (Msg::KeyEvent(ch, key, state, modified), ShutdownState::NotShuttingDown) => {
if state == KeyState::Pressed {
- self.window.handle_key(key, modified);
+ self.window.handle_key(ch, key, modified);
}
}
@@ -1348,8 +1348,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.on_touchpad_pressure_event(cursor, pressure, stage);
}
- WindowEvent::KeyEvent(key, state, modifiers) => {
- self.on_key_event(key, state, modifiers);
+ WindowEvent::KeyEvent(ch, key, state, modifiers) => {
+ self.on_key_event(ch, key, state, modifiers);
}
WindowEvent::Quit => {
@@ -1880,8 +1880,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
}
- fn on_key_event(&self, key: Key, state: KeyState, modifiers: KeyModifiers) {
- let msg = ConstellationMsg::KeyEvent(key, state, modifiers);
+ fn on_key_event(&self, ch: Option<char>, key: Key, state: KeyState, modifiers: KeyModifiers) {
+ let msg = ConstellationMsg::KeyEvent(ch, key, state, modifiers);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending key event to constellation failed ({}).", e);
}
diff --git a/components/compositing/compositor_thread.rs b/components/compositing/compositor_thread.rs
index 24b9caba0a7..49d0692e6c8 100644
--- a/components/compositing/compositor_thread.rs
+++ b/components/compositing/compositor_thread.rs
@@ -151,7 +151,7 @@ pub enum Msg {
/// Composite.
Recomposite(CompositingReason),
/// Sends an unconsumed key event back to the compositor.
- KeyEvent(Key, KeyState, KeyModifiers),
+ KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
/// Script has handled a touch event, and either prevented or allowed default actions.
TouchEventProcessed(EventResult),
/// Changes the cursor.
diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs
index b67803b07c6..2da658a9661 100644
--- a/components/compositing/windowing.rs
+++ b/components/compositing/windowing.rs
@@ -76,7 +76,7 @@ pub enum WindowEvent {
/// Sent when the user quits the application
Quit,
/// Sent when a key input state changes
- KeyEvent(Key, KeyState, KeyModifiers),
+ KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
/// Sent when Ctr+R/Apple+R is called to reload the current page.
Reload,
}
@@ -159,7 +159,7 @@ pub trait WindowMethods {
fn set_cursor(&self, cursor: Cursor);
/// Process a key event.
- fn handle_key(&self, key: Key, mods: KeyModifiers);
+ fn handle_key(&self, ch: Option<char>, key: Key, mods: KeyModifiers);
/// Does this window support a clipboard
fn supports_clipboard(&self) -> bool;
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs
index 7c3a95dbc54..0920a4a523f 100644
--- a/components/constellation/constellation.rs
+++ b/components/constellation/constellation.rs
@@ -574,9 +574,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
debug!("constellation got get-pipeline-title message");
self.handle_get_pipeline_title_msg(pipeline_id);
}
- FromCompositorMsg::KeyEvent(key, state, modifiers) => {
+ FromCompositorMsg::KeyEvent(ch, key, state, modifiers) => {
debug!("constellation got key event message");
- self.handle_key_msg(key, state, modifiers);
+ self.handle_key_msg(ch, key, state, modifiers);
}
// Load a new page from a typed url
// If there is already a pending page (self.pending_frames), it will not be overridden;
@@ -803,8 +803,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
self.compositor_proxy.send(ToCompositorMsg::ChangePageTitle(pipeline_id, title))
}
- FromScriptMsg::SendKeyEvent(key, key_state, key_modifiers) => {
- self.compositor_proxy.send(ToCompositorMsg::KeyEvent(key, key_state, key_modifiers))
+ FromScriptMsg::SendKeyEvent(ch, key, key_state, key_modifiers) => {
+ self.compositor_proxy.send(ToCompositorMsg::KeyEvent(ch, key, key_state, key_modifiers))
}
FromScriptMsg::TouchEventProcessed(result) => {
@@ -1396,7 +1396,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
}
}
- fn handle_key_msg(&mut self, key: Key, state: KeyState, mods: KeyModifiers) {
+ fn handle_key_msg(&mut self, ch: Option<char>, key: Key, state: KeyState, mods: KeyModifiers) {
// Send to the explicitly focused pipeline (if it exists), or the root
// frame's current pipeline. If neither exist, fall back to sending to
// the compositor below.
@@ -1407,7 +1407,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
match pipeline_id {
Some(pipeline_id) => {
- let event = CompositorEvent::KeyEvent(key, state, mods);
+ let event = CompositorEvent::KeyEvent(ch, key, state, mods);
let msg = ConstellationControlMsg::SendEvent(pipeline_id, event);
let result = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline.script_chan.send(msg),
@@ -1418,7 +1418,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
}
},
None => {
- let event = ToCompositorMsg::KeyEvent(key, state, mods);
+ let event = ToCompositorMsg::KeyEvent(ch, key, state, mods);
self.compositor_proxy.clone_compositor_proxy().send(event);
}
}
@@ -1629,7 +1629,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
None => return warn!("Pipeline {:?} SendKeys after closure.", pipeline_id),
};
for (key, mods, state) in cmd {
- let event = CompositorEvent::KeyEvent(key, state, mods);
+ let event = CompositorEvent::KeyEvent(None, key, state, mods);
let control_msg = ConstellationControlMsg::SendEvent(pipeline_id, event);
if let Err(e) = script_channel.send(control_msg) {
return self.handle_send_error(pipeline_id, e);
diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs
index 69565520899..310285a8bcd 100644
--- a/components/script/dom/bindings/str.rs
+++ b/components/script/dom/bindings/str.rs
@@ -5,7 +5,7 @@
//! The `ByteString` struct.
use std::ascii::AsciiExt;
-use std::borrow::ToOwned;
+use std::borrow::{ToOwned, Cow};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops;
@@ -204,6 +204,15 @@ impl<'a> From<&'a str> for DOMString {
}
}
+impl<'a> From<Cow<'a, str>> for DOMString {
+ fn from(contents: Cow<'a, str>) -> DOMString {
+ match contents {
+ Cow::Owned(s) => DOMString::from(s),
+ Cow::Borrowed(s) => DOMString::from(s),
+ }
+ }
+}
+
impl From<DOMString> for Atom {
fn from(contents: DOMString) -> Atom {
Atom::from(contents.0)
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
index 7404e96b95d..e2c6e540a3d 100644
--- a/components/script/dom/bindings/trace.rs
+++ b/components/script/dom/bindings/trace.rs
@@ -276,7 +276,7 @@ impl<A: JSTraceable, B: JSTraceable, C: JSTraceable> JSTraceable for (A, B, C) {
}
}
-no_jsmanaged_fields!(bool, f32, f64, String, Url, AtomicBool, AtomicUsize, UrlOrigin, Uuid);
+no_jsmanaged_fields!(bool, f32, f64, String, Url, AtomicBool, AtomicUsize, UrlOrigin, Uuid, char);
no_jsmanaged_fields!(usize, u8, u16, u32, u64);
no_jsmanaged_fields!(isize, i8, i16, i32, i64);
no_jsmanaged_fields!(Sender<T>);
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 2160a3a4556..43a4473689e 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -1044,6 +1044,7 @@ impl Document {
/// The entry point for all key processing for web content
pub fn dispatch_key_event(&self,
+ ch: Option<char>,
key: Key,
state: KeyState,
modifiers: KeyModifiers,
@@ -1070,7 +1071,7 @@ impl Document {
}
.to_owned());
- let props = KeyboardEvent::key_properties(key, modifiers);
+ let props = KeyboardEvent::key_properties(ch, key, modifiers);
let keyevent = KeyboardEvent::new(&self.window,
ev_type,
@@ -1078,8 +1079,9 @@ impl Document {
true,
Some(&self.window),
0,
+ ch,
Some(key),
- DOMString::from(props.key_string),
+ DOMString::from(props.key_string.clone()),
DOMString::from(props.code),
props.location,
is_repeating,
@@ -1103,6 +1105,7 @@ impl Document {
true,
Some(&self.window),
0,
+ ch,
Some(key),
DOMString::from(props.key_string),
DOMString::from(props.code),
@@ -1122,7 +1125,7 @@ impl Document {
}
if !prevented {
- constellation.send(ConstellationMsg::SendKeyEvent(key, state, modifiers)).unwrap();
+ constellation.send(ConstellationMsg::SendKeyEvent(ch, key, state, modifiers)).unwrap();
}
// This behavior is unspecced
diff --git a/components/script/dom/keyboardevent.rs b/components/script/dom/keyboardevent.rs
index 4cfa1a61011..71b00767a8e 100644
--- a/components/script/dom/keyboardevent.rs
+++ b/components/script/dom/keyboardevent.rs
@@ -17,6 +17,7 @@ use dom::uievent::UIEvent;
use dom::window::Window;
use msg::constellation_msg;
use msg::constellation_msg::{Key, KeyModifiers};
+use std::borrow::Cow;
use std::cell::Cell;
no_jsmanaged_fields!(Key);
@@ -36,6 +37,7 @@ pub struct KeyboardEvent {
is_composing: Cell<bool>,
char_code: Cell<Option<u32>>,
key_code: Cell<u32>,
+ printable: Cell<Option<char>>,
}
impl KeyboardEvent {
@@ -54,6 +56,7 @@ impl KeyboardEvent {
is_composing: Cell::new(false),
char_code: Cell::new(None),
key_code: Cell::new(0),
+ printable: Cell::new(None),
}
}
@@ -69,6 +72,7 @@ impl KeyboardEvent {
cancelable: bool,
view: Option<&Window>,
_detail: i32,
+ ch: Option<char>,
key: Option<Key>,
key_string: DOMString,
code: DOMString,
@@ -91,6 +95,7 @@ impl KeyboardEvent {
ev.shift.set(shiftKey);
ev.meta.set(metaKey);
ev.char_code.set(char_code);
+ ev.printable.set(ch);
ev.key_code.set(key_code);
ev.is_composing.set(isComposing);
ev
@@ -103,7 +108,9 @@ impl KeyboardEvent {
init.parent.parent.parent.bubbles,
init.parent.parent.parent.cancelable,
init.parent.parent.view.r(),
- init.parent.parent.detail, key_from_string(&init.key, init.location),
+ init.parent.parent.detail,
+ None,
+ key_from_string(&init.key, init.location),
init.key.clone(), init.code.clone(), init.location,
init.repeat, init.isComposing, init.parent.ctrlKey,
init.parent.altKey, init.parent.shiftKey, init.parent.metaKey,
@@ -111,13 +118,13 @@ impl KeyboardEvent {
Ok(event)
}
- pub fn key_properties(key: Key, mods: KeyModifiers)
+ pub fn key_properties(ch: Option<char>, key: Key, mods: KeyModifiers)
-> KeyEventProperties {
KeyEventProperties {
- key_string: key_value(key, mods),
+ key_string: key_value(ch, key, mods),
code: code_value(key),
location: key_location(key),
- char_code: key_charcode(key, mods),
+ char_code: ch.map(|ch| ch as u32),
key_code: key_keycode(key),
}
}
@@ -125,6 +132,10 @@ impl KeyboardEvent {
impl KeyboardEvent {
+ pub fn printable(&self) -> Option<char> {
+ self.printable.get()
+ }
+
pub fn get_key(&self) -> Option<Key> {
self.key.get().clone()
}
@@ -147,11 +158,14 @@ impl KeyboardEvent {
}
}
-
// https://w3c.github.io/uievents-key/#key-value-tables
-pub fn key_value(key: Key, mods: KeyModifiers) -> &'static str {
+pub fn key_value(ch: Option<char>, key: Key, mods: KeyModifiers) -> Cow<'static, str> {
+ if let Some(ch) = ch {
+ return Cow::from(format!("{}", ch));
+ }
+
let shift = mods.contains(constellation_msg::SHIFT);
- match key {
+ Cow::from(match key {
Key::Space => " ",
Key::Apostrophe if shift => "\"",
Key::Apostrophe => "'",
@@ -321,7 +335,7 @@ pub fn key_value(key: Key, mods: KeyModifiers) -> &'static str {
Key::Menu => "ContextMenu",
Key::NavigateForward => "BrowserForward",
Key::NavigateBackward => "BrowserBack",
- }
+ })
}
fn key_from_string(key_string: &str, location: u32) -> Option<Key> {
@@ -647,16 +661,6 @@ fn key_location(key: Key) -> u32 {
}
}
-// https://w3c.github.io/uievents/#dom-keyboardevent-charcode
-fn key_charcode(key: Key, mods: KeyModifiers) -> Option<u32> {
- let key_string = key_value(key, mods);
- if key_string.len() == 1 {
- Some(key_string.chars().next().unwrap() as u32)
- } else {
- None
- }
-}
-
// https://w3c.github.io/uievents/#legacy-key-models
fn key_keycode(key: Key) -> u32 {
match key {
@@ -739,7 +743,7 @@ fn key_keycode(key: Key) -> u32 {
#[derive(HeapSizeOf)]
pub struct KeyEventProperties {
- pub key_string: &'static str,
+ pub key_string: Cow<'static, str>,
pub code: &'static str,
pub location: u32,
pub char_code: Option<u32>,
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 2367ecc0fd9..05689baac39 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -1937,12 +1937,12 @@ impl ScriptThread {
document.r().handle_touchpad_pressure_event(self.js_runtime.rt(), point, pressure, phase);
}
- KeyEvent(key, state, modifiers) => {
+ KeyEvent(ch, key, state, modifiers) => {
let document = match self.root_browsing_context().find(pipeline_id) {
Some(browsing_context) => browsing_context.active_document(),
None => return warn!("Message sent to closed pipeline {}.", pipeline_id),
};
- document.dispatch_key_event(key, state, modifiers, &self.constellation_chan);
+ document.dispatch_key_event(ch, key, state, modifiers, &self.constellation_chan);
}
}
}
diff --git a/components/script/textinput.rs b/components/script/textinput.rs
index a56bd983f34..871af9637c0 100644
--- a/components/script/textinput.rs
+++ b/components/script/textinput.rs
@@ -6,7 +6,7 @@
use clipboard_provider::ClipboardProvider;
use dom::bindings::str::DOMString;
-use dom::keyboardevent::{KeyboardEvent, key_value};
+use dom::keyboardevent::KeyboardEvent;
use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER};
use msg::constellation_msg::{Key, KeyModifiers};
use std::borrow::ToOwned;
@@ -120,24 +120,6 @@ fn is_control_key(mods: KeyModifiers) -> bool {
mods.contains(CONTROL) && !mods.contains(SUPER | ALT)
}
-fn is_printable_key(key: Key) -> bool {
- match key {
- Key::Space | Key::Apostrophe | Key::Comma | Key::Minus |
- Key::Period | Key::Slash | Key::GraveAccent | Key::Num0 |
- Key::Num1 | Key::Num2 | Key::Num3 | Key::Num4 | Key::Num5 |
- Key::Num6 | Key::Num7 | Key::Num8 | Key::Num9 | Key::Semicolon |
- Key::Equal | Key::A | Key::B | Key::C | Key::D | Key::E | Key::F |
- Key::G | Key::H | Key::I | Key::J | Key::K | Key::L | Key::M | Key::N |
- Key::O | Key::P | Key::Q | Key::R | Key::S | Key::T | Key::U | Key::V |
- Key::W | Key::X | Key::Y | Key::Z | Key::LeftBracket | Key::Backslash |
- Key::RightBracket | Key::Kp0 | Key::Kp1 | Key::Kp2 | Key::Kp3 |
- Key::Kp4 | Key::Kp5 | Key::Kp6 | Key::Kp7 | Key::Kp8 | Key::Kp9 |
- Key::KpDecimal | Key::KpDivide | Key::KpMultiply | Key::KpSubtract |
- Key::KpAdd | Key::KpEqual => true,
- _ => false,
- }
-}
-
/// The length in bytes of the first n characters in a UTF-8 string.
///
/// If the string has fewer than n characters, returns the length of the whole string.
@@ -486,80 +468,80 @@ impl<T: ClipboardProvider> TextInput<T> {
/// Process a given `KeyboardEvent` and return an action for the caller to execute.
pub fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
if let Some(key) = event.get_key() {
- self.handle_keydown_aux(key, event.get_key_modifiers())
+ self.handle_keydown_aux(event.printable(), key, event.get_key_modifiers())
} else {
KeyReaction::Nothing
}
}
- pub fn handle_keydown_aux(&mut self, key: Key, mods: KeyModifiers) -> KeyReaction {
+
+ pub fn handle_keydown_aux(&mut self,
+ printable: Option<char>,
+ key: Key,
+ mods: KeyModifiers) -> KeyReaction {
let maybe_select = if mods.contains(SHIFT) { Selection::Selected } else { Selection::NotSelected };
- match key {
- Key::A if is_control_key(mods) => {
+ match (printable, key) {
+ (Some('a'), _) if is_control_key(mods) => {
self.select_all();
KeyReaction::RedrawSelection
},
- Key::C if is_control_key(mods) => {
+ (Some('c'), _) if is_control_key(mods) => {
if let Some(text) = self.get_selection_text() {
self.clipboard_provider.set_clipboard_contents(text);
}
KeyReaction::DispatchInput
},
- Key::V if is_control_key(mods) => {
+ (Some('v'), _) if is_control_key(mods) => {
let contents = self.clipboard_provider.clipboard_contents();
self.insert_string(contents);
KeyReaction::DispatchInput
},
- _ if is_printable_key(key) => {
- self.insert_string(key_value(key, mods));
- KeyReaction::DispatchInput
- }
- Key::Space => {
- self.insert_char(' ');
+ (Some(c), _) => {
+ self.insert_char(c);
KeyReaction::DispatchInput
}
- Key::Delete => {
+ (None, Key::Delete) => {
self.delete_char(Direction::Forward);
KeyReaction::DispatchInput
}
- Key::Backspace => {
+ (None, Key::Backspace) => {
self.delete_char(Direction::Backward);
KeyReaction::DispatchInput
}
- Key::Left => {
+ (None, Key::Left) => {
self.adjust_horizontal_by_one(Direction::Backward, maybe_select);
KeyReaction::RedrawSelection
}
- Key::Right => {
+ (None, Key::Right) => {
self.adjust_horizontal_by_one(Direction::Forward, maybe_select);
KeyReaction::RedrawSelection
}
- Key::Up => {
+ (None, Key::Up) => {
self.adjust_vertical(-1, maybe_select);
KeyReaction::RedrawSelection
}
- Key::Down => {
+ (None, Key::Down) => {
self.adjust_vertical(1, maybe_select);
KeyReaction::RedrawSelection
}
- Key::Enter | Key::KpEnter => self.handle_return(),
- Key::Home => {
+ (None, Key::Enter) | (None, Key::KpEnter) => self.handle_return(),
+ (None, Key::Home) => {
self.edit_point.index = 0;
KeyReaction::RedrawSelection
}
- Key::End => {
+ (None, Key::End) => {
self.edit_point.index = self.current_line_length();
self.assert_ok_selection();
KeyReaction::RedrawSelection
}
- Key::PageUp => {
+ (None, Key::PageUp) => {
self.adjust_vertical(-28, maybe_select);
KeyReaction::RedrawSelection
}
- Key::PageDown => {
+ (None, Key::PageDown) => {
self.adjust_vertical(28, maybe_select);
KeyReaction::RedrawSelection
}
- Key::Tab => KeyReaction::TriggerDefaultAction,
+ (None, Key::Tab) => KeyReaction::TriggerDefaultAction,
_ => KeyReaction::Nothing,
}
}
diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs
index bd82a498d46..5c6064a3d3e 100644
--- a/components/script_traits/lib.rs
+++ b/components/script_traits/lib.rs
@@ -282,7 +282,7 @@ pub enum CompositorEvent {
/// Touchpad pressure event
TouchpadPressureEvent(Point2D<f32>, f32, TouchpadPressurePhase),
/// A key was pressed.
- KeyEvent(Key, KeyState, KeyModifiers),
+ KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
}
/// Touchpad pressure phase for TouchpadPressureEvent.
@@ -586,7 +586,7 @@ pub enum ConstellationMsg {
/// Query the constellation to see if the current compositor output is stable
IsReadyToSaveImage(HashMap<PipelineId, Epoch>),
/// Inform the constellation of a key event.
- KeyEvent(Key, KeyState, KeyModifiers),
+ KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
/// Request to load a page.
LoadUrl(PipelineId, LoadData),
/// Request to navigate a frame.
diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs
index 7fc871eb4cc..0ac5aeae4b4 100644
--- a/components/script_traits/script_msg.rs
+++ b/components/script_traits/script_msg.rs
@@ -103,7 +103,7 @@ pub enum ScriptMsg {
/// https://html.spec.whatwg.org/multipage/#document.title
SetTitle(PipelineId, Option<String>),
/// Send a key event
- SendKeyEvent(Key, KeyState, KeyModifiers),
+ SendKeyEvent(Option<char>, Key, KeyState, KeyModifiers),
/// Get Window Informations size and position
GetClientWindow(IpcSender<(Size2D<u32>, Point2D<i32>)>),
/// Move the window to a point
diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock
index 997fc8ab250..030f19a5038 100644
--- a/components/servo/Cargo.lock
+++ b/components/servo/Cargo.lock
@@ -146,7 +146,7 @@ dependencies = [
[[package]]
name = "bincode"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1061,7 +1061,7 @@ name = "ipc-channel"
version = "0.2.3"
source = "git+https://github.com/servo/ipc-channel#48137d69955f5460da586c552de275ecdc3f4efe"
dependencies = [
- "bincode 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bincode 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock
index 4907cc20629..7071b45ddcf 100644
--- a/ports/cef/Cargo.lock
+++ b/ports/cef/Cargo.lock
@@ -120,7 +120,7 @@ dependencies = [
[[package]]
name = "bincode"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -970,7 +970,7 @@ name = "ipc-channel"
version = "0.2.3"
source = "git+https://github.com/servo/ipc-channel#48137d69955f5460da586c552de275ecdc3f4efe"
dependencies = [
- "bincode 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bincode 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/ports/cef/browser_host.rs b/ports/cef/browser_host.rs
index bbf776ee839..fce7c6c5735 100644
--- a/ports/cef/browser_host.rs
+++ b/ports/cef/browser_host.rs
@@ -17,6 +17,7 @@ use libc::{c_double, c_int};
use msg::constellation_msg::{self, KeyModifiers, KeyState};
use script_traits::{MouseButton, TouchEventType};
use std::cell::{Cell, RefCell};
+use std::char;
pub struct ServoCefBrowserHost {
/// A reference to the browser.
@@ -424,7 +425,8 @@ full_cef_class_impl! {
if (*event).modifiers & EVENTFLAG_ALT_DOWN as u32 != 0 {
key_modifiers = key_modifiers | constellation_msg::ALT;
}
- this.downcast().send_window_event(WindowEvent::KeyEvent(key, key_state, key_modifiers))
+ let ch = char::from_u32((*event).character as u32);
+ this.downcast().send_window_event(WindowEvent::KeyEvent(ch, key, key_state, key_modifiers))
}}
fn send_mouse_click_event(&this,
diff --git a/ports/cef/window.rs b/ports/cef/window.rs
index 1d4ee0a3281..e5c2cd5f4db 100644
--- a/ports/cef/window.rs
+++ b/ports/cef/window.rs
@@ -479,7 +479,7 @@ impl WindowMethods for Window {
}
}
- fn handle_key(&self, _: Key, _: KeyModifiers) {
+ fn handle_key(&self, _: Option<char>, _: Key, _: KeyModifiers) {
// TODO(negge)
}
diff --git a/ports/geckolib/Cargo.lock b/ports/geckolib/Cargo.lock
index 27b841fb790..38213b2f352 100644
--- a/ports/geckolib/Cargo.lock
+++ b/ports/geckolib/Cargo.lock
@@ -72,7 +72,7 @@ dependencies = [
[[package]]
name = "bincode"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -254,7 +254,7 @@ name = "ipc-channel"
version = "0.2.4"
source = "git+https://github.com/servo/ipc-channel#8411eeabf3a712006ad1b47637b2d8fe71177f85"
dependencies = [
- "bincode 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bincode 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/ports/glutin/window.rs b/ports/glutin/window.rs
index a6efae6aab4..00bd7ceff0a 100644
--- a/ports/glutin/window.rs
+++ b/ports/glutin/window.rs
@@ -14,10 +14,10 @@ use euclid::{Size2D, Point2D};
#[cfg(target_os = "windows")] use gdi32;
use gleam::gl;
use glutin;
-use glutin::TouchPhase;
#[cfg(target_os = "macos")]
use glutin::os::macos::{ActivationPolicy, WindowBuilderExt};
use glutin::{Api, ElementState, Event, GlRequest, MouseButton, VirtualKeyCode, MouseScrollDelta};
+use glutin::{ScanCode, TouchPhase};
use layers::geometry::DevicePixel;
use layers::platform::surface::NativeDisplay;
use msg::constellation_msg::{KeyState, NONE, CONTROL, SHIFT, ALT, SUPER};
@@ -99,6 +99,12 @@ pub struct Window {
mouse_pos: Cell<Point2D<i32>>,
key_modifiers: Cell<KeyModifiers>,
current_url: RefCell<Option<Url>>,
+
+ /// The contents of the last ReceivedCharacter event for use in a subsequent KeyEvent.
+ pending_key_event_char: Cell<Option<char>>,
+ /// The list of keys that have been pressed but not yet released, to allow providing
+ /// the equivalent ReceivedCharacter data as was received for the press event.
+ pressed_key_map: RefCell<Vec<(ScanCode, char)>>,
}
#[cfg(not(target_os = "windows"))]
@@ -175,6 +181,9 @@ impl Window {
mouse_pos: Cell::new(Point2D::new(0, 0)),
key_modifiers: Cell::new(KeyModifiers::empty()),
current_url: RefCell::new(None),
+
+ pending_key_event_char: Cell::new(None),
+ pressed_key_map: RefCell::new(vec![]),
};
gl::clear_color(0.6, 0.6, 0.6, 1.0);
@@ -232,7 +241,13 @@ impl Window {
fn handle_window_event(&self, event: glutin::Event) -> bool {
match event {
- Event::KeyboardInput(element_state, _scan_code, Some(virtual_key_code)) => {
+ Event::ReceivedCharacter(ch) => {
+ assert!(self.pending_key_event_char.get().is_none());
+ if !ch.is_control() {
+ self.pending_key_event_char.set(Some(ch));
+ }
+ }
+ Event::KeyboardInput(element_state, scan_code, Some(virtual_key_code)) => {
match virtual_key_code {
VirtualKeyCode::LControl => self.toggle_modifier(LEFT_CONTROL),
VirtualKeyCode::RControl => self.toggle_modifier(RIGHT_CONTROL),
@@ -245,13 +260,39 @@ impl Window {
_ => {}
}
+ let ch = match element_state {
+ ElementState::Pressed => {
+ // Retrieve any previosly stored ReceivedCharacter value.
+ // Store the association between the scan code and the actual
+ // character value, if there is one.
+ let ch = self.pending_key_event_char
+ .get()
+ .and_then(|ch| filter_nonprintable(ch, virtual_key_code));
+ self.pending_key_event_char.set(None);
+ if let Some(ch) = ch {
+ self.pressed_key_map.borrow_mut().push((scan_code, ch));
+ }
+ ch
+ }
+
+ ElementState::Released => {
+ // Retrieve the associated character value for this release key,
+ // if one was previously stored.
+ let idx = self.pressed_key_map
+ .borrow()
+ .iter()
+ .position(|&(code, _)| code == scan_code);
+ idx.map(|idx| self.pressed_key_map.borrow_mut().swap_remove(idx).1)
+ }
+ };
+
if let Ok(key) = Window::glutin_key_to_script_key(virtual_key_code) {
let state = match element_state {
ElementState::Pressed => KeyState::Pressed,
ElementState::Released => KeyState::Released,
};
let modifiers = Window::glutin_mods_to_script_mods(self.key_modifiers.get());
- self.event_queue.borrow_mut().push(WindowEvent::KeyEvent(key, state, modifiers));
+ self.event_queue.borrow_mut().push(WindowEvent::KeyEvent(ch, key, state, modifiers));
}
}
Event::KeyboardInput(_, _, None) => {
@@ -803,48 +844,47 @@ impl WindowMethods for Window {
}
/// Helper function to handle keyboard events.
- fn handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers) {
- match (mods, key) {
- (_, Key::Equal) => {
+ fn handle_key(&self, ch: Option<char>, key: Key, mods: constellation_msg::KeyModifiers) {
+ match (mods, ch, key) {
+ (_, Some('+'), _) => {
if mods & !SHIFT == CMD_OR_CONTROL {
self.event_queue.borrow_mut().push(WindowEvent::Zoom(1.1));
} else if mods & !SHIFT == CMD_OR_CONTROL | ALT {
self.event_queue.borrow_mut().push(WindowEvent::PinchZoom(1.1));
}
}
- (CMD_OR_CONTROL, Key::Minus) => {
+ (CMD_OR_CONTROL, Some('-'), _) => {
self.event_queue.borrow_mut().push(WindowEvent::Zoom(1.0 / 1.1));
}
- (_, Key::Minus) if mods == CMD_OR_CONTROL | ALT => {
+ (_, Some('-'), _) if mods == CMD_OR_CONTROL | ALT => {
self.event_queue.borrow_mut().push(WindowEvent::PinchZoom(1.0 / 1.1));
}
- (CMD_OR_CONTROL, Key::Num0) |
- (CMD_OR_CONTROL, Key::Kp0) => {
+ (CMD_OR_CONTROL, Some('0'), _) => {
self.event_queue.borrow_mut().push(WindowEvent::ResetZoom);
}
- (NONE, Key::NavigateForward) => {
+ (NONE, None, Key::NavigateForward) => {
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Forward));
}
- (NONE, Key::NavigateBackward) => {
+ (NONE, None, Key::NavigateBackward) => {
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Back));
}
- (NONE, Key::Escape) => {
+ (NONE, None, Key::Escape) => {
if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() {
self.event_queue.borrow_mut().push(WindowEvent::Quit);
}
}
- (CMD_OR_ALT, Key::Right) => {
+ (CMD_OR_ALT, None, Key::Right) => {
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Forward));
}
- (CMD_OR_ALT, Key::Left) => {
+ (CMD_OR_ALT, None, Key::Left) => {
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Back));
}
- (NONE, Key::PageDown) |
- (NONE, Key::Space) => {
+ (NONE, None, Key::PageDown) |
+ (NONE, Some(' '), _) => {
self.scroll_window(0.0,
-self.framebuffer_size()
.as_f32()
@@ -852,8 +892,8 @@ impl WindowMethods for Window {
.height + 2.0 * LINE_HEIGHT,
TouchEventType::Move);
}
- (NONE, Key::PageUp) |
- (SHIFT, Key::Space) => {
+ (NONE, None, Key::PageUp) |
+ (SHIFT, Some(' '), _) => {
self.scroll_window(0.0,
self.framebuffer_size()
.as_f32()
@@ -861,19 +901,19 @@ impl WindowMethods for Window {
.height - 2.0 * LINE_HEIGHT,
TouchEventType::Move);
}
- (NONE, Key::Up) => {
+ (NONE, None, Key::Up) => {
self.scroll_window(0.0, 3.0 * LINE_HEIGHT, TouchEventType::Move);
}
- (NONE, Key::Down) => {
+ (NONE, None, Key::Down) => {
self.scroll_window(0.0, -3.0 * LINE_HEIGHT, TouchEventType::Move);
}
- (NONE, Key::Left) => {
+ (NONE, None, Key::Left) => {
self.scroll_window(LINE_HEIGHT, 0.0, TouchEventType::Move);
}
- (NONE, Key::Right) => {
+ (NONE, None, Key::Right) => {
self.scroll_window(-LINE_HEIGHT, 0.0, TouchEventType::Move);
}
- (CMD_OR_CONTROL, Key::R) => {
+ (CMD_OR_CONTROL, Some('r'), _) => {
if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() {
self.event_queue.borrow_mut().push(WindowEvent::Reload);
}
@@ -932,6 +972,77 @@ fn glutin_pressure_stage_to_touchpad_pressure_phase(stage: i64) -> TouchpadPress
}
}
+fn filter_nonprintable(ch: char, key_code: VirtualKeyCode) -> Option<char> {
+ use glutin::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 |
+ LMenu |
+ LShift |
+ LWin |
+ Mail |
+ MediaSelect |
+ MediaStop |
+ Mute |
+ MyComputer |
+ NavigateForward |
+ NavigateBackward |
+ NextTrack |
+ NoConvert |
+ PlayPause |
+ Power |
+ PrevTrack |
+ RAlt |
+ RControl |
+ RMenu |
+ RShift |
+ RWin |
+ Sleep |
+ Stop |
+ VolumeDown |
+ VolumeUp |
+ Wake |
+ WebBack |
+ WebFavorites |
+ WebForward |
+ WebHome |
+ WebRefresh |
+ WebSearch |
+ WebStop => None,
+ _ => Some(ch),
+ }
+}
+
// These functions aren't actually called. They are here as a link
// hack because Skia references them.
diff --git a/tests/unit/script/textinput.rs b/tests/unit/script/textinput.rs
index 4ee2c897bef..d5e9c1268d6 100644
--- a/tests/unit/script/textinput.rs
+++ b/tests/unit/script/textinput.rs
@@ -401,7 +401,7 @@ fn test_clipboard_paste() {
SelectionDirection::None);
assert_eq!(textinput.get_content(), "defg");
assert_eq!(textinput.edit_point.index, 0);
- textinput.handle_keydown_aux(Key::V, MODIFIERS);
+ textinput.handle_keydown_aux(Some('v'), Key::V, MODIFIERS);
assert_eq!(textinput.get_content(), "abcdefg");
}