Skip to content

Commit

Permalink
Auto merge of #11950 - jdm:keylayout2, r=emilio
Browse files Browse the repository at this point in the history
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 -->
  • Loading branch information
bors-servo committed Jul 6, 2016
2 parents 23f5264 + 6496d73 commit 68fb9eb
Show file tree
Hide file tree
Showing 19 changed files with 233 additions and 122 deletions.
12 changes: 6 additions & 6 deletions components/compositing/compositor.rs
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion components/compositing/compositor_thread.rs
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions components/compositing/windowing.rs
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 8 additions & 8 deletions components/constellation/constellation.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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.
Expand All @@ -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),
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 10 additions & 1 deletion components/script/dom/bindings/str.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion components/script/dom/bindings/trace.rs
Expand Up @@ -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>);
Expand Down
9 changes: 6 additions & 3 deletions components/script/dom/document.rs
Expand Up @@ -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,
Expand All @@ -1070,16 +1071,17 @@ 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,
true,
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,
Expand All @@ -1103,6 +1105,7 @@ impl Document {
true,
Some(&self.window),
0,
ch,
Some(key),
DOMString::from(props.key_string),
DOMString::from(props.code),
Expand All @@ -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
Expand Down
42 changes: 23 additions & 19 deletions components/script/dom/keyboardevent.rs
Expand Up @@ -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);
Expand All @@ -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 {
Expand All @@ -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),
}
}

Expand All @@ -69,6 +72,7 @@ impl KeyboardEvent {
cancelable: bool,
view: Option<&Window>,
_detail: i32,
ch: Option<char>,
key: Option<Key>,
key_string: DOMString,
code: DOMString,
Expand All @@ -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
Expand All @@ -103,28 +108,34 @@ 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,
None, 0);
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),
}
}
}


impl KeyboardEvent {
pub fn printable(&self) -> Option<char> {
self.printable.get()
}

pub fn get_key(&self) -> Option<Key> {
self.key.get().clone()
}
Expand All @@ -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 => "'",
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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>,
Expand Down
4 changes: 2 additions & 2 deletions components/script/script_thread.rs
Expand Up @@ -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);
}
}
}
Expand Down

0 comments on commit 68fb9eb

Please sign in to comment.