Sending key events through script task before processing them in the compositor.

Fixes #4163
This commit is contained in:
Nathan E. Egge 2014-12-12 10:19:04 -05:00
parent f451005f4f
commit b0552cb98e
10 changed files with 131 additions and 66 deletions

View file

@ -4,6 +4,7 @@
use compositor_layer::{CompositorData, CompositorLayer, DoesntWantScrollEvents}; use compositor_layer::{CompositorData, CompositorLayer, DoesntWantScrollEvents};
use compositor_layer::{WantsScrollEvents}; use compositor_layer::{WantsScrollEvents};
use compositor_task;
use compositor_task::{ChangePageLoadData, ChangePageTitle, ChangePaintState, ChangeReadyState}; use compositor_task::{ChangePageLoadData, ChangePageTitle, ChangePaintState, ChangeReadyState};
use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver}; use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver};
use compositor_task::{CompositorTask, CreateOrUpdateDescendantLayer, CreateOrUpdateRootLayer}; use compositor_task::{CompositorTask, CreateOrUpdateDescendantLayer, CreateOrUpdateRootLayer};
@ -336,6 +337,10 @@ impl<Window: WindowMethods> IOCompositor<Window> {
} }
} }
(compositor_task::KeyEvent(key, modified), NotShuttingDown) => {
self.window.handle_key(key, modified);
}
// When we are shutting_down, we need to avoid performing operations // When we are shutting_down, we need to avoid performing operations
// such as Paint that may crash because we have begun tearing down // such as Paint that may crash because we have begun tearing down
// the rest of our resources. // the rest of our resources.

View file

@ -20,6 +20,7 @@ use layers::layers::LayerBufferSet;
use servo_msg::compositor_msg::{Epoch, LayerId, LayerMetadata, ReadyState}; use servo_msg::compositor_msg::{Epoch, LayerId, LayerMetadata, ReadyState};
use servo_msg::compositor_msg::{PaintListener, PaintState, ScriptListener, ScrollPolicy}; use servo_msg::compositor_msg::{PaintListener, PaintState, ScriptListener, ScrollPolicy};
use servo_msg::constellation_msg::{ConstellationChan, LoadData, PipelineId}; use servo_msg::constellation_msg::{ConstellationChan, LoadData, PipelineId};
use servo_msg::constellation_msg::{Key, KeyState, KeyModifiers, Pressed};
use servo_util::memory::MemoryProfilerChan; use servo_util::memory::MemoryProfilerChan;
use servo_util::time::TimeProfilerChan; use servo_util::time::TimeProfilerChan;
use std::comm::{channel, Sender, Receiver}; use std::comm::{channel, Sender, Receiver};
@ -85,6 +86,12 @@ impl ScriptListener for Box<CompositorProxy+'static+Send> {
fn set_title(&mut self, pipeline_id: PipelineId, title: Option<String>) { fn set_title(&mut self, pipeline_id: PipelineId, title: Option<String>) {
self.send(ChangePageTitle(pipeline_id, title)) self.send(ChangePageTitle(pipeline_id, title))
} }
fn send_key_event(&mut self, key: Key, state: KeyState, modifiers: KeyModifiers) {
if state == Pressed {
self.send(KeyEvent(key, modifiers));
}
}
} }
/// Information about each layer that the compositor keeps. /// Information about each layer that the compositor keeps.
@ -204,6 +211,8 @@ pub enum Msg {
/// Indicates that the scrolling timeout with the given starting timestamp has happened and a /// Indicates that the scrolling timeout with the given starting timestamp has happened and a
/// composite should happen. (See the `scrolling` module.) /// composite should happen. (See the `scrolling` module.)
ScrollTimeout(u64), ScrollTimeout(u64),
/// Sends an unconsumed key event back to the compositor.
KeyEvent(Key, KeyModifiers),
} }
impl Show for Msg { impl Show for Msg {
@ -226,6 +235,7 @@ impl Show for Msg {
FrameTreeUpdateMsg(..) => write!(f, "FrameTreeUpdateMsg"), FrameTreeUpdateMsg(..) => write!(f, "FrameTreeUpdateMsg"),
LoadComplete => write!(f, "LoadComplete"), LoadComplete => write!(f, "LoadComplete"),
ScrollTimeout(..) => write!(f, "ScrollTimeout"), ScrollTimeout(..) => write!(f, "ScrollTimeout"),
KeyEvent(..) => write!(f, "KeyEvent"),
} }
} }
} }

View file

@ -6,7 +6,7 @@ use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpda
use compositor_task::{Exit, ChangeReadyState, LoadComplete, Paint, ScrollFragmentPoint, SetIds}; use compositor_task::{Exit, ChangeReadyState, LoadComplete, Paint, ScrollFragmentPoint, SetIds};
use compositor_task::{SetLayerOrigin, ShutdownComplete, ChangePaintState, PaintMsgDiscarded}; use compositor_task::{SetLayerOrigin, ShutdownComplete, ChangePaintState, PaintMsgDiscarded};
use compositor_task::{CompositorEventListener, CompositorReceiver, ScrollTimeout, ChangePageTitle}; use compositor_task::{CompositorEventListener, CompositorReceiver, ScrollTimeout, ChangePageTitle};
use compositor_task::{ChangePageLoadData, FrameTreeUpdateMsg}; use compositor_task::{ChangePageLoadData, FrameTreeUpdateMsg, KeyEvent};
use windowing::WindowEvent; use windowing::WindowEvent;
use geom::scale_factor::ScaleFactor; use geom::scale_factor::ScaleFactor;
@ -106,7 +106,7 @@ impl CompositorEventListener for NullCompositor {
SetLayerOrigin(..) | Paint(..) | SetLayerOrigin(..) | Paint(..) |
ChangeReadyState(..) | ChangePaintState(..) | ScrollFragmentPoint(..) | ChangeReadyState(..) | ChangePaintState(..) | ScrollFragmentPoint(..) |
LoadComplete | PaintMsgDiscarded(..) | ScrollTimeout(..) | ChangePageTitle(..) | LoadComplete | PaintMsgDiscarded(..) | ScrollTimeout(..) | ChangePageTitle(..) |
ChangePageLoadData(..) => () ChangePageLoadData(..) | KeyEvent(..) => ()
} }
true true
} }

View file

@ -123,5 +123,7 @@ pub trait WindowMethods {
/// some type of platform-specific graphics context current. Returns true if the composite may /// some type of platform-specific graphics context current. Returns true if the composite may
/// proceed and false if it should not. /// proceed and false if it should not.
fn prepare_for_composite(&self) -> bool; fn prepare_for_composite(&self) -> bool;
}
/// Process a key event.
fn handle_key(&self, key: Key, mods: KeyModifiers);
}

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use azure::azure_hl::Color; use azure::azure_hl::Color;
use constellation_msg::{Key, KeyState, KeyModifiers};
use geom::point::Point2D; use geom::point::Point2D;
use geom::rect::Rect; use geom::rect::Rect;
use layers::platform::surface::NativeGraphicsMetadata; use layers::platform::surface::NativeGraphicsMetadata;
@ -115,4 +116,5 @@ pub trait ScriptListener {
fn set_title(&mut self, pipeline_id: PipelineId, new_title: Option<String>); fn set_title(&mut self, pipeline_id: PipelineId, new_title: Option<String>);
fn close(&mut self); fn close(&mut self);
fn dup(&mut self) -> Box<ScriptListener+'static>; fn dup(&mut self) -> Box<ScriptListener+'static>;
fn send_key_event(&mut self, key: Key, state: KeyState, modifiers: KeyModifiers);
} }

View file

@ -505,11 +505,12 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
} else if "keydown" == event.Type().as_slice() && !event.DefaultPrevented() && } else if "keydown" == event.Type().as_slice() && !event.DefaultPrevented() &&
(self.input_type.get() == InputText || self.input_type.get() == InputPassword) { (self.input_type.get() == InputText || self.input_type.get() == InputPassword) {
let keyevent: Option<JSRef<KeyboardEvent>> = KeyboardEventCast::to_ref(event); let keyevent: Option<JSRef<KeyboardEvent>> = KeyboardEventCast::to_ref(event);
keyevent.map(|event| { keyevent.map(|keyevent| {
match self.textinput.borrow_mut().handle_keydown(event) { match self.textinput.borrow_mut().handle_keydown(keyevent) {
TriggerDefaultAction => (), TriggerDefaultAction => (),
DispatchInput => { DispatchInput => {
self.force_relayout(); self.force_relayout();
event.PreventDefault();
} }
Nothing => (), Nothing => (),
} }

View file

@ -946,6 +946,10 @@ impl ScriptTask {
// TODO: if keypress event is canceled, prevent firing input events // TODO: if keypress event is canceled, prevent firing input events
} }
if !prevented {
self.compositor.borrow_mut().send_key_event(key, state, modifiers);
}
// This behavior is unspecced // This behavior is unspecced
// We are supposed to dispatch synthetic click activation for Space and/or Return, // We are supposed to dispatch synthetic click activation for Space and/or Return,
// however *when* we do it is up to us // however *when* we do it is up to us

View file

@ -21,6 +21,7 @@ use gleam::gl;
use layers::geometry::DevicePixel; use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata; use layers::platform::surface::NativeGraphicsMetadata;
use libc::{c_char, c_void}; use libc::{c_char, c_void};
use servo_msg::constellation_msg::{Key, KeyModifiers};
use servo_msg::compositor_msg::{Blank, FinishedLoading, Loading, PerformingLayout, PaintState}; use servo_msg::compositor_msg::{Blank, FinishedLoading, Loading, PerformingLayout, PaintState};
use servo_msg::compositor_msg::{ReadyState}; use servo_msg::compositor_msg::{ReadyState};
use servo_msg::constellation_msg::LoadData; use servo_msg::constellation_msg::LoadData;
@ -260,6 +261,10 @@ impl WindowMethods for Window {
let frame = frame.downcast(); let frame = frame.downcast();
*frame.url.borrow_mut() = load_data.url.to_string() *frame.url.borrow_mut() = load_data.url.to_string()
} }
fn handle_key(&self, _: Key, _: KeyModifiers) {
// TODO(negge)
}
} }
struct CefCompositorProxy { struct CefCompositorProxy {

View file

@ -26,6 +26,7 @@ use libc::c_int;
use msg::compositor_msg::{Blank, FinishedLoading, IdlePaintState, Loading, PaintState}; use msg::compositor_msg::{Blank, FinishedLoading, IdlePaintState, Loading, PaintState};
use msg::compositor_msg::{PaintingPaintState, PerformingLayout, ReadyState}; use msg::compositor_msg::{PaintingPaintState, PerformingLayout, ReadyState};
use msg::constellation_msg::{mod, LoadData}; use msg::constellation_msg::{mod, LoadData};
use msg::constellation_msg::{Key, KeyModifiers, KeyEscape, KeyEqual, KeyMinus, KeyBackspace, KeyPageUp, KeyPageDown, CONTROL, SHIFT};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::comm::Receiver; use std::comm::Receiver;
use std::rc::Rc; use std::rc::Rc;
@ -206,6 +207,35 @@ impl WindowMethods for Window {
box receiver as Box<CompositorReceiver>) box receiver as Box<CompositorReceiver>)
} }
/// Helper function to handle keyboard events.
fn handle_key(&self, key: Key, mods: KeyModifiers) {
match key {
KeyEscape => self.glfw_window.set_should_close(true),
KeyEqual if mods.contains(CONTROL) => { // Ctrl-+
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1));
}
KeyMinus if mods.contains(CONTROL) => { // Ctrl--
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.0/1.1));
}
KeyBackspace if mods.contains(SHIFT) => { // Shift-Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
}
KeyBackspace => { // Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
}
KeyPageDown => {
let (_, height) = self.glfw_window.get_size();
self.scroll_window(0.0, -height as f32);
}
KeyPageUp => {
let (_, height) = self.glfw_window.get_size();
self.scroll_window(0.0, height as f32);
}
_ => {}
}
}
fn prepare_for_composite(&self) -> bool { fn prepare_for_composite(&self) -> bool {
true true
} }
@ -221,9 +251,6 @@ impl Window {
fn handle_window_event(&self, window: &glfw::Window, event: glfw::WindowEvent) { fn handle_window_event(&self, window: &glfw::Window, event: glfw::WindowEvent) {
match event { match event {
glfw::KeyEvent(key, _, action, mods) => { glfw::KeyEvent(key, _, action, mods) => {
if action == glfw::Press {
self.handle_key(key, mods);
}
let key = glfw_key_to_script_key(key); let key = glfw_key_to_script_key(key);
let state = match action { let state = match action {
glfw::Press => constellation_msg::Pressed, glfw::Press => constellation_msg::Pressed,
@ -333,34 +360,6 @@ impl Window {
} }
} }
/// Helper function to handle keyboard events.
fn handle_key(&self, key: glfw::Key, mods: glfw::Modifiers) {
match key {
glfw::KeyEscape => self.glfw_window.set_should_close(true),
glfw::KeyEqual if mods.contains(glfw::Control) => { // Ctrl-+
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1));
}
glfw::KeyMinus if mods.contains(glfw::Control) => { // Ctrl--
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.0/1.1));
}
glfw::KeyBackspace if mods.contains(glfw::Shift) => { // Shift-Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
}
glfw::KeyBackspace => { // Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
}
glfw::KeyPageDown => {
let (_, height) = self.glfw_window.get_size();
self.scroll_window(0.0, -height as f32);
}
glfw::KeyPageUp => {
let (_, height) = self.glfw_window.get_size();
self.scroll_window(0.0, height as f32);
}
_ => {}
}
}
/// Helper function to handle a click /// Helper function to handle a click
fn handle_mouse(&self, button: glfw::MouseButton, action: glfw::Action, x: c_int, y: c_int) { fn handle_mouse(&self, button: glfw::MouseButton, action: glfw::Action, x: c_int, y: c_int) {
// FIXME(tkuehn): max pixel dist should be based on pixel density // FIXME(tkuehn): max pixel dist should be based on pixel density

View file

@ -5,7 +5,7 @@
//! A windowing implementation using glutin. //! A windowing implementation using glutin.
use compositing::compositor_task::{mod, CompositorProxy, CompositorReceiver}; use compositing::compositor_task::{mod, CompositorProxy, CompositorReceiver};
use compositing::windowing::{WindowEvent, WindowMethods}; use compositing::windowing::{WindowEvent, WindowMethods, KeyEvent};
use compositing::windowing::{IdleWindowEvent, ResizeWindowEvent}; use compositing::windowing::{IdleWindowEvent, ResizeWindowEvent};
use compositing::windowing::{MouseWindowEventClass, MouseWindowMoveEventClass, ScrollWindowEvent}; use compositing::windowing::{MouseWindowEventClass, MouseWindowMoveEventClass, ScrollWindowEvent};
use compositing::windowing::{ZoomWindowEvent, PinchZoomWindowEvent, NavigationWindowEvent}; use compositing::windowing::{ZoomWindowEvent, PinchZoomWindowEvent, NavigationWindowEvent};
@ -17,6 +17,8 @@ use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D; use geom::size::TypedSize2D;
use layers::geometry::DevicePixel; use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata; use layers::platform::surface::NativeGraphicsMetadata;
use msg::constellation_msg;
use msg::constellation_msg::{Key, KeyEscape, KeyEqual, KeyMinus, KeyBackspace, KeyPageUp, KeyPageDown, CONTROL, SHIFT, ALT};
use msg::compositor_msg::{IdlePaintState, PaintState, PaintingPaintState}; use msg::compositor_msg::{IdlePaintState, PaintState, PaintingPaintState};
use msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState}; use msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState};
use msg::constellation_msg::LoadData; use msg::constellation_msg::LoadData;
@ -253,6 +255,33 @@ impl WindowMethods for Window {
display: GetCurrentDisplay(), display: GetCurrentDisplay(),
} }
} }
/// Helper function to handle keyboard events.
fn handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers) {
match key {
// TODO(negge): handle window close event
KeyEscape => {},
KeyEqual if mods.contains(CONTROL) => { // Ctrl-+
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1));
}
KeyMinus if mods.contains(CONTROL) => { // Ctrl--
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.0/1.1));
}
KeyBackspace if mods.contains(SHIFT) => { // Shift-Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
}
KeyBackspace => { // Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
}
KeyPageDown => {
self.scroll_window(0.0, -self.framebuffer_size().as_f32().to_untyped().height);
}
KeyPageUp => {
self.scroll_window(0.0, self.framebuffer_size().as_f32().to_untyped().height);
}
_ => {}
}
}
} }
impl Window { impl Window {
@ -293,6 +322,33 @@ impl Window {
} }
} }
fn glutin_mods_to_script_mods(modifiers: KeyModifiers) -> constellation_msg::KeyModifiers {
let mut result = constellation_msg::KeyModifiers::from_bits(0).unwrap();
if modifiers.intersects(LEFT_SHIFT | RIGHT_SHIFT) {
result.insert(SHIFT);
}
if modifiers.intersects(LEFT_CONTROL | RIGHT_CONTROL) {
result.insert(CONTROL);
}
if modifiers.intersects(LEFT_ALT | RIGHT_ALT) {
result.insert(ALT);
}
result
}
fn glutin_key_to_script_key(key: glutin::VirtualKeyCode) -> Result<constellation_msg::Key, ()> {
// TODO(negge): add more key mappings
match key {
glutin::Escape => Ok(KeyEscape),
glutin::Equals => Ok(KeyEqual),
glutin::Minus => Ok(KeyMinus),
glutin::Back => Ok(KeyBackspace),
glutin::PageDown => Ok(KeyPageDown),
glutin::PageUp => Ok(KeyPageUp),
_ => Err(()),
}
}
impl Window { impl Window {
fn handle_window_event(&self, event: glutin::Event) -> bool { fn handle_window_event(&self, event: glutin::Event) -> bool {
match event { match event {
@ -307,7 +363,16 @@ impl Window {
(_, glutin::RShift) => self.toggle_modifier(RIGHT_SHIFT), (_, glutin::RShift) => self.toggle_modifier(RIGHT_SHIFT),
(_, glutin::LAlt) => self.toggle_modifier(LEFT_ALT), (_, glutin::LAlt) => self.toggle_modifier(LEFT_ALT),
(_, glutin::RAlt) => self.toggle_modifier(RIGHT_ALT), (_, glutin::RAlt) => self.toggle_modifier(RIGHT_ALT),
(glutin::Pressed, key_code) => return self.handle_key(key_code), (glutin::Pressed, key_code) => {
match glutin_key_to_script_key(key_code) {
Ok(key) => {
let state = constellation_msg::Pressed;
let modifiers = glutin_mods_to_script_mods(self.key_modifiers.get());
self.event_queue.borrow_mut().push(KeyEvent(key, state, modifiers));
}
_ => {}
}
}
(_, _) => {} (_, _) => {}
} }
} }
@ -371,34 +436,6 @@ impl Window {
self.event_queue.borrow_mut().push(event); self.event_queue.borrow_mut().push(event);
} }
/// Helper function to handle keyboard events.
fn handle_key(&self, key: glutin::VirtualKeyCode) -> bool {
match key {
glutin::Escape => return true,
glutin::Equals if self.ctrl_pressed() => { // Ctrl-+
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1));
}
glutin::Minus if self.ctrl_pressed() => { // Ctrl--
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.0/1.1));
}
glutin::Back if self.shift_pressed() => { // Shift-Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
}
glutin::Back => { // Backspace
self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
}
glutin::PageDown => {
self.scroll_window(0.0, -self.framebuffer_size().as_f32().to_untyped().height);
}
glutin::PageUp => {
self.scroll_window(0.0, self.framebuffer_size().as_f32().to_untyped().height);
}
_ => {}
}
false
}
/// Helper function to handle a click /// Helper function to handle a click
fn handle_mouse(&self, button: glutin::MouseButton, action: glutin::ElementState, x: int, y: int) { fn handle_mouse(&self, button: glutin::MouseButton, action: glutin::ElementState, x: int, y: int) {
// FIXME(tkuehn): max pixel dist should be based on pixel density // FIXME(tkuehn): max pixel dist should be based on pixel density