зеркало из https://github.com/mozilla/gecko-dev.git
1458 строки
53 KiB
Rust
1458 строки
53 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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/. */
|
|
|
|
//! A windowing implementation using glutin.
|
|
|
|
use NestedEventLoopListener;
|
|
use compositing::compositor_thread::{self, CompositorProxy, CompositorReceiver};
|
|
use compositing::windowing::{MouseWindowEvent, WindowNavigateMsg};
|
|
use compositing::windowing::{WindowEvent, WindowMethods};
|
|
use euclid::{Point2D, Size2D, TypedPoint2D};
|
|
use euclid::rect::TypedRect;
|
|
use euclid::scale_factor::ScaleFactor;
|
|
use euclid::size::TypedSize2D;
|
|
#[cfg(target_os = "windows")]
|
|
use gdi32;
|
|
use gleam::gl;
|
|
use glutin;
|
|
use glutin::{Api, ElementState, Event, GlRequest, MouseButton, MouseScrollDelta, VirtualKeyCode};
|
|
#[cfg(not(target_os = "windows"))]
|
|
use glutin::ScanCode;
|
|
use glutin::TouchPhase;
|
|
#[cfg(target_os = "macos")]
|
|
use glutin::os::macos::{ActivationPolicy, WindowBuilderExt};
|
|
use msg::constellation_msg::{self, Key};
|
|
use msg::constellation_msg::{ALT, CONTROL, KeyState, NONE, SHIFT, SUPER};
|
|
use net_traits::net_error_list::NetError;
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
use osmesa_sys;
|
|
use script_traits::{DevicePixel, LoadData, TouchEventType, TouchpadPressurePhase};
|
|
use servo_config::opts;
|
|
use servo_config::prefs::PREFS;
|
|
use servo_config::resource_files;
|
|
use servo_geometry::DeviceIndependentPixel;
|
|
use servo_url::ServoUrl;
|
|
use std::cell::{Cell, RefCell};
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
use std::ffi::CString;
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
use std::mem;
|
|
use std::os::raw::c_void;
|
|
use std::ptr;
|
|
use std::rc::Rc;
|
|
use std::sync::mpsc::{Sender, channel};
|
|
use style_traits::cursor::Cursor;
|
|
#[cfg(target_os = "windows")]
|
|
use user32;
|
|
use webrender_traits::ScrollLocation;
|
|
#[cfg(target_os = "windows")]
|
|
use winapi;
|
|
|
|
static mut G_NESTED_EVENT_LOOP_LISTENER: Option<*mut (NestedEventLoopListener + 'static)> = None;
|
|
|
|
bitflags! {
|
|
flags KeyModifiers: u8 {
|
|
const LEFT_CONTROL = 1,
|
|
const RIGHT_CONTROL = 2,
|
|
const LEFT_SHIFT = 4,
|
|
const RIGHT_SHIFT = 8,
|
|
const LEFT_ALT = 16,
|
|
const RIGHT_ALT = 32,
|
|
const LEFT_SUPER = 64,
|
|
const RIGHT_SUPER = 128,
|
|
}
|
|
}
|
|
|
|
// Some shortcuts use Cmd on Mac and Control on other systems.
|
|
#[cfg(target_os = "macos")]
|
|
const CMD_OR_CONTROL: constellation_msg::KeyModifiers = SUPER;
|
|
#[cfg(not(target_os = "macos"))]
|
|
const CMD_OR_CONTROL: constellation_msg::KeyModifiers = CONTROL;
|
|
|
|
// Some shortcuts use Cmd on Mac and Alt on other systems.
|
|
#[cfg(target_os = "macos")]
|
|
const CMD_OR_ALT: constellation_msg::KeyModifiers = SUPER;
|
|
#[cfg(not(target_os = "macos"))]
|
|
const CMD_OR_ALT: constellation_msg::KeyModifiers = ALT;
|
|
|
|
// This should vary by zoom level and maybe actual text size (focused or under cursor)
|
|
const LINE_HEIGHT: f32 = 38.0;
|
|
|
|
const MULTISAMPLES: u16 = 16;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn builder_with_platform_options(mut builder: glutin::WindowBuilder) -> glutin::WindowBuilder {
|
|
if opts::get().headless || opts::get().output_file.is_some() {
|
|
// Prevent the window from showing in Dock.app, stealing focus,
|
|
// or appearing at all when running in headless mode or generating an
|
|
// output file.
|
|
builder = builder.with_activation_policy(ActivationPolicy::Prohibited)
|
|
}
|
|
builder.with_app_name(String::from("Servo"))
|
|
}
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
fn builder_with_platform_options(builder: glutin::WindowBuilder) -> glutin::WindowBuilder {
|
|
builder
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
struct HeadlessContext {
|
|
width: u32,
|
|
height: u32,
|
|
_context: osmesa_sys::OSMesaContext,
|
|
_buffer: Vec<u32>,
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
struct HeadlessContext {
|
|
width: u32,
|
|
height: u32,
|
|
}
|
|
|
|
impl HeadlessContext {
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
fn new(width: u32, height: u32) -> HeadlessContext {
|
|
let mut attribs = Vec::new();
|
|
|
|
attribs.push(osmesa_sys::OSMESA_PROFILE);
|
|
attribs.push(osmesa_sys::OSMESA_CORE_PROFILE);
|
|
attribs.push(osmesa_sys::OSMESA_CONTEXT_MAJOR_VERSION);
|
|
attribs.push(3);
|
|
attribs.push(osmesa_sys::OSMESA_CONTEXT_MINOR_VERSION);
|
|
attribs.push(3);
|
|
attribs.push(0);
|
|
|
|
let context = unsafe {
|
|
osmesa_sys::OSMesaCreateContextAttribs(attribs.as_ptr(), ptr::null_mut())
|
|
};
|
|
|
|
assert!(!context.is_null());
|
|
|
|
let mut buffer = vec![0; (width * height) as usize];
|
|
|
|
unsafe {
|
|
let ret = osmesa_sys::OSMesaMakeCurrent(context,
|
|
buffer.as_mut_ptr() as *mut _,
|
|
gl::UNSIGNED_BYTE,
|
|
width as i32,
|
|
height as i32);
|
|
assert!(ret != 0);
|
|
};
|
|
|
|
HeadlessContext {
|
|
width: width,
|
|
height: height,
|
|
_context: context,
|
|
_buffer: buffer,
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
fn new(width: u32, height: u32) -> HeadlessContext {
|
|
HeadlessContext {
|
|
width: width,
|
|
height: height,
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
fn get_proc_address(s: &str) -> *const c_void {
|
|
let c_str = CString::new(s).expect("Unable to create CString");
|
|
unsafe {
|
|
mem::transmute(osmesa_sys::OSMesaGetProcAddress(c_str.as_ptr()))
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
fn get_proc_address(_: &str) -> *const c_void {
|
|
ptr::null() as *const _
|
|
}
|
|
}
|
|
|
|
enum WindowKind {
|
|
Window(glutin::Window),
|
|
Headless(HeadlessContext),
|
|
}
|
|
|
|
/// The type of a window.
|
|
pub struct Window {
|
|
kind: WindowKind,
|
|
|
|
mouse_down_button: Cell<Option<glutin::MouseButton>>,
|
|
mouse_down_point: Cell<Point2D<i32>>,
|
|
event_queue: RefCell<Vec<WindowEvent>>,
|
|
|
|
mouse_pos: Cell<Point2D<i32>>,
|
|
key_modifiers: Cell<KeyModifiers>,
|
|
current_url: RefCell<Option<ServoUrl>>,
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
/// The contents of the last ReceivedCharacter event for use in a subsequent KeyEvent.
|
|
pending_key_event_char: Cell<Option<char>>,
|
|
|
|
#[cfg(target_os = "windows")]
|
|
last_pressed_key: Cell<Option<constellation_msg::Key>>,
|
|
|
|
/// 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.
|
|
#[cfg(not(target_os = "windows"))]
|
|
pressed_key_map: RefCell<Vec<(ScanCode, char)>>,
|
|
|
|
gl: Rc<gl::Gl>,
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn window_creation_scale_factor() -> ScaleFactor<f32, DeviceIndependentPixel, DevicePixel> {
|
|
ScaleFactor::new(1.0)
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn window_creation_scale_factor() -> ScaleFactor<f32, DeviceIndependentPixel, DevicePixel> {
|
|
let hdc = unsafe { user32::GetDC(::std::ptr::null_mut()) };
|
|
let ppi = unsafe { gdi32::GetDeviceCaps(hdc, winapi::wingdi::LOGPIXELSY) };
|
|
ScaleFactor::new(ppi as f32 / 96.0)
|
|
}
|
|
|
|
|
|
impl Window {
|
|
pub fn new(is_foreground: bool,
|
|
window_size: TypedSize2D<u32, DeviceIndependentPixel>,
|
|
parent: Option<glutin::WindowID>) -> Rc<Window> {
|
|
let win_size: TypedSize2D<u32, DevicePixel> =
|
|
(window_size.to_f32() * window_creation_scale_factor())
|
|
.to_uint().cast().expect("Window size should fit in u32");
|
|
let width = win_size.to_untyped().width;
|
|
let height = win_size.to_untyped().height;
|
|
|
|
// If there's no chrome, start off with the window invisible. It will be set to visible in
|
|
// `load_end()`. This avoids an ugly flash of unstyled content (especially important since
|
|
// unstyled content is white and chrome often has a transparent background). See issue
|
|
// #9996.
|
|
let visible = is_foreground && !opts::get().no_native_titlebar;
|
|
|
|
let window_kind = if opts::get().headless {
|
|
WindowKind::Headless(HeadlessContext::new(width, height))
|
|
} else {
|
|
let mut builder =
|
|
glutin::WindowBuilder::new().with_title("Servo".to_string())
|
|
.with_decorations(!opts::get().no_native_titlebar)
|
|
.with_transparency(opts::get().no_native_titlebar)
|
|
.with_dimensions(width, height)
|
|
.with_gl(Window::gl_version())
|
|
.with_visibility(visible)
|
|
.with_parent(parent)
|
|
.with_multitouch();
|
|
|
|
if let Ok(mut icon_path) = resource_files::resources_dir_path() {
|
|
icon_path.push("servo.png");
|
|
builder = builder.with_icon(icon_path);
|
|
}
|
|
|
|
if opts::get().enable_vsync {
|
|
builder = builder.with_vsync();
|
|
}
|
|
|
|
if opts::get().use_msaa {
|
|
builder = builder.with_multisampling(MULTISAMPLES)
|
|
}
|
|
|
|
builder = builder_with_platform_options(builder);
|
|
|
|
let mut glutin_window = builder.build().expect("Failed to create window.");
|
|
|
|
unsafe { glutin_window.make_current().expect("Failed to make context current!") }
|
|
|
|
glutin_window.set_window_resize_callback(Some(Window::nested_window_resize as fn(u32, u32)));
|
|
|
|
WindowKind::Window(glutin_window)
|
|
};
|
|
|
|
let gl = match window_kind {
|
|
WindowKind::Window(ref window) => {
|
|
match gl::GlType::default() {
|
|
gl::GlType::Gl => {
|
|
unsafe {
|
|
gl::GlFns::load_with(|s| window.get_proc_address(s) as *const _)
|
|
}
|
|
}
|
|
gl::GlType::Gles => {
|
|
unsafe {
|
|
gl::GlesFns::load_with(|s| window.get_proc_address(s) as *const _)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
WindowKind::Headless(..) => {
|
|
unsafe {
|
|
gl::GlFns::load_with(|s| HeadlessContext::get_proc_address(s))
|
|
}
|
|
}
|
|
};
|
|
|
|
if opts::get().headless {
|
|
// Print some information about the headless renderer that
|
|
// can be useful in diagnosing CI failures on build machines.
|
|
println!("{}", gl.get_string(gl::VENDOR));
|
|
println!("{}", gl.get_string(gl::RENDERER));
|
|
println!("{}", gl.get_string(gl::VERSION));
|
|
}
|
|
|
|
gl.clear_color(0.6, 0.6, 0.6, 1.0);
|
|
gl.clear(gl::COLOR_BUFFER_BIT);
|
|
gl.finish();
|
|
|
|
let window = Window {
|
|
kind: window_kind,
|
|
event_queue: RefCell::new(vec!()),
|
|
mouse_down_button: Cell::new(None),
|
|
mouse_down_point: Cell::new(Point2D::new(0, 0)),
|
|
|
|
mouse_pos: Cell::new(Point2D::new(0, 0)),
|
|
key_modifiers: Cell::new(KeyModifiers::empty()),
|
|
current_url: RefCell::new(None),
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pending_key_event_char: Cell::new(None),
|
|
#[cfg(not(target_os = "windows"))]
|
|
pressed_key_map: RefCell::new(vec![]),
|
|
#[cfg(target_os = "windows")]
|
|
last_pressed_key: Cell::new(None),
|
|
gl: gl.clone(),
|
|
};
|
|
|
|
window.present();
|
|
|
|
Rc::new(window)
|
|
}
|
|
|
|
pub fn platform_window(&self) -> glutin::WindowID {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
unsafe { glutin::WindowID::new(window.platform_window()) }
|
|
}
|
|
WindowKind::Headless(..) => {
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn nested_window_resize(width: u32, height: u32) {
|
|
unsafe {
|
|
if let Some(listener) = G_NESTED_EVENT_LOOP_LISTENER {
|
|
(*listener).handle_event_from_nested_event_loop(
|
|
WindowEvent::Resize(TypedSize2D::new(width, height)));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
|
|
fn gl_version() -> GlRequest {
|
|
return GlRequest::Specific(Api::OpenGl, (3, 2));
|
|
}
|
|
|
|
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
|
fn gl_version() -> GlRequest {
|
|
GlRequest::Specific(Api::OpenGlEs, (3, 0))
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn handle_received_character(&self, ch: char) {
|
|
if !ch.is_control() {
|
|
self.pending_key_event_char.set(Some(ch));
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn handle_received_character(&self, ch: char) {
|
|
let modifiers = Window::glutin_mods_to_script_mods(self.key_modifiers.get());
|
|
if let Some(last_pressed_key) = self.last_pressed_key.get() {
|
|
let event = WindowEvent::KeyEvent(Some(ch), last_pressed_key, KeyState::Pressed, modifiers);
|
|
self.event_queue.borrow_mut().push(event);
|
|
} else {
|
|
// Only send the character if we can print it (by ignoring characters like backspace)
|
|
if !ch.is_control() {
|
|
match Window::char_to_script_key(ch) {
|
|
Some(key) => {
|
|
let event = WindowEvent::KeyEvent(Some(ch),
|
|
key,
|
|
KeyState::Pressed,
|
|
modifiers);
|
|
self.event_queue.borrow_mut().push(event);
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
}
|
|
self.last_pressed_key.set(None);
|
|
}
|
|
|
|
fn toggle_keyboard_modifiers(&self, virtual_key_code: VirtualKeyCode) {
|
|
match virtual_key_code {
|
|
VirtualKeyCode::LControl => self.toggle_modifier(LEFT_CONTROL),
|
|
VirtualKeyCode::RControl => self.toggle_modifier(RIGHT_CONTROL),
|
|
VirtualKeyCode::LShift => self.toggle_modifier(LEFT_SHIFT),
|
|
VirtualKeyCode::RShift => self.toggle_modifier(RIGHT_SHIFT),
|
|
VirtualKeyCode::LAlt => self.toggle_modifier(LEFT_ALT),
|
|
VirtualKeyCode::RAlt => self.toggle_modifier(RIGHT_ALT),
|
|
VirtualKeyCode::LWin => self.toggle_modifier(LEFT_SUPER),
|
|
VirtualKeyCode::RWin => self.toggle_modifier(RIGHT_SUPER),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn handle_keyboard_input(&self, element_state: ElementState, _scan_code: u8, virtual_key_code: VirtualKeyCode) {
|
|
self.toggle_keyboard_modifiers(virtual_key_code);
|
|
|
|
let ch = match element_state {
|
|
ElementState::Pressed => {
|
|
// Retrieve any previously 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(ch, key, state, modifiers));
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn handle_keyboard_input(&self, element_state: ElementState, _scan_code: u8, virtual_key_code: VirtualKeyCode) {
|
|
self.toggle_keyboard_modifiers(virtual_key_code);
|
|
|
|
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,
|
|
};
|
|
if element_state == ElementState::Pressed {
|
|
if is_printable(virtual_key_code) {
|
|
self.last_pressed_key.set(Some(key));
|
|
}
|
|
}
|
|
let modifiers = Window::glutin_mods_to_script_mods(self.key_modifiers.get());
|
|
self.event_queue.borrow_mut().push(WindowEvent::KeyEvent(None, key, state, modifiers));
|
|
}
|
|
}
|
|
|
|
fn handle_window_event(&self, event: glutin::Event) -> bool {
|
|
match event {
|
|
Event::ReceivedCharacter(ch) => {
|
|
self.handle_received_character(ch)
|
|
}
|
|
Event::KeyboardInput(element_state, _scan_code, Some(virtual_key_code)) => {
|
|
self.handle_keyboard_input(element_state, _scan_code, virtual_key_code);
|
|
}
|
|
Event::KeyboardInput(_, _, None) => {
|
|
debug!("Keyboard input without virtual key.");
|
|
}
|
|
Event::Resized(width, height) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Resize(TypedSize2D::new(width, height)));
|
|
}
|
|
Event::MouseInput(element_state, mouse_button, pos) => {
|
|
if mouse_button == MouseButton::Left ||
|
|
mouse_button == MouseButton::Right {
|
|
match pos {
|
|
Some((x, y)) => {
|
|
self.mouse_pos.set(Point2D::new(x, y));
|
|
self.event_queue.borrow_mut().push(
|
|
WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new(x as f32, y as f32)));
|
|
self.handle_mouse(mouse_button, element_state, x, y);
|
|
}
|
|
None => {
|
|
let mouse_pos = self.mouse_pos.get();
|
|
self.handle_mouse(mouse_button, element_state, mouse_pos.x, mouse_pos.y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Event::MouseMoved(x, y) => {
|
|
self.mouse_pos.set(Point2D::new(x, y));
|
|
self.event_queue.borrow_mut().push(
|
|
WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new(x as f32, y as f32)));
|
|
}
|
|
Event::MouseWheel(delta, phase, pos) => {
|
|
let (dx, dy) = match delta {
|
|
MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
|
|
MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
|
|
};
|
|
let scroll_location = ScrollLocation::Delta(TypedPoint2D::new(dx, dy));
|
|
if let Some((x, y)) = pos {
|
|
self.mouse_pos.set(Point2D::new(x, y));
|
|
self.event_queue.borrow_mut().push(
|
|
WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new(x as f32, y as f32)));
|
|
};
|
|
let phase = glutin_phase_to_touch_event_type(phase);
|
|
self.scroll_window(scroll_location, phase);
|
|
},
|
|
Event::Touch(touch) => {
|
|
use script_traits::TouchId;
|
|
|
|
let phase = glutin_phase_to_touch_event_type(touch.phase);
|
|
let id = TouchId(touch.id as i32);
|
|
let point = TypedPoint2D::new(touch.location.0 as f32, touch.location.1 as f32);
|
|
self.event_queue.borrow_mut().push(WindowEvent::Touch(phase, id, point));
|
|
}
|
|
Event::TouchpadPressure(pressure, stage) => {
|
|
let m = self.mouse_pos.get();
|
|
let point = TypedPoint2D::new(m.x as f32, m.y as f32);
|
|
let phase = glutin_pressure_stage_to_touchpad_pressure_phase(stage);
|
|
self.event_queue.borrow_mut().push(WindowEvent::TouchpadPressure(point, pressure, phase));
|
|
}
|
|
Event::Refresh => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Refresh);
|
|
}
|
|
Event::Closed => {
|
|
return true
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn toggle_modifier(&self, modifier: KeyModifiers) {
|
|
let mut modifiers = self.key_modifiers.get();
|
|
modifiers.toggle(modifier);
|
|
self.key_modifiers.set(modifiers);
|
|
}
|
|
|
|
/// Helper function to send a scroll event.
|
|
fn scroll_window(&self, mut scroll_location: ScrollLocation, phase: TouchEventType) {
|
|
// Scroll events snap to the major axis of movement, with vertical
|
|
// preferred over horizontal.
|
|
if let ScrollLocation::Delta(ref mut delta) = scroll_location {
|
|
if delta.y.abs() >= delta.x.abs() {
|
|
delta.x = 0.0;
|
|
} else {
|
|
delta.y = 0.0;
|
|
}
|
|
}
|
|
|
|
let mouse_pos = self.mouse_pos.get();
|
|
let event = WindowEvent::Scroll(scroll_location,
|
|
TypedPoint2D::new(mouse_pos.x as i32, mouse_pos.y as i32),
|
|
phase);
|
|
self.event_queue.borrow_mut().push(event);
|
|
}
|
|
|
|
/// Helper function to handle a click
|
|
fn handle_mouse(&self, button: glutin::MouseButton, action: glutin::ElementState, x: i32, y: i32) {
|
|
use script_traits::MouseButton;
|
|
|
|
// FIXME(tkuehn): max pixel dist should be based on pixel density
|
|
let max_pixel_dist = 10f64;
|
|
let event = match action {
|
|
ElementState::Pressed => {
|
|
self.mouse_down_point.set(Point2D::new(x, y));
|
|
self.mouse_down_button.set(Some(button));
|
|
MouseWindowEvent::MouseDown(MouseButton::Left, TypedPoint2D::new(x as f32, y as f32))
|
|
}
|
|
ElementState::Released => {
|
|
let mouse_up_event = MouseWindowEvent::MouseUp(MouseButton::Left,
|
|
TypedPoint2D::new(x as f32, y as f32));
|
|
match self.mouse_down_button.get() {
|
|
None => mouse_up_event,
|
|
Some(but) if button == but => {
|
|
let pixel_dist = self.mouse_down_point.get() - Point2D::new(x, y);
|
|
let pixel_dist = ((pixel_dist.x * pixel_dist.x +
|
|
pixel_dist.y * pixel_dist.y) as f64).sqrt();
|
|
if pixel_dist < max_pixel_dist {
|
|
self.event_queue.borrow_mut().push(WindowEvent::MouseWindowEventClass(mouse_up_event));
|
|
MouseWindowEvent::Click(MouseButton::Left, TypedPoint2D::new(x as f32, y as f32))
|
|
} else {
|
|
mouse_up_event
|
|
}
|
|
},
|
|
Some(_) => mouse_up_event,
|
|
}
|
|
}
|
|
};
|
|
self.event_queue.borrow_mut().push(WindowEvent::MouseWindowEventClass(event));
|
|
}
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
|
fn handle_next_event(&self) -> bool {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
let event = match window.wait_events().next() {
|
|
None => {
|
|
warn!("Window event stream closed.");
|
|
return true;
|
|
},
|
|
Some(event) => event,
|
|
};
|
|
let mut close = self.handle_window_event(event);
|
|
if !close {
|
|
while let Some(event) = window.poll_events().next() {
|
|
if self.handle_window_event(event) {
|
|
close = true;
|
|
break
|
|
}
|
|
}
|
|
}
|
|
close
|
|
}
|
|
WindowKind::Headless(..) => {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
fn handle_next_event(&self) -> bool {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
let event = match window.wait_events().next() {
|
|
None => {
|
|
warn!("Window event stream closed.");
|
|
return true;
|
|
},
|
|
Some(event) => event,
|
|
};
|
|
let mut close = self.handle_window_event(event);
|
|
if !close {
|
|
while let Some(event) = window.poll_events().next() {
|
|
if self.handle_window_event(event) {
|
|
close = true;
|
|
break
|
|
}
|
|
}
|
|
}
|
|
close
|
|
}
|
|
WindowKind::Headless(..) => {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn wait_events(&self) -> Vec<WindowEvent> {
|
|
use std::mem;
|
|
|
|
let mut events = mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new());
|
|
let mut close_event = false;
|
|
|
|
// When writing to a file then exiting, use event
|
|
// polling so that we don't block on a GUI event
|
|
// such as mouse click.
|
|
if opts::get().output_file.is_some() || opts::get().exit_after_load || opts::get().headless {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
while let Some(event) = window.poll_events().next() {
|
|
close_event = self.handle_window_event(event) || close_event;
|
|
}
|
|
}
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
} else {
|
|
close_event = self.handle_next_event();
|
|
}
|
|
|
|
if close_event {
|
|
events.push(WindowEvent::Quit)
|
|
}
|
|
|
|
events.extend(mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new()).into_iter());
|
|
events
|
|
}
|
|
|
|
pub unsafe fn set_nested_event_loop_listener(
|
|
&self,
|
|
listener: *mut (NestedEventLoopListener + 'static)) {
|
|
G_NESTED_EVENT_LOOP_LISTENER = Some(listener)
|
|
}
|
|
|
|
pub unsafe fn remove_nested_event_loop_listener(&self) {
|
|
G_NESTED_EVENT_LOOP_LISTENER = None
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
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
|
|
}
|
|
}
|
|
|
|
fn glutin_key_to_script_key(key: glutin::VirtualKeyCode) -> Result<constellation_msg::Key, ()> {
|
|
// TODO(negge): add more key mappings
|
|
match key {
|
|
VirtualKeyCode::A => Ok(Key::A),
|
|
VirtualKeyCode::B => Ok(Key::B),
|
|
VirtualKeyCode::C => Ok(Key::C),
|
|
VirtualKeyCode::D => Ok(Key::D),
|
|
VirtualKeyCode::E => Ok(Key::E),
|
|
VirtualKeyCode::F => Ok(Key::F),
|
|
VirtualKeyCode::G => Ok(Key::G),
|
|
VirtualKeyCode::H => Ok(Key::H),
|
|
VirtualKeyCode::I => Ok(Key::I),
|
|
VirtualKeyCode::J => Ok(Key::J),
|
|
VirtualKeyCode::K => Ok(Key::K),
|
|
VirtualKeyCode::L => Ok(Key::L),
|
|
VirtualKeyCode::M => Ok(Key::M),
|
|
VirtualKeyCode::N => Ok(Key::N),
|
|
VirtualKeyCode::O => Ok(Key::O),
|
|
VirtualKeyCode::P => Ok(Key::P),
|
|
VirtualKeyCode::Q => Ok(Key::Q),
|
|
VirtualKeyCode::R => Ok(Key::R),
|
|
VirtualKeyCode::S => Ok(Key::S),
|
|
VirtualKeyCode::T => Ok(Key::T),
|
|
VirtualKeyCode::U => Ok(Key::U),
|
|
VirtualKeyCode::V => Ok(Key::V),
|
|
VirtualKeyCode::W => Ok(Key::W),
|
|
VirtualKeyCode::X => Ok(Key::X),
|
|
VirtualKeyCode::Y => Ok(Key::Y),
|
|
VirtualKeyCode::Z => Ok(Key::Z),
|
|
|
|
VirtualKeyCode::Numpad0 => Ok(Key::Kp0),
|
|
VirtualKeyCode::Numpad1 => Ok(Key::Kp1),
|
|
VirtualKeyCode::Numpad2 => Ok(Key::Kp2),
|
|
VirtualKeyCode::Numpad3 => Ok(Key::Kp3),
|
|
VirtualKeyCode::Numpad4 => Ok(Key::Kp4),
|
|
VirtualKeyCode::Numpad5 => Ok(Key::Kp5),
|
|
VirtualKeyCode::Numpad6 => Ok(Key::Kp6),
|
|
VirtualKeyCode::Numpad7 => Ok(Key::Kp7),
|
|
VirtualKeyCode::Numpad8 => Ok(Key::Kp8),
|
|
VirtualKeyCode::Numpad9 => Ok(Key::Kp9),
|
|
|
|
VirtualKeyCode::Key0 => Ok(Key::Num0),
|
|
VirtualKeyCode::Key1 => Ok(Key::Num1),
|
|
VirtualKeyCode::Key2 => Ok(Key::Num2),
|
|
VirtualKeyCode::Key3 => Ok(Key::Num3),
|
|
VirtualKeyCode::Key4 => Ok(Key::Num4),
|
|
VirtualKeyCode::Key5 => Ok(Key::Num5),
|
|
VirtualKeyCode::Key6 => Ok(Key::Num6),
|
|
VirtualKeyCode::Key7 => Ok(Key::Num7),
|
|
VirtualKeyCode::Key8 => Ok(Key::Num8),
|
|
VirtualKeyCode::Key9 => Ok(Key::Num9),
|
|
|
|
VirtualKeyCode::Return => Ok(Key::Enter),
|
|
VirtualKeyCode::Space => Ok(Key::Space),
|
|
VirtualKeyCode::Escape => Ok(Key::Escape),
|
|
VirtualKeyCode::Equals => Ok(Key::Equal),
|
|
VirtualKeyCode::Minus => Ok(Key::Minus),
|
|
VirtualKeyCode::Back => Ok(Key::Backspace),
|
|
VirtualKeyCode::PageDown => Ok(Key::PageDown),
|
|
VirtualKeyCode::PageUp => Ok(Key::PageUp),
|
|
|
|
VirtualKeyCode::Insert => Ok(Key::Insert),
|
|
VirtualKeyCode::Home => Ok(Key::Home),
|
|
VirtualKeyCode::Delete => Ok(Key::Delete),
|
|
VirtualKeyCode::End => Ok(Key::End),
|
|
|
|
VirtualKeyCode::Left => Ok(Key::Left),
|
|
VirtualKeyCode::Up => Ok(Key::Up),
|
|
VirtualKeyCode::Right => Ok(Key::Right),
|
|
VirtualKeyCode::Down => Ok(Key::Down),
|
|
|
|
VirtualKeyCode::LShift => Ok(Key::LeftShift),
|
|
VirtualKeyCode::LControl => Ok(Key::LeftControl),
|
|
VirtualKeyCode::LAlt => Ok(Key::LeftAlt),
|
|
VirtualKeyCode::LWin => Ok(Key::LeftSuper),
|
|
VirtualKeyCode::RShift => Ok(Key::RightShift),
|
|
VirtualKeyCode::RControl => Ok(Key::RightControl),
|
|
VirtualKeyCode::RAlt => Ok(Key::RightAlt),
|
|
VirtualKeyCode::RWin => Ok(Key::RightSuper),
|
|
|
|
VirtualKeyCode::Apostrophe => Ok(Key::Apostrophe),
|
|
VirtualKeyCode::Backslash => Ok(Key::Backslash),
|
|
VirtualKeyCode::Comma => Ok(Key::Comma),
|
|
VirtualKeyCode::Grave => Ok(Key::GraveAccent),
|
|
VirtualKeyCode::LBracket => Ok(Key::LeftBracket),
|
|
VirtualKeyCode::Period => Ok(Key::Period),
|
|
VirtualKeyCode::RBracket => Ok(Key::RightBracket),
|
|
VirtualKeyCode::Semicolon => Ok(Key::Semicolon),
|
|
VirtualKeyCode::Slash => Ok(Key::Slash),
|
|
VirtualKeyCode::Tab => Ok(Key::Tab),
|
|
VirtualKeyCode::Subtract => Ok(Key::Minus),
|
|
|
|
VirtualKeyCode::F1 => Ok(Key::F1),
|
|
VirtualKeyCode::F2 => Ok(Key::F2),
|
|
VirtualKeyCode::F3 => Ok(Key::F3),
|
|
VirtualKeyCode::F4 => Ok(Key::F4),
|
|
VirtualKeyCode::F5 => Ok(Key::F5),
|
|
VirtualKeyCode::F6 => Ok(Key::F6),
|
|
VirtualKeyCode::F7 => Ok(Key::F7),
|
|
VirtualKeyCode::F8 => Ok(Key::F8),
|
|
VirtualKeyCode::F9 => Ok(Key::F9),
|
|
VirtualKeyCode::F10 => Ok(Key::F10),
|
|
VirtualKeyCode::F11 => Ok(Key::F11),
|
|
VirtualKeyCode::F12 => Ok(Key::F12),
|
|
|
|
VirtualKeyCode::NavigateBackward => Ok(Key::NavigateBackward),
|
|
VirtualKeyCode::NavigateForward => Ok(Key::NavigateForward),
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
|
|
fn glutin_mods_to_script_mods(modifiers: KeyModifiers) -> constellation_msg::KeyModifiers {
|
|
let mut result = constellation_msg::KeyModifiers::empty();
|
|
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);
|
|
}
|
|
if modifiers.intersects(LEFT_SUPER | RIGHT_SUPER) {
|
|
result.insert(SUPER);
|
|
}
|
|
result
|
|
}
|
|
|
|
#[cfg(not(target_os = "win"))]
|
|
fn platform_handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers) {
|
|
match (mods, key) {
|
|
(CMD_OR_CONTROL, Key::LeftBracket) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Back));
|
|
}
|
|
(CMD_OR_CONTROL, Key::RightBracket) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Forward));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "win")]
|
|
fn platform_handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers) {
|
|
}
|
|
}
|
|
|
|
fn create_window_proxy(window: &Window) -> Option<glutin::WindowProxy> {
|
|
match window.kind {
|
|
WindowKind::Window(ref window) => {
|
|
Some(window.create_window_proxy())
|
|
}
|
|
WindowKind::Headless(..) => {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WindowMethods for Window {
|
|
fn gl(&self) -> Rc<gl::Gl> {
|
|
self.gl.clone()
|
|
}
|
|
|
|
fn framebuffer_size(&self) -> TypedSize2D<u32, DevicePixel> {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
let scale_factor = window.hidpi_factor() as u32;
|
|
// TODO(ajeffrey): can this fail?
|
|
let (width, height) = window.get_inner_size().expect("Failed to get window inner size.");
|
|
TypedSize2D::new(width * scale_factor, height * scale_factor)
|
|
}
|
|
WindowKind::Headless(ref context) => {
|
|
TypedSize2D::new(context.width, context.height)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn window_rect(&self) -> TypedRect<u32, DevicePixel> {
|
|
let size = self.framebuffer_size();
|
|
let origin = TypedPoint2D::zero();
|
|
TypedRect::new(origin, size)
|
|
}
|
|
|
|
fn size(&self) -> TypedSize2D<f32, DeviceIndependentPixel> {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
// TODO(ajeffrey): can this fail?
|
|
let (width, height) = window.get_inner_size().expect("Failed to get window inner size.");
|
|
TypedSize2D::new(width as f32, height as f32)
|
|
}
|
|
WindowKind::Headless(ref context) => {
|
|
TypedSize2D::new(context.width as f32, context.height as f32)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn client_window(&self) -> (Size2D<u32>, Point2D<i32>) {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
// TODO(ajeffrey): can this fail?
|
|
let (width, height) = window.get_outer_size().expect("Failed to get window outer size.");
|
|
let size = Size2D::new(width, height);
|
|
// TODO(ajeffrey): can this fail?
|
|
let (x, y) = window.get_position().expect("Failed to get window position.");
|
|
let origin = Point2D::new(x as i32, y as i32);
|
|
(size, origin)
|
|
}
|
|
WindowKind::Headless(ref context) => {
|
|
let size = TypedSize2D::new(context.width, context.height);
|
|
(size, Point2D::zero())
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fn set_inner_size(&self, size: Size2D<u32>) {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
window.set_inner_size(size.width as u32, size.height as u32)
|
|
}
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
}
|
|
|
|
fn set_position(&self, point: Point2D<i32>) {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
window.set_position(point.x, point.y)
|
|
}
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
}
|
|
|
|
fn set_fullscreen_state(&self, _state: bool) {
|
|
match self.kind {
|
|
WindowKind::Window(..) => {
|
|
warn!("Fullscreen is not implemented!")
|
|
},
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
}
|
|
|
|
fn present(&self) {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
if let Err(err) = window.swap_buffers() {
|
|
warn!("Failed to swap window buffers ({}).", err);
|
|
}
|
|
}
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
}
|
|
|
|
fn create_compositor_channel(&self)
|
|
-> (Box<CompositorProxy + Send>, Box<CompositorReceiver>) {
|
|
let (sender, receiver) = channel();
|
|
|
|
let window_proxy = create_window_proxy(self);
|
|
|
|
(box GlutinCompositorProxy {
|
|
sender: sender,
|
|
window_proxy: window_proxy,
|
|
} as Box<CompositorProxy + Send>,
|
|
box receiver as Box<CompositorReceiver>)
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn hidpi_factor(&self) -> ScaleFactor<f32, DeviceIndependentPixel, DevicePixel> {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
ScaleFactor::new(window.hidpi_factor())
|
|
}
|
|
WindowKind::Headless(..) => {
|
|
ScaleFactor::new(1.0)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn hidpi_factor(&self) -> ScaleFactor<f32, DeviceIndependentPixel, DevicePixel> {
|
|
let hdc = unsafe { user32::GetDC(::std::ptr::null_mut()) };
|
|
let ppi = unsafe { gdi32::GetDeviceCaps(hdc, winapi::wingdi::LOGPIXELSY) };
|
|
ScaleFactor::new(ppi as f32 / 96.0)
|
|
}
|
|
|
|
fn set_page_title(&self, title: Option<String>) {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
let fallback_title: String = if let Some(ref current_url) = *self.current_url.borrow() {
|
|
current_url.to_string()
|
|
} else {
|
|
String::from("Untitled")
|
|
};
|
|
|
|
let title = match title {
|
|
Some(ref title) if title.len() > 0 => &**title,
|
|
_ => &fallback_title,
|
|
};
|
|
let title = format!("{} - Servo", title);
|
|
window.set_title(&title);
|
|
}
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
}
|
|
|
|
fn status(&self, _: Option<String>) {
|
|
}
|
|
|
|
fn load_start(&self) {
|
|
}
|
|
|
|
fn load_end(&self) {
|
|
if opts::get().no_native_titlebar {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
window.show();
|
|
}
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn history_changed(&self, history: Vec<LoadData>, current: usize) {
|
|
*self.current_url.borrow_mut() = Some(history[current].url.clone());
|
|
}
|
|
|
|
fn load_error(&self, _: NetError, _: String) {
|
|
}
|
|
|
|
fn head_parsed(&self) {
|
|
}
|
|
|
|
/// Has no effect on Android.
|
|
fn set_cursor(&self, c: Cursor) {
|
|
match self.kind {
|
|
WindowKind::Window(ref window) => {
|
|
use glutin::MouseCursor;
|
|
|
|
let glutin_cursor = match c {
|
|
Cursor::None => MouseCursor::NoneCursor,
|
|
Cursor::Default => MouseCursor::Default,
|
|
Cursor::Pointer => MouseCursor::Hand,
|
|
Cursor::ContextMenu => MouseCursor::ContextMenu,
|
|
Cursor::Help => MouseCursor::Help,
|
|
Cursor::Progress => MouseCursor::Progress,
|
|
Cursor::Wait => MouseCursor::Wait,
|
|
Cursor::Cell => MouseCursor::Cell,
|
|
Cursor::Crosshair => MouseCursor::Crosshair,
|
|
Cursor::Text => MouseCursor::Text,
|
|
Cursor::VerticalText => MouseCursor::VerticalText,
|
|
Cursor::Alias => MouseCursor::Alias,
|
|
Cursor::Copy => MouseCursor::Copy,
|
|
Cursor::Move => MouseCursor::Move,
|
|
Cursor::NoDrop => MouseCursor::NoDrop,
|
|
Cursor::NotAllowed => MouseCursor::NotAllowed,
|
|
Cursor::Grab => MouseCursor::Grab,
|
|
Cursor::Grabbing => MouseCursor::Grabbing,
|
|
Cursor::EResize => MouseCursor::EResize,
|
|
Cursor::NResize => MouseCursor::NResize,
|
|
Cursor::NeResize => MouseCursor::NeResize,
|
|
Cursor::NwResize => MouseCursor::NwResize,
|
|
Cursor::SResize => MouseCursor::SResize,
|
|
Cursor::SeResize => MouseCursor::SeResize,
|
|
Cursor::SwResize => MouseCursor::SwResize,
|
|
Cursor::WResize => MouseCursor::WResize,
|
|
Cursor::EwResize => MouseCursor::EwResize,
|
|
Cursor::NsResize => MouseCursor::NsResize,
|
|
Cursor::NeswResize => MouseCursor::NeswResize,
|
|
Cursor::NwseResize => MouseCursor::NwseResize,
|
|
Cursor::ColResize => MouseCursor::ColResize,
|
|
Cursor::RowResize => MouseCursor::RowResize,
|
|
Cursor::AllScroll => MouseCursor::AllScroll,
|
|
Cursor::ZoomIn => MouseCursor::ZoomIn,
|
|
Cursor::ZoomOut => MouseCursor::ZoomOut,
|
|
};
|
|
window.set_cursor(glutin_cursor);
|
|
}
|
|
WindowKind::Headless(..) => {}
|
|
}
|
|
}
|
|
|
|
fn set_favicon(&self, _: ServoUrl) {
|
|
}
|
|
|
|
fn prepare_for_composite(&self, _width: usize, _height: usize) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Helper function to handle keyboard events.
|
|
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, Some('-'), _) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Zoom(1.0 / 1.1));
|
|
}
|
|
(_, Some('-'), _) if mods == CMD_OR_CONTROL | ALT => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::PinchZoom(1.0 / 1.1));
|
|
}
|
|
(CMD_OR_CONTROL, Some('0'), _) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::ResetZoom);
|
|
}
|
|
|
|
(NONE, None, Key::NavigateForward) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Forward));
|
|
}
|
|
(NONE, None, Key::NavigateBackward) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Back));
|
|
}
|
|
|
|
(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, None, Key::Right) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Forward));
|
|
}
|
|
(CMD_OR_ALT, None, Key::Left) => {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Navigation(WindowNavigateMsg::Back));
|
|
}
|
|
|
|
(NONE, None, Key::PageDown) => {
|
|
let scroll_location = ScrollLocation::Delta(TypedPoint2D::new(0.0,
|
|
-self.framebuffer_size()
|
|
.to_f32()
|
|
.to_untyped()
|
|
.height + 2.0 * LINE_HEIGHT));
|
|
self.scroll_window(scroll_location,
|
|
TouchEventType::Move);
|
|
}
|
|
(NONE, None, Key::PageUp) => {
|
|
let scroll_location = ScrollLocation::Delta(TypedPoint2D::new(0.0,
|
|
self.framebuffer_size()
|
|
.to_f32()
|
|
.to_untyped()
|
|
.height - 2.0 * LINE_HEIGHT));
|
|
self.scroll_window(scroll_location,
|
|
TouchEventType::Move);
|
|
}
|
|
|
|
(NONE, None, Key::Home) => {
|
|
self.scroll_window(ScrollLocation::Start, TouchEventType::Move);
|
|
}
|
|
|
|
(NONE, None, Key::End) => {
|
|
self.scroll_window(ScrollLocation::End, TouchEventType::Move);
|
|
}
|
|
|
|
(NONE, None, Key::Up) => {
|
|
self.scroll_window(ScrollLocation::Delta(TypedPoint2D::new(0.0, 3.0 * LINE_HEIGHT)),
|
|
TouchEventType::Move);
|
|
}
|
|
(NONE, None, Key::Down) => {
|
|
self.scroll_window(ScrollLocation::Delta(TypedPoint2D::new(0.0, -3.0 * LINE_HEIGHT)),
|
|
TouchEventType::Move);
|
|
}
|
|
(NONE, None, Key::Left) => {
|
|
self.scroll_window(ScrollLocation::Delta(TypedPoint2D::new(LINE_HEIGHT, 0.0)), TouchEventType::Move);
|
|
}
|
|
(NONE, None, Key::Right) => {
|
|
self.scroll_window(ScrollLocation::Delta(TypedPoint2D::new(-LINE_HEIGHT, 0.0)), TouchEventType::Move);
|
|
}
|
|
(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);
|
|
}
|
|
}
|
|
(CMD_OR_CONTROL, Some('q'), _) => {
|
|
if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() {
|
|
self.event_queue.borrow_mut().push(WindowEvent::Quit);
|
|
}
|
|
}
|
|
|
|
_ => {
|
|
self.platform_handle_key(key, mods);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn allow_navigation(&self, _: ServoUrl) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_clipboard(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
struct GlutinCompositorProxy {
|
|
sender: Sender<compositor_thread::Msg>,
|
|
window_proxy: Option<glutin::WindowProxy>,
|
|
}
|
|
|
|
impl CompositorProxy for GlutinCompositorProxy {
|
|
fn send(&self, msg: compositor_thread::Msg) {
|
|
// Send a message and kick the OS event loop awake.
|
|
if let Err(err) = self.sender.send(msg) {
|
|
warn!("Failed to send response ({}).", err);
|
|
}
|
|
if let Some(ref window_proxy) = self.window_proxy {
|
|
window_proxy.wakeup_event_loop()
|
|
}
|
|
}
|
|
fn clone_compositor_proxy(&self) -> Box<CompositorProxy + Send> {
|
|
box GlutinCompositorProxy {
|
|
sender: self.sender.clone(),
|
|
window_proxy: self.window_proxy.clone(),
|
|
} as Box<CompositorProxy + Send>
|
|
}
|
|
}
|
|
|
|
fn glutin_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {
|
|
match phase {
|
|
TouchPhase::Started => TouchEventType::Down,
|
|
TouchPhase::Moved => TouchEventType::Move,
|
|
TouchPhase::Ended => TouchEventType::Up,
|
|
TouchPhase::Cancelled => TouchEventType::Cancel,
|
|
}
|
|
}
|
|
|
|
fn glutin_pressure_stage_to_touchpad_pressure_phase(stage: i64) -> TouchpadPressurePhase {
|
|
if stage < 1 {
|
|
TouchpadPressurePhase::BeforeClick
|
|
} else if stage < 2 {
|
|
TouchpadPressurePhase::AfterFirstClick
|
|
} else {
|
|
TouchpadPressurePhase::AfterSecondClick
|
|
}
|
|
}
|
|
|
|
fn is_printable(key_code: VirtualKeyCode) -> bool {
|
|
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 => false,
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn filter_nonprintable(ch: char, key_code: VirtualKeyCode) -> Option<char> {
|
|
if is_printable(key_code) {
|
|
Some(ch)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
// These functions aren't actually called. They are here as a link
|
|
// hack because Skia references them.
|
|
|
|
#[allow(non_snake_case)]
|
|
#[no_mangle]
|
|
pub extern "C" fn glBindVertexArrayOES(_array: usize)
|
|
{
|
|
unimplemented!()
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[no_mangle]
|
|
pub extern "C" fn glDeleteVertexArraysOES(_n: isize, _arrays: *const ())
|
|
{
|
|
unimplemented!()
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[no_mangle]
|
|
pub extern "C" fn glGenVertexArraysOES(_n: isize, _arrays: *const ())
|
|
{
|
|
unimplemented!()
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[no_mangle]
|
|
pub extern "C" fn glRenderbufferStorageMultisampleIMG(_: isize, _: isize, _: isize, _: isize, _: isize)
|
|
{
|
|
unimplemented!()
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[no_mangle]
|
|
pub extern "C" fn glFramebufferTexture2DMultisampleIMG(_: isize, _: isize, _: isize, _: isize, _: isize, _: isize)
|
|
{
|
|
unimplemented!()
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[no_mangle]
|
|
pub extern "C" fn glDiscardFramebufferEXT(_: isize, _: isize, _: *const ())
|
|
{
|
|
unimplemented!()
|
|
}
|