mirror of
https://github.com/servo/servo.git
synced 2025-06-13 10:54:29 +00:00
Refactoring of the Glutin port in preparation of the compositor refactoring.
This commit is contained in:
parent
d58ea974ba
commit
21ed7653f4
30 changed files with 1287 additions and 1067 deletions
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"ports/servo",
|
"ports/glutin",
|
||||||
"ports/libsimpleservo/capi/",
|
"ports/libsimpleservo/capi/",
|
||||||
"ports/libsimpleservo/jniapi/",
|
"ports/libsimpleservo/jniapi/",
|
||||||
"ports/libmlservo/",
|
"ports/libmlservo/",
|
||||||
|
|
|
@ -103,7 +103,7 @@ impl FrameTreeId {
|
||||||
enum LayerPixel {}
|
enum LayerPixel {}
|
||||||
|
|
||||||
/// NB: Never block on the constellation, because sometimes the constellation blocks on us.
|
/// NB: Never block on the constellation, because sometimes the constellation blocks on us.
|
||||||
pub struct IOCompositor<Window: WindowMethods> {
|
pub struct IOCompositor<Window: WindowMethods + ?Sized> {
|
||||||
/// The application window.
|
/// The application window.
|
||||||
pub window: Rc<Window>,
|
pub window: Rc<Window>,
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ enum CompositeTarget {
|
||||||
PngFile,
|
PngFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Window: WindowMethods> IOCompositor<Window> {
|
impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
|
||||||
fn new(window: Rc<Window>, state: InitialCompositorState) -> Self {
|
fn new(window: Rc<Window>, state: InitialCompositorState) -> Self {
|
||||||
let composite_target = match opts::get().output_file {
|
let composite_target = match opts::get().output_file {
|
||||||
Some(_) => CompositeTarget::PngFile,
|
Some(_) => CompositeTarget::PngFile,
|
||||||
|
|
|
@ -148,8 +148,6 @@ pub trait WindowMethods {
|
||||||
/// Return the GL function pointer trait.
|
/// Return the GL function pointer trait.
|
||||||
#[cfg(feature = "gl")]
|
#[cfg(feature = "gl")]
|
||||||
fn gl(&self) -> Rc<dyn gl::Gl>;
|
fn gl(&self) -> Rc<dyn gl::Gl>;
|
||||||
/// Returns a thread-safe object to wake up the window's event loop.
|
|
||||||
fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker>;
|
|
||||||
/// Get the coordinates of the native window, the screen and the framebuffer.
|
/// Get the coordinates of the native window, the screen and the framebuffer.
|
||||||
fn get_coordinates(&self) -> EmbedderCoordinates;
|
fn get_coordinates(&self) -> EmbedderCoordinates;
|
||||||
/// Set whether the application is currently animating.
|
/// Set whether the application is currently animating.
|
||||||
|
@ -157,6 +155,11 @@ pub trait WindowMethods {
|
||||||
/// will want to avoid blocking on UI events, and just
|
/// will want to avoid blocking on UI events, and just
|
||||||
/// run the event loop at the vsync interval.
|
/// run the event loop at the vsync interval.
|
||||||
fn set_animation_state(&self, _state: AnimationState);
|
fn set_animation_state(&self, _state: AnimationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait EmbedderMethods {
|
||||||
|
/// Returns a thread-safe object to wake up the window's event loop.
|
||||||
|
fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker>;
|
||||||
/// Register services with a VRServiceManager.
|
/// Register services with a VRServiceManager.
|
||||||
fn register_vr_services(
|
fn register_vr_services(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -67,7 +67,7 @@ use canvas::webgl_thread::WebGLThreads;
|
||||||
use compositing::compositor_thread::{
|
use compositing::compositor_thread::{
|
||||||
CompositorProxy, CompositorReceiver, InitialCompositorState, Msg,
|
CompositorProxy, CompositorReceiver, InitialCompositorState, Msg,
|
||||||
};
|
};
|
||||||
use compositing::windowing::{WindowEvent, WindowMethods};
|
use compositing::windowing::{EmbedderMethods, WindowEvent, WindowMethods};
|
||||||
use compositing::{CompositingReason, IOCompositor, ShutdownState};
|
use compositing::{CompositingReason, IOCompositor, ShutdownState};
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(target_os = "windows"),
|
not(target_os = "windows"),
|
||||||
|
@ -148,7 +148,7 @@ type MediaBackend = media_platform::MediaBackend;
|
||||||
/// application Servo is embedded in. Clients then create an event
|
/// application Servo is embedded in. Clients then create an event
|
||||||
/// loop to pump messages between the embedding application and
|
/// loop to pump messages between the embedding application and
|
||||||
/// various browser components.
|
/// various browser components.
|
||||||
pub struct Servo<Window: WindowMethods + 'static> {
|
pub struct Servo<Window: WindowMethods + 'static + ?Sized> {
|
||||||
compositor: IOCompositor<Window>,
|
compositor: IOCompositor<Window>,
|
||||||
constellation_chan: Sender<ConstellationMsg>,
|
constellation_chan: Sender<ConstellationMsg>,
|
||||||
embedder_receiver: EmbedderReceiver,
|
embedder_receiver: EmbedderReceiver,
|
||||||
|
@ -197,9 +197,9 @@ impl webrender_api::RenderNotifier for RenderNotifier {
|
||||||
|
|
||||||
impl<Window> Servo<Window>
|
impl<Window> Servo<Window>
|
||||||
where
|
where
|
||||||
Window: WindowMethods + 'static,
|
Window: WindowMethods + 'static + ?Sized,
|
||||||
{
|
{
|
||||||
pub fn new(window: Rc<Window>) -> Servo<Window> {
|
pub fn new(embedder: Box<EmbedderMethods>, window: Rc<Window>) -> Servo<Window> {
|
||||||
// Global configuration options, parsed from the command line.
|
// Global configuration options, parsed from the command line.
|
||||||
let opts = opts::get();
|
let opts = opts::get();
|
||||||
|
|
||||||
|
@ -218,9 +218,9 @@ where
|
||||||
// messages to client may need to pump a platform-specific event loop
|
// messages to client may need to pump a platform-specific event loop
|
||||||
// to deliver the message.
|
// to deliver the message.
|
||||||
let (compositor_proxy, compositor_receiver) =
|
let (compositor_proxy, compositor_receiver) =
|
||||||
create_compositor_channel(window.create_event_loop_waker());
|
create_compositor_channel(embedder.create_event_loop_waker());
|
||||||
let (embedder_proxy, embedder_receiver) =
|
let (embedder_proxy, embedder_receiver) =
|
||||||
create_embedder_channel(window.create_event_loop_waker());
|
create_embedder_channel(embedder.create_event_loop_waker());
|
||||||
let time_profiler_chan = profile_time::Profiler::create(
|
let time_profiler_chan = profile_time::Profiler::create(
|
||||||
&opts.time_profiling,
|
&opts.time_profiling,
|
||||||
opts.time_profiler_trace_path.clone(),
|
opts.time_profiler_trace_path.clone(),
|
||||||
|
@ -288,7 +288,7 @@ where
|
||||||
let webvr_services = if pref!(dom.webvr.enabled) {
|
let webvr_services = if pref!(dom.webvr.enabled) {
|
||||||
let mut services = VRServiceManager::new();
|
let mut services = VRServiceManager::new();
|
||||||
services.register_defaults();
|
services.register_defaults();
|
||||||
window.register_vr_services(&mut services, &mut webvr_heartbeats);
|
embedder.register_vr_services(&mut services, &mut webvr_heartbeats);
|
||||||
Some(services)
|
Some(services)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -17,8 +17,9 @@ cd "$(git rev-parse --show-toplevel)"
|
||||||
PATHS=(
|
PATHS=(
|
||||||
"components/compositing/compositor.rs"
|
"components/compositing/compositor.rs"
|
||||||
"components/constellation/"
|
"components/constellation/"
|
||||||
"ports/servo/glutin_app/mod.rs"
|
"ports/glutin/headed_window.rs"
|
||||||
"ports/servo/glutin_app/window.rs"
|
"ports/glutin/headless_window.rs"
|
||||||
|
"ports/glutin/embedder.rs"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make sure the paths exist
|
# Make sure the paths exist
|
||||||
|
|
195
ports/glutin/app.rs
Normal file
195
ports/glutin/app.rs
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Application entry point, runs the event loop.
|
||||||
|
|
||||||
|
use crate::browser::Browser;
|
||||||
|
use crate::embedder::EmbedderCallbacks;
|
||||||
|
use crate::window_trait::WindowPortsMethods;
|
||||||
|
use crate::events_loop::EventsLoop;
|
||||||
|
use crate::{headed_window, headless_window};
|
||||||
|
use servo::compositing::windowing::WindowEvent;
|
||||||
|
use servo::config::opts::{self, parse_url_or_filename};
|
||||||
|
use servo::servo_config::pref;
|
||||||
|
use servo::servo_url::ServoUrl;
|
||||||
|
use servo::{BrowserId, Servo};
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::env;
|
||||||
|
use std::mem;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
events_loop: Rc<RefCell<EventsLoop>>,
|
||||||
|
window: Rc<WindowPortsMethods>,
|
||||||
|
servo: RefCell<Servo<WindowPortsMethods>>,
|
||||||
|
browser: RefCell<Browser<WindowPortsMethods>>,
|
||||||
|
event_queue: RefCell<Vec<WindowEvent>>,
|
||||||
|
suspended: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn run() {
|
||||||
|
let events_loop = EventsLoop::new(opts::get().headless);
|
||||||
|
|
||||||
|
// Implements window methods, used by compositor.
|
||||||
|
let window = if opts::get().headless {
|
||||||
|
headless_window::Window::new(opts::get().initial_window_size)
|
||||||
|
} else {
|
||||||
|
headed_window::Window::new(opts::get().initial_window_size, events_loop.borrow().as_winit())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implements embedder methods, used by libservo and constellation.
|
||||||
|
let embedder = Box::new(EmbedderCallbacks::new(
|
||||||
|
events_loop.clone(),
|
||||||
|
window.gl(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Handle browser state.
|
||||||
|
let browser = Browser::new(window.clone());
|
||||||
|
|
||||||
|
let mut servo = Servo::new(embedder, window.clone());
|
||||||
|
let browser_id = BrowserId::new();
|
||||||
|
servo.handle_events(vec![WindowEvent::NewBrowser(get_default_url(), browser_id)]);
|
||||||
|
servo.setup_logging();
|
||||||
|
|
||||||
|
let app = App {
|
||||||
|
event_queue: RefCell::new(vec![]),
|
||||||
|
events_loop,
|
||||||
|
window: window,
|
||||||
|
browser: RefCell::new(browser),
|
||||||
|
servo: RefCell::new(servo),
|
||||||
|
suspended: Cell::new(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
app.run_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_events(&self) -> Vec<WindowEvent> {
|
||||||
|
mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_events(&self) -> bool {
|
||||||
|
!self.event_queue.borrow().is_empty() || self.window.has_events()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn winit_event_to_servo_event(&self, event: glutin::Event) {
|
||||||
|
match event {
|
||||||
|
// App level events
|
||||||
|
glutin::Event::Suspended(suspended) => {
|
||||||
|
self.suspended.set(suspended);
|
||||||
|
if !suspended {
|
||||||
|
self.event_queue.borrow_mut().push(WindowEvent::Idle);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
glutin::Event::Awakened => {
|
||||||
|
self.event_queue.borrow_mut().push(WindowEvent::Idle);
|
||||||
|
},
|
||||||
|
glutin::Event::DeviceEvent { .. } => {},
|
||||||
|
|
||||||
|
// Window level events
|
||||||
|
glutin::Event::WindowEvent {
|
||||||
|
window_id, event, ..
|
||||||
|
} => {
|
||||||
|
if Some(window_id) != self.window.id() {
|
||||||
|
warn!("Got an event from unknown window");
|
||||||
|
} else {
|
||||||
|
self.window.winit_event_to_servo_event(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_loop(self) {
|
||||||
|
let mut stop = false;
|
||||||
|
loop {
|
||||||
|
let mut events_loop = self.events_loop.borrow_mut();
|
||||||
|
if self.window.is_animating() && !self.suspended.get() {
|
||||||
|
// We block on compositing (self.handle_events() ends up calling swap_buffers)
|
||||||
|
events_loop.poll_events(|e| {
|
||||||
|
self.winit_event_to_servo_event(e);
|
||||||
|
});
|
||||||
|
stop = self.handle_events();
|
||||||
|
} else {
|
||||||
|
// We block on winit's event loop (window events)
|
||||||
|
events_loop.run_forever(|e| {
|
||||||
|
self.winit_event_to_servo_event(e);
|
||||||
|
if self.has_events() && !self.suspended.get() {
|
||||||
|
stop = self.handle_events();
|
||||||
|
}
|
||||||
|
if stop || self.window.is_animating() && !self.suspended.get() {
|
||||||
|
glutin::ControlFlow::Break
|
||||||
|
} else {
|
||||||
|
glutin::ControlFlow::Continue
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if stop {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.servo.into_inner().deinit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_events(&self) -> bool {
|
||||||
|
let mut browser = self.browser.borrow_mut();
|
||||||
|
let mut servo = self.servo.borrow_mut();
|
||||||
|
|
||||||
|
let win_events = self.window.get_events();
|
||||||
|
|
||||||
|
// FIXME: this could be handled by Servo. We don't need
|
||||||
|
// a repaint_synchronously function exposed.
|
||||||
|
let need_resize = win_events.iter().any(|e| match *e {
|
||||||
|
WindowEvent::Resize => true,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut app_events = self.get_events();
|
||||||
|
app_events.extend(win_events);
|
||||||
|
|
||||||
|
browser.handle_window_events(app_events);
|
||||||
|
|
||||||
|
let mut servo_events = servo.get_events();
|
||||||
|
loop {
|
||||||
|
browser.handle_servo_events(servo_events);
|
||||||
|
servo.handle_events(browser.get_events());
|
||||||
|
if browser.shutdown_requested() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
servo_events = servo.get_events();
|
||||||
|
if servo_events.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if need_resize {
|
||||||
|
servo.repaint_synchronously();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_default_url() -> ServoUrl {
|
||||||
|
// If the url is not provided, we fallback to the homepage in prefs,
|
||||||
|
// or a blank page in case the homepage is not set either.
|
||||||
|
let cwd = env::current_dir().unwrap();
|
||||||
|
let cmdline_url = opts::get().url.clone();
|
||||||
|
let pref_url = {
|
||||||
|
let homepage_url = pref!(shell.homepage);
|
||||||
|
parse_url_or_filename(&cwd, &homepage_url).ok()
|
||||||
|
};
|
||||||
|
let blank_url = ServoUrl::parse("about:blank").ok();
|
||||||
|
|
||||||
|
cmdline_url.or(pref_url).or(blank_url).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
|
||||||
|
pub fn gl_version() -> glutin::GlRequest {
|
||||||
|
glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||||
|
pub fn gl_version() -> glutin::GlRequest {
|
||||||
|
glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (3, 0))
|
||||||
|
}
|
|
@ -2,13 +2,13 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use crate::glutin_app::keyutils::{CMD_OR_CONTROL, CMD_OR_ALT};
|
use crate::keyutils::{CMD_OR_ALT, CMD_OR_CONTROL};
|
||||||
use crate::glutin_app::window::{Window, LINE_HEIGHT};
|
use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT};
|
||||||
use euclid::{TypedPoint2D, TypedVector2D};
|
use euclid::{TypedPoint2D, TypedVector2D};
|
||||||
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
|
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
|
||||||
use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent};
|
use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent};
|
||||||
use servo::embedder_traits::{EmbedderMsg, FilterPattern};
|
use servo::embedder_traits::{EmbedderMsg, FilterPattern};
|
||||||
use servo::msg::constellation_msg::{TopLevelBrowsingContextId as BrowserId};
|
use servo::msg::constellation_msg::TopLevelBrowsingContextId as BrowserId;
|
||||||
use servo::msg::constellation_msg::TraversalDirection;
|
use servo::msg::constellation_msg::TraversalDirection;
|
||||||
use servo::net_traits::pub_domains::is_reg_domain;
|
use servo::net_traits::pub_domains::is_reg_domain;
|
||||||
use servo::script_traits::TouchEventType;
|
use servo::script_traits::TouchEventType;
|
||||||
|
@ -25,7 +25,7 @@ use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tinyfiledialogs::{self, MessageBoxIcon};
|
use tinyfiledialogs::{self, MessageBoxIcon};
|
||||||
|
|
||||||
pub struct Browser {
|
pub struct Browser<Window: WindowPortsMethods + ?Sized> {
|
||||||
current_url: Option<ServoUrl>,
|
current_url: Option<ServoUrl>,
|
||||||
/// id of the top level browsing context. It is unique as tabs
|
/// id of the top level browsing context. It is unique as tabs
|
||||||
/// are not supported yet. None until created.
|
/// are not supported yet. None until created.
|
||||||
|
@ -52,8 +52,11 @@ enum LoadingState {
|
||||||
Loaded,
|
Loaded,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Browser {
|
impl<Window> Browser<Window>
|
||||||
pub fn new(window: Rc<Window>) -> Browser {
|
where
|
||||||
|
Window: WindowPortsMethods + ?Sized,
|
||||||
|
{
|
||||||
|
pub fn new(window: Rc<Window>) -> Browser<Window> {
|
||||||
Browser {
|
Browser {
|
||||||
title: None,
|
title: None,
|
||||||
current_url: None,
|
current_url: None,
|
||||||
|
@ -126,7 +129,9 @@ impl Browser {
|
||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
.unwrap_or(10);
|
.unwrap_or(10);
|
||||||
self.event_queue.push(WindowEvent::ToggleSamplingProfiler(
|
self.event_queue.push(WindowEvent::ToggleSamplingProfiler(
|
||||||
Duration::from_millis(rate), Duration::from_secs(duration)));
|
Duration::from_millis(rate),
|
||||||
|
Duration::from_secs(duration),
|
||||||
|
));
|
||||||
})
|
})
|
||||||
.shortcut(Modifiers::CONTROL, Key::F9, || {
|
.shortcut(Modifiers::CONTROL, Key::F9, || {
|
||||||
self.event_queue.push(WindowEvent::CaptureWebRender)
|
self.event_queue.push(WindowEvent::CaptureWebRender)
|
||||||
|
@ -403,14 +408,12 @@ impl Browser {
|
||||||
debug!("HideIME received");
|
debug!("HideIME received");
|
||||||
},
|
},
|
||||||
EmbedderMsg::ReportProfile(bytes) => {
|
EmbedderMsg::ReportProfile(bytes) => {
|
||||||
let filename = env::var("PROFILE_OUTPUT")
|
let filename = env::var("PROFILE_OUTPUT").unwrap_or("samples.json".to_string());
|
||||||
.unwrap_or("samples.json".to_string());
|
let result = File::create(&filename).and_then(|mut f| f.write_all(&bytes));
|
||||||
let result = File::create(&filename)
|
|
||||||
.and_then(|mut f| f.write_all(&bytes));
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
error!("Failed to store profile: {}", e);
|
error!("Failed to store profile: {}", e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
71
ports/glutin/embedder.rs
Normal file
71
ports/glutin/embedder.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Implements the global methods required by Servo (not window/gl/compositor related).
|
||||||
|
|
||||||
|
use crate::app;
|
||||||
|
use crate::events_loop::EventsLoop;
|
||||||
|
use gleam::gl;
|
||||||
|
use glutin;
|
||||||
|
use glutin::dpi::LogicalSize;
|
||||||
|
use glutin::{ContextBuilder, GlWindow};
|
||||||
|
use rust_webvr::GlWindowVRService;
|
||||||
|
use servo::compositing::windowing::EmbedderMethods;
|
||||||
|
use servo::embedder_traits::EventLoopWaker;
|
||||||
|
use servo::servo_config::{opts, pref};
|
||||||
|
use servo::webvr::VRServiceManager;
|
||||||
|
use servo::webvr_traits::WebVRMainThreadHeartbeat;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub struct EmbedderCallbacks {
|
||||||
|
events_loop: Rc<RefCell<EventsLoop>>,
|
||||||
|
gl: Rc<dyn gl::Gl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmbedderCallbacks {
|
||||||
|
pub fn new(events_loop: Rc<RefCell<EventsLoop>>, gl: Rc<gl::Gl>) -> EmbedderCallbacks {
|
||||||
|
EmbedderCallbacks { events_loop, gl }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmbedderMethods for EmbedderCallbacks {
|
||||||
|
fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> {
|
||||||
|
self.events_loop.borrow().create_event_loop_waker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_vr_services(
|
||||||
|
&self,
|
||||||
|
services: &mut VRServiceManager,
|
||||||
|
heartbeats: &mut Vec<Box<WebVRMainThreadHeartbeat>>,
|
||||||
|
) {
|
||||||
|
if !opts::get().headless {
|
||||||
|
if pref!(dom.webvr.test) {
|
||||||
|
warn!("Creating test VR display");
|
||||||
|
// This is safe, because register_vr_services is called from the main thread.
|
||||||
|
let name = String::from("Test VR Display");
|
||||||
|
let size = opts::get().initial_window_size.to_f64();
|
||||||
|
let size = LogicalSize::new(size.width, size.height);
|
||||||
|
let window_builder = glutin::WindowBuilder::new()
|
||||||
|
.with_title(name.clone())
|
||||||
|
.with_dimensions(size)
|
||||||
|
.with_visibility(false)
|
||||||
|
.with_multitouch();
|
||||||
|
let context_builder = ContextBuilder::new()
|
||||||
|
.with_gl(app::gl_version())
|
||||||
|
.with_vsync(false); // Assume the browser vsync is the same as the test VR window vsync
|
||||||
|
let gl_window =
|
||||||
|
GlWindow::new(window_builder, context_builder, &*self.events_loop.borrow().as_winit())
|
||||||
|
.expect("Failed to create window.");
|
||||||
|
let gl = self.gl.clone();
|
||||||
|
let (service, heartbeat) = GlWindowVRService::new(name, gl_window, gl);
|
||||||
|
|
||||||
|
services.register(Box::new(service));
|
||||||
|
heartbeats.push(Box::new(heartbeat));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// FIXME: support headless mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
ports/glutin/events_loop.rs
Normal file
98
ports/glutin/events_loop.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! An event loop implementation that works in headless mode.
|
||||||
|
|
||||||
|
|
||||||
|
use glutin;
|
||||||
|
use servo::embedder_traits::EventLoopWaker;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
pub struct EventsLoop(Option<glutin::EventsLoop>);
|
||||||
|
|
||||||
|
impl EventsLoop {
|
||||||
|
// Ideally, we could use the winit event loop in both modes,
|
||||||
|
// but on Linux, the event loop requires a X11 server.
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
pub fn new(_headless: bool) -> Rc<RefCell<EventsLoop>> {
|
||||||
|
Rc::new(RefCell::new(EventsLoop(Some(glutin::EventsLoop::new()))))
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn new(headless: bool) -> Rc<RefCell<EventsLoop>> {
|
||||||
|
let events_loop = if headless {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(glutin::EventsLoop::new())
|
||||||
|
};
|
||||||
|
Rc::new(RefCell::new(EventsLoop(events_loop)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventsLoop {
|
||||||
|
pub fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> {
|
||||||
|
if let Some(ref events_loop) = self.0 {
|
||||||
|
Box::new(HeadedEventLoopWaker::new(&events_loop))
|
||||||
|
} else {
|
||||||
|
Box::new(HeadlessEventLoopWaker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_winit(&self) -> &glutin::EventsLoop {
|
||||||
|
&self.0.as_ref().expect("Can't access winit event loop while using the fake headless event loop")
|
||||||
|
}
|
||||||
|
pub fn poll_events<F>(&mut self, callback: F) where F: FnMut(glutin::Event) {
|
||||||
|
if let Some(ref mut events_loop) = self.0 {
|
||||||
|
events_loop.poll_events(callback);
|
||||||
|
} else {
|
||||||
|
self.sleep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn run_forever<F>(&mut self, mut callback: F) where F: FnMut(glutin::Event) -> glutin::ControlFlow {
|
||||||
|
if let Some(ref mut events_loop) = self.0 {
|
||||||
|
events_loop.run_forever(callback);
|
||||||
|
} else {
|
||||||
|
loop {
|
||||||
|
self.sleep();
|
||||||
|
if callback(glutin::Event::Awakened) == glutin::ControlFlow::Break {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn sleep(&self) {
|
||||||
|
thread::sleep(time::Duration::from_millis(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HeadedEventLoopWaker {
|
||||||
|
proxy: Arc<glutin::EventsLoopProxy>,
|
||||||
|
}
|
||||||
|
impl HeadedEventLoopWaker {
|
||||||
|
fn new(events_loop: &glutin::EventsLoop) -> HeadedEventLoopWaker {
|
||||||
|
let proxy = Arc::new(events_loop.create_proxy());
|
||||||
|
HeadedEventLoopWaker { proxy }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl EventLoopWaker for HeadedEventLoopWaker {
|
||||||
|
fn wake(&self) {
|
||||||
|
// kick the OS event loop awake.
|
||||||
|
if let Err(err) = self.proxy.wakeup() {
|
||||||
|
warn!("Failed to wake up event loop ({}).", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn clone(&self) -> Box<dyn EventLoopWaker + Send> {
|
||||||
|
Box::new(HeadedEventLoopWaker {
|
||||||
|
proxy: self.proxy.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HeadlessEventLoopWaker;
|
||||||
|
impl EventLoopWaker for HeadlessEventLoopWaker {
|
||||||
|
fn wake(&self) {}
|
||||||
|
fn clone(&self) -> Box<dyn EventLoopWaker + Send> { Box::new(HeadlessEventLoopWaker) }
|
||||||
|
}
|
533
ports/glutin/headed_window.rs
Normal file
533
ports/glutin/headed_window.rs
Normal file
|
@ -0,0 +1,533 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! A glutin window implementation.
|
||||||
|
|
||||||
|
use crate::app;
|
||||||
|
use crate::keyutils::keyboard_event_from_winit;
|
||||||
|
use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT};
|
||||||
|
use euclid::{TypedPoint2D, TypedScale, TypedSize2D, TypedVector2D};
|
||||||
|
use gleam::gl;
|
||||||
|
use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use glutin::os::macos::{ActivationPolicy, WindowBuilderExt};
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
use glutin::Icon;
|
||||||
|
use glutin::{ContextBuilder, GlContext, GlWindow};
|
||||||
|
use glutin::{ElementState, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase};
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
use image;
|
||||||
|
use keyboard_types::{Key, KeyState, KeyboardEvent};
|
||||||
|
use servo::compositing::windowing::{AnimationState, MouseWindowEvent, WindowEvent};
|
||||||
|
use servo::compositing::windowing::{EmbedderCoordinates, WindowMethods};
|
||||||
|
use servo::embedder_traits::Cursor;
|
||||||
|
use servo::script_traits::TouchEventType;
|
||||||
|
use servo::servo_config::opts;
|
||||||
|
use servo::servo_geometry::DeviceIndependentPixel;
|
||||||
|
use servo::style_traits::DevicePixel;
|
||||||
|
use servo::webrender_api::{
|
||||||
|
DeviceIntPoint, DeviceIntRect, DeviceIntSize, FramebufferIntSize, ScrollLocation,
|
||||||
|
};
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::mem;
|
||||||
|
use std::rc::Rc;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use winapi;
|
||||||
|
|
||||||
|
const MULTISAMPLES: u16 = 16;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn builder_with_platform_options(mut builder: glutin::WindowBuilder) -> glutin::WindowBuilder {
|
||||||
|
if opts::get().output_file.is_some() {
|
||||||
|
// Prevent the window from showing in Dock.app, stealing focus,
|
||||||
|
// when generating an output file.
|
||||||
|
builder = builder.with_activation_policy(ActivationPolicy::Prohibited)
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
fn builder_with_platform_options(builder: glutin::WindowBuilder) -> glutin::WindowBuilder {
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
gl_window: GlWindow,
|
||||||
|
screen_size: TypedSize2D<u32, DeviceIndependentPixel>,
|
||||||
|
inner_size: Cell<TypedSize2D<u32, DeviceIndependentPixel>>,
|
||||||
|
mouse_down_button: Cell<Option<glutin::MouseButton>>,
|
||||||
|
mouse_down_point: Cell<TypedPoint2D<i32, DevicePixel>>,
|
||||||
|
primary_monitor: glutin::MonitorId,
|
||||||
|
event_queue: RefCell<Vec<WindowEvent>>,
|
||||||
|
mouse_pos: Cell<TypedPoint2D<i32, DevicePixel>>,
|
||||||
|
last_pressed: Cell<Option<KeyboardEvent>>,
|
||||||
|
animation_state: Cell<AnimationState>,
|
||||||
|
fullscreen: Cell<bool>,
|
||||||
|
gl: Rc<dyn gl::Gl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||||
|
TypedScale::new(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||||
|
let hdc = unsafe { winapi::um::winuser::GetDC(::std::ptr::null_mut()) };
|
||||||
|
let ppi = unsafe { winapi::um::wingdi::GetDeviceCaps(hdc, winapi::um::wingdi::LOGPIXELSY) };
|
||||||
|
TypedScale::new(ppi as f32 / 96.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(
|
||||||
|
win_size: TypedSize2D<u32, DeviceIndependentPixel>,
|
||||||
|
events_loop: &glutin::EventsLoop,
|
||||||
|
) -> Rc<dyn WindowPortsMethods> {
|
||||||
|
let opts = opts::get();
|
||||||
|
|
||||||
|
// 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 = opts.output_file.is_none() && !opts.no_native_titlebar;
|
||||||
|
|
||||||
|
let win_size: DeviceIntSize = (win_size.to_f32() * window_creation_scale_factor()).to_i32();
|
||||||
|
let width = win_size.to_untyped().width;
|
||||||
|
let height = win_size.to_untyped().height;
|
||||||
|
|
||||||
|
let mut window_builder = glutin::WindowBuilder::new()
|
||||||
|
.with_title("Servo".to_string())
|
||||||
|
.with_decorations(!opts.no_native_titlebar)
|
||||||
|
.with_transparency(opts.no_native_titlebar)
|
||||||
|
.with_dimensions(LogicalSize::new(width as f64, height as f64))
|
||||||
|
.with_visibility(visible)
|
||||||
|
.with_multitouch();
|
||||||
|
|
||||||
|
window_builder = builder_with_platform_options(window_builder);
|
||||||
|
|
||||||
|
let mut context_builder = ContextBuilder::new()
|
||||||
|
.with_gl(app::gl_version())
|
||||||
|
.with_vsync(opts.enable_vsync);
|
||||||
|
|
||||||
|
if opts.use_msaa {
|
||||||
|
context_builder = context_builder.with_multisampling(MULTISAMPLES)
|
||||||
|
}
|
||||||
|
|
||||||
|
let glutin_window = GlWindow::new(window_builder, context_builder, &events_loop)
|
||||||
|
.expect("Failed to create window.");
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
{
|
||||||
|
let icon_bytes = include_bytes!("../../resources/servo64.png");
|
||||||
|
glutin_window.set_window_icon(Some(load_icon(icon_bytes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
glutin_window
|
||||||
|
.context()
|
||||||
|
.make_current()
|
||||||
|
.expect("Couldn't make window current");
|
||||||
|
}
|
||||||
|
|
||||||
|
let primary_monitor = events_loop.get_primary_monitor();
|
||||||
|
|
||||||
|
let PhysicalSize {
|
||||||
|
width: screen_width,
|
||||||
|
height: screen_height,
|
||||||
|
} = primary_monitor.get_dimensions();
|
||||||
|
let screen_size = TypedSize2D::new(screen_width as u32, screen_height as u32);
|
||||||
|
// TODO(ajeffrey): can this fail?
|
||||||
|
let LogicalSize { width, height } = glutin_window
|
||||||
|
.get_inner_size()
|
||||||
|
.expect("Failed to get window inner size.");
|
||||||
|
let inner_size = TypedSize2D::new(width as u32, height as u32);
|
||||||
|
|
||||||
|
glutin_window.show();
|
||||||
|
|
||||||
|
let gl = match gl::GlType::default() {
|
||||||
|
gl::GlType::Gl => unsafe {
|
||||||
|
gl::GlFns::load_with(|s| glutin_window.get_proc_address(s) as *const _)
|
||||||
|
},
|
||||||
|
gl::GlType::Gles => unsafe {
|
||||||
|
gl::GlesFns::load_with(|s| glutin_window.get_proc_address(s) as *const _)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
gl.clear_color(0.6, 0.6, 0.6, 1.0);
|
||||||
|
gl.clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
gl.finish();
|
||||||
|
|
||||||
|
let window = Window {
|
||||||
|
gl_window: glutin_window,
|
||||||
|
event_queue: RefCell::new(vec![]),
|
||||||
|
mouse_down_button: Cell::new(None),
|
||||||
|
mouse_down_point: Cell::new(TypedPoint2D::new(0, 0)),
|
||||||
|
mouse_pos: Cell::new(TypedPoint2D::new(0, 0)),
|
||||||
|
last_pressed: Cell::new(None),
|
||||||
|
gl: gl.clone(),
|
||||||
|
animation_state: Cell::new(AnimationState::Idle),
|
||||||
|
fullscreen: Cell::new(false),
|
||||||
|
inner_size: Cell::new(inner_size),
|
||||||
|
primary_monitor,
|
||||||
|
screen_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.present();
|
||||||
|
|
||||||
|
Rc::new(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_received_character(&self, mut ch: char) {
|
||||||
|
info!("winit received character: {:?}", ch);
|
||||||
|
if ch.is_control() {
|
||||||
|
if ch as u8 >= 32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// shift ASCII control characters to lowercase
|
||||||
|
ch = (ch as u8 + 96) as char;
|
||||||
|
}
|
||||||
|
let mut event = if let Some(event) = self.last_pressed.replace(None) {
|
||||||
|
event
|
||||||
|
} else if ch.is_ascii() {
|
||||||
|
// Some keys like Backspace emit a control character in winit
|
||||||
|
// but they are already dealt with in handle_keyboard_input
|
||||||
|
// so just ignore the character.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// For combined characters like the letter e with an acute accent
|
||||||
|
// no keyboard event is emitted. A dummy event is created in this case.
|
||||||
|
KeyboardEvent::default()
|
||||||
|
};
|
||||||
|
event.key = Key::Character(ch.to_string());
|
||||||
|
self.event_queue
|
||||||
|
.borrow_mut()
|
||||||
|
.push(WindowEvent::Keyboard(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_keyboard_input(&self, input: KeyboardInput) {
|
||||||
|
let event = keyboard_event_from_winit(input);
|
||||||
|
if event.state == KeyState::Down && event.key == Key::Unidentified {
|
||||||
|
// If pressed and probably printable, we expect a ReceivedCharacter event.
|
||||||
|
self.last_pressed.set(Some(event));
|
||||||
|
} else if event.key != Key::Unidentified {
|
||||||
|
self.last_pressed.set(None);
|
||||||
|
self.event_queue
|
||||||
|
.borrow_mut()
|
||||||
|
.push(WindowEvent::Keyboard(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to handle a click
|
||||||
|
fn handle_mouse(
|
||||||
|
&self,
|
||||||
|
button: glutin::MouseButton,
|
||||||
|
action: glutin::ElementState,
|
||||||
|
coords: TypedPoint2D<i32, DevicePixel>,
|
||||||
|
) {
|
||||||
|
use servo::script_traits::MouseButton;
|
||||||
|
|
||||||
|
let max_pixel_dist = 10.0 * self.servo_hidpi_factor().get();
|
||||||
|
let event = match action {
|
||||||
|
ElementState::Pressed => {
|
||||||
|
self.mouse_down_point.set(coords);
|
||||||
|
self.mouse_down_button.set(Some(button));
|
||||||
|
MouseWindowEvent::MouseDown(MouseButton::Left, coords.to_f32())
|
||||||
|
},
|
||||||
|
ElementState::Released => {
|
||||||
|
let mouse_up_event = MouseWindowEvent::MouseUp(MouseButton::Left, coords.to_f32());
|
||||||
|
match self.mouse_down_button.get() {
|
||||||
|
None => mouse_up_event,
|
||||||
|
Some(but) if button == but => {
|
||||||
|
let pixel_dist = self.mouse_down_point.get() - coords;
|
||||||
|
let pixel_dist =
|
||||||
|
((pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y) as f32)
|
||||||
|
.sqrt();
|
||||||
|
if pixel_dist < max_pixel_dist {
|
||||||
|
self.event_queue
|
||||||
|
.borrow_mut()
|
||||||
|
.push(WindowEvent::MouseWindowEventClass(mouse_up_event));
|
||||||
|
MouseWindowEvent::Click(MouseButton::Left, coords.to_f32())
|
||||||
|
} else {
|
||||||
|
mouse_up_event
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(_) => mouse_up_event,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.event_queue
|
||||||
|
.borrow_mut()
|
||||||
|
.push(WindowEvent::MouseWindowEventClass(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||||
|
TypedScale::new(self.gl_window.get_hidpi_factor() as f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn servo_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||||
|
match opts::get().device_pixels_per_px {
|
||||||
|
Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px),
|
||||||
|
_ => match opts::get().output_file {
|
||||||
|
Some(_) => TypedScale::new(1.0),
|
||||||
|
None => self.device_hidpi_factor(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowPortsMethods for Window {
|
||||||
|
fn get_events(&self) -> Vec<WindowEvent> {
|
||||||
|
mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_events(&self) -> bool {
|
||||||
|
!self.event_queue.borrow().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_height(&self) -> f32 {
|
||||||
|
let dpr = self.servo_hidpi_factor();
|
||||||
|
let size = self
|
||||||
|
.gl_window
|
||||||
|
.get_inner_size()
|
||||||
|
.expect("Failed to get window inner size.");
|
||||||
|
size.height as f32 * dpr.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_title(&self, title: &str) {
|
||||||
|
self.gl_window.set_title(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_inner_size(&self, size: DeviceIntSize) {
|
||||||
|
let size = size.to_f32() / self.device_hidpi_factor();
|
||||||
|
self.gl_window
|
||||||
|
.set_inner_size(LogicalSize::new(size.width.into(), size.height.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_position(&self, point: DeviceIntPoint) {
|
||||||
|
let point = point.to_f32() / self.device_hidpi_factor();
|
||||||
|
self.gl_window
|
||||||
|
.set_position(LogicalPosition::new(point.x.into(), point.y.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_fullscreen(&self, state: bool) {
|
||||||
|
if self.fullscreen.get() != state {
|
||||||
|
self.gl_window
|
||||||
|
.set_fullscreen(Some(self.primary_monitor.clone()));
|
||||||
|
}
|
||||||
|
self.fullscreen.set(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fullscreen(&self) -> bool {
|
||||||
|
return self.fullscreen.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor(&self, cursor: Cursor) {
|
||||||
|
use glutin::MouseCursor;
|
||||||
|
|
||||||
|
let winit_cursor = match cursor {
|
||||||
|
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,
|
||||||
|
_ => MouseCursor::Default,
|
||||||
|
};
|
||||||
|
self.gl_window.set_cursor(winit_cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_animating(&self) -> bool {
|
||||||
|
self.animation_state.get() == AnimationState::Animating
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<glutin::WindowId> {
|
||||||
|
Some(self.gl_window.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn winit_event_to_servo_event(&self, event: glutin::WindowEvent) {
|
||||||
|
match event {
|
||||||
|
glutin::WindowEvent::ReceivedCharacter(ch) => self.handle_received_character(ch),
|
||||||
|
glutin::WindowEvent::KeyboardInput { input, .. } => self.handle_keyboard_input(input),
|
||||||
|
glutin::WindowEvent::MouseInput { state, button, .. } => {
|
||||||
|
if button == MouseButton::Left || button == MouseButton::Right {
|
||||||
|
self.handle_mouse(button, state, self.mouse_pos.get());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
glutin::WindowEvent::CursorMoved { position, .. } => {
|
||||||
|
let pos = position.to_physical(self.device_hidpi_factor().get() as f64);
|
||||||
|
let (x, y): (i32, i32) = pos.into();
|
||||||
|
self.mouse_pos.set(TypedPoint2D::new(x, y));
|
||||||
|
self.event_queue
|
||||||
|
.borrow_mut()
|
||||||
|
.push(WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new(
|
||||||
|
x as f32, y as f32,
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
glutin::WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||||
|
let (mut dx, mut dy) = match delta {
|
||||||
|
MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
|
||||||
|
MouseScrollDelta::PixelDelta(position) => {
|
||||||
|
let position =
|
||||||
|
position.to_physical(self.device_hidpi_factor().get() as f64);
|
||||||
|
(position.x as f32, position.y as f32)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Scroll events snap to the major axis of movement, with vertical
|
||||||
|
// preferred over horizontal.
|
||||||
|
if dy.abs() >= dx.abs() {
|
||||||
|
dx = 0.0;
|
||||||
|
} else {
|
||||||
|
dy = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scroll_location = ScrollLocation::Delta(TypedVector2D::new(dx, dy));
|
||||||
|
let phase = winit_phase_to_touch_event_type(phase);
|
||||||
|
let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase);
|
||||||
|
self.event_queue.borrow_mut().push(event);
|
||||||
|
},
|
||||||
|
glutin::WindowEvent::Touch(touch) => {
|
||||||
|
use servo::script_traits::TouchId;
|
||||||
|
|
||||||
|
let phase = winit_phase_to_touch_event_type(touch.phase);
|
||||||
|
let id = TouchId(touch.id as i32);
|
||||||
|
let position = touch
|
||||||
|
.location
|
||||||
|
.to_physical(self.device_hidpi_factor().get() as f64);
|
||||||
|
let point = TypedPoint2D::new(position.x as f32, position.y as f32);
|
||||||
|
self.event_queue
|
||||||
|
.borrow_mut()
|
||||||
|
.push(WindowEvent::Touch(phase, id, point));
|
||||||
|
},
|
||||||
|
glutin::WindowEvent::Refresh => {
|
||||||
|
self.event_queue.borrow_mut().push(WindowEvent::Refresh);
|
||||||
|
},
|
||||||
|
glutin::WindowEvent::CloseRequested => {
|
||||||
|
self.event_queue.borrow_mut().push(WindowEvent::Quit);
|
||||||
|
},
|
||||||
|
glutin::WindowEvent::Resized(size) => {
|
||||||
|
// size is DeviceIndependentPixel.
|
||||||
|
// gl_window.resize() takes DevicePixel.
|
||||||
|
let size = size.to_physical(self.device_hidpi_factor().get() as f64);
|
||||||
|
self.gl_window.resize(size);
|
||||||
|
// window.set_inner_size() takes DeviceIndependentPixel.
|
||||||
|
let (width, height) = size.into();
|
||||||
|
let new_size = TypedSize2D::new(width, height);
|
||||||
|
if self.inner_size.get() != new_size {
|
||||||
|
self.inner_size.set(new_size);
|
||||||
|
self.event_queue.borrow_mut().push(WindowEvent::Resize);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowMethods for Window {
|
||||||
|
fn gl(&self) -> Rc<dyn gl::Gl> {
|
||||||
|
self.gl.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_coordinates(&self) -> EmbedderCoordinates {
|
||||||
|
// TODO(ajeffrey): can this fail?
|
||||||
|
let dpr = self.device_hidpi_factor();
|
||||||
|
let LogicalSize { width, height } = self
|
||||||
|
.gl_window
|
||||||
|
.get_outer_size()
|
||||||
|
.expect("Failed to get window outer size.");
|
||||||
|
let LogicalPosition { x, y } = self
|
||||||
|
.gl_window
|
||||||
|
.get_position()
|
||||||
|
.unwrap_or(LogicalPosition::new(0., 0.));
|
||||||
|
let win_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32();
|
||||||
|
let win_origin = (TypedPoint2D::new(x as f32, y as f32) * dpr).to_i32();
|
||||||
|
let screen = (self.screen_size.to_f32() * dpr).to_i32();
|
||||||
|
|
||||||
|
let LogicalSize { width, height } = self
|
||||||
|
.gl_window
|
||||||
|
.get_inner_size()
|
||||||
|
.expect("Failed to get window inner size.");
|
||||||
|
let inner_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32();
|
||||||
|
let viewport = DeviceIntRect::new(TypedPoint2D::zero(), inner_size);
|
||||||
|
let framebuffer = FramebufferIntSize::from_untyped(&viewport.size.to_untyped());
|
||||||
|
|
||||||
|
EmbedderCoordinates {
|
||||||
|
viewport,
|
||||||
|
framebuffer,
|
||||||
|
window: (win_size, win_origin),
|
||||||
|
screen: screen,
|
||||||
|
// FIXME: Glutin doesn't have API for available size. Fallback to screen size
|
||||||
|
screen_avail: screen,
|
||||||
|
hidpi_factor: self.servo_hidpi_factor(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present(&self) {
|
||||||
|
if let Err(err) = self.gl_window.swap_buffers() {
|
||||||
|
warn!("Failed to swap window buffers ({}).", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_animation_state(&self, state: AnimationState) {
|
||||||
|
self.animation_state.set(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_for_composite(&self) -> bool {
|
||||||
|
if let Err(err) = unsafe { self.gl_window.context().make_current() } {
|
||||||
|
warn!("Couldn't make window current: {}", err);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn winit_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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
fn load_icon(icon_bytes: &[u8]) -> Icon {
|
||||||
|
let (icon_rgba, icon_width, icon_height) = {
|
||||||
|
use image::{GenericImageView, Pixel};
|
||||||
|
let image = image::load_from_memory(icon_bytes).expect("Failed to load icon");;
|
||||||
|
let (width, height) = image.dimensions();
|
||||||
|
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
|
||||||
|
for (_, _, pixel) in image.pixels() {
|
||||||
|
rgba.extend_from_slice(&pixel.to_rgba().data);
|
||||||
|
}
|
||||||
|
(rgba, width, height)
|
||||||
|
};
|
||||||
|
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon")
|
||||||
|
}
|
200
ports/glutin/headless_window.rs
Normal file
200
ports/glutin/headless_window.rs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! A headless window implementation.
|
||||||
|
|
||||||
|
use crate::window_trait::WindowPortsMethods;
|
||||||
|
use glutin;
|
||||||
|
use euclid::{TypedPoint2D, TypedScale, TypedSize2D};
|
||||||
|
use gleam::gl;
|
||||||
|
use servo::compositing::windowing::{AnimationState, WindowEvent};
|
||||||
|
use servo::compositing::windowing::{EmbedderCoordinates, WindowMethods};
|
||||||
|
use servo::servo_config::opts;
|
||||||
|
use servo::servo_geometry::DeviceIndependentPixel;
|
||||||
|
use servo::style_traits::DevicePixel;
|
||||||
|
use servo::webrender_api::{DeviceIntRect, FramebufferIntSize};
|
||||||
|
use std::cell::Cell;
|
||||||
|
#[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;
|
||||||
|
|
||||||
|
#[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_ne!(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 _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
context: HeadlessContext,
|
||||||
|
animation_state: Cell<AnimationState>,
|
||||||
|
fullscreen: Cell<bool>,
|
||||||
|
gl: Rc<dyn gl::Gl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(size: TypedSize2D<u32, DeviceIndependentPixel>) -> Rc<dyn WindowPortsMethods> {
|
||||||
|
let context = HeadlessContext::new(size.width, size.height);
|
||||||
|
let gl = unsafe { gl::GlFns::load_with(|s| HeadlessContext::get_proc_address(s)) };
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
let window = Window {
|
||||||
|
context,
|
||||||
|
gl,
|
||||||
|
animation_state: Cell::new(AnimationState::Idle),
|
||||||
|
fullscreen: Cell::new(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
Rc::new(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn servo_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||||
|
match opts::get().device_pixels_per_px {
|
||||||
|
Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px),
|
||||||
|
_ => TypedScale::new(1.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowPortsMethods for Window {
|
||||||
|
fn get_events(&self) -> Vec<WindowEvent> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_events(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<glutin::WindowId> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_height(&self) -> f32 {
|
||||||
|
let dpr = self.servo_hidpi_factor();
|
||||||
|
self.context.height as f32 * dpr.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_fullscreen(&self, state: bool) {
|
||||||
|
self.fullscreen.set(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fullscreen(&self) -> bool {
|
||||||
|
return self.fullscreen.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_animating(&self) -> bool {
|
||||||
|
self.animation_state.get() == AnimationState::Animating
|
||||||
|
}
|
||||||
|
|
||||||
|
fn winit_event_to_servo_event(&self, _event: glutin::WindowEvent) {
|
||||||
|
// Not expecting any winit events.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowMethods for Window {
|
||||||
|
fn gl(&self) -> Rc<dyn gl::Gl> {
|
||||||
|
self.gl.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_coordinates(&self) -> EmbedderCoordinates {
|
||||||
|
let dpr = self.servo_hidpi_factor();
|
||||||
|
let size =
|
||||||
|
(TypedSize2D::new(self.context.width, self.context.height).to_f32() * dpr).to_i32();
|
||||||
|
let viewport = DeviceIntRect::new(TypedPoint2D::zero(), size);
|
||||||
|
let framebuffer = FramebufferIntSize::from_untyped(&size.to_untyped());
|
||||||
|
EmbedderCoordinates {
|
||||||
|
viewport,
|
||||||
|
framebuffer,
|
||||||
|
window: (size, TypedPoint2D::zero()),
|
||||||
|
screen: size,
|
||||||
|
screen_avail: size,
|
||||||
|
hidpi_factor: dpr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present(&self) {}
|
||||||
|
|
||||||
|
fn set_animation_state(&self, state: AnimationState) {
|
||||||
|
self.animation_state.set(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_for_composite(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,8 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use keyboard_types::{Code, Key, KeyboardEvent, KeyState, Modifiers, Location};
|
|
||||||
use glutin::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode};
|
use glutin::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode};
|
||||||
|
use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, Modifiers};
|
||||||
|
|
||||||
// Some shortcuts use Cmd on Mac and Control on other systems.
|
// Some shortcuts use Cmd on Mac and Control on other systems.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
|
@ -17,14 +17,8 @@
|
||||||
|
|
||||||
#![cfg_attr(feature = "unstable", feature(core_intrinsics))]
|
#![cfg_attr(feature = "unstable", feature(core_intrinsics))]
|
||||||
|
|
||||||
// Have this here rather than in non_android_main.rs to work around
|
|
||||||
// https://github.com/rust-lang/rust/issues/53205
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
#[macro_use]
|
include!("main2.rs");
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
include!("non_android_main.rs");
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub fn main() {
|
pub fn main() {
|
|
@ -2,24 +2,29 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
#[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))]
|
#[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))]
|
||||||
#[macro_use] extern crate sig;
|
#[macro_use]
|
||||||
|
extern crate sig;
|
||||||
// The window backed by glutin
|
|
||||||
mod glutin_app;
|
|
||||||
|
|
||||||
mod resources;
|
|
||||||
|
|
||||||
|
mod app;
|
||||||
mod browser;
|
mod browser;
|
||||||
|
mod embedder;
|
||||||
|
mod events_loop;
|
||||||
|
mod headed_window;
|
||||||
|
mod headless_window;
|
||||||
|
mod keyutils;
|
||||||
|
mod resources;
|
||||||
|
mod skia_symbols;
|
||||||
|
mod window_trait;
|
||||||
|
|
||||||
|
use app::App;
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use servo::{Servo, BrowserId};
|
use servo::config::opts::{self, ArgumentParsingResult};
|
||||||
use servo::compositing::windowing::WindowEvent;
|
|
||||||
use servo::config::opts::{self, ArgumentParsingResult, parse_url_or_filename};
|
|
||||||
use servo::config::servo_version;
|
use servo::config::servo_version;
|
||||||
use servo::servo_config::pref;
|
|
||||||
use servo::servo_url::ServoUrl;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::panic;
|
use std::panic;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
@ -36,7 +41,10 @@ pub mod platform {
|
||||||
pub fn deinit() {}
|
pub fn deinit() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(not(feature = "unstable"), not(any(target_os = "macos", target_os = "linux"))))]
|
#[cfg(any(
|
||||||
|
not(feature = "unstable"),
|
||||||
|
not(any(target_os = "macos", target_os = "linux"))
|
||||||
|
))]
|
||||||
fn install_crash_handler() {}
|
fn install_crash_handler() {}
|
||||||
|
|
||||||
#[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))]
|
#[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))]
|
||||||
|
@ -122,112 +130,7 @@ pub fn main() {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let window = glutin_app::create_window();
|
App::run();
|
||||||
|
|
||||||
let mut browser = browser::Browser::new(window.clone());
|
|
||||||
|
|
||||||
// If the url is not provided, we fallback to the homepage in prefs,
|
|
||||||
// or a blank page in case the homepage is not set either.
|
|
||||||
let cwd = env::current_dir().unwrap();
|
|
||||||
let cmdline_url = opts::get().url.clone();
|
|
||||||
let pref_url = {
|
|
||||||
let homepage_url = pref!(shell.homepage);
|
|
||||||
parse_url_or_filename(&cwd, &homepage_url).ok()
|
|
||||||
};
|
|
||||||
let blank_url = ServoUrl::parse("about:blank").ok();
|
|
||||||
|
|
||||||
let target_url = cmdline_url.or(pref_url).or(blank_url).unwrap();
|
|
||||||
|
|
||||||
let mut servo = Servo::new(window.clone());
|
|
||||||
let browser_id = BrowserId::new();
|
|
||||||
servo.handle_events(vec![WindowEvent::NewBrowser(target_url, browser_id)]);
|
|
||||||
|
|
||||||
servo.setup_logging();
|
|
||||||
|
|
||||||
window.run(|| {
|
|
||||||
let win_events = window.get_events();
|
|
||||||
|
|
||||||
// FIXME: this could be handled by Servo. We don't need
|
|
||||||
// a repaint_synchronously function exposed.
|
|
||||||
let need_resize = win_events.iter().any(|e| match *e {
|
|
||||||
WindowEvent::Resize => true,
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.handle_window_events(win_events);
|
|
||||||
|
|
||||||
let mut servo_events = servo.get_events();
|
|
||||||
loop {
|
|
||||||
browser.handle_servo_events(servo_events);
|
|
||||||
servo.handle_events(browser.get_events());
|
|
||||||
if browser.shutdown_requested() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
servo_events = servo.get_events();
|
|
||||||
if servo_events.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if need_resize {
|
|
||||||
servo.repaint_synchronously();
|
|
||||||
}
|
|
||||||
false
|
|
||||||
});
|
|
||||||
|
|
||||||
servo.deinit();
|
|
||||||
|
|
||||||
platform::deinit()
|
platform::deinit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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!()
|
|
||||||
}
|
|
|
@ -14,18 +14,17 @@ pub fn deinit() {
|
||||||
ptr::read_volatile(&INFO_PLIST[0]);
|
ptr::read_volatile(&INFO_PLIST[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let thread_count = unsafe {
|
let thread_count = unsafe { macos_count_running_threads() };
|
||||||
macos_count_running_threads()
|
|
||||||
};
|
|
||||||
|
|
||||||
if thread_count != 1 {
|
if thread_count != 1 {
|
||||||
println!("{} threads are still running after shutdown (bad).", thread_count);
|
println!(
|
||||||
|
"{} threads are still running after shutdown (bad).",
|
||||||
|
thread_count
|
||||||
|
);
|
||||||
if opts::get().clean_shutdown {
|
if opts::get().clean_shutdown {
|
||||||
println!("Waiting until all threads have shutdown");
|
println!("Waiting until all threads have shutdown");
|
||||||
loop {
|
loop {
|
||||||
let thread_count = unsafe {
|
let thread_count = unsafe { macos_count_running_threads() };
|
||||||
macos_count_running_threads()
|
|
||||||
};
|
|
||||||
if thread_count == 1 {
|
if thread_count == 1 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +42,6 @@ pub fn deinit() {
|
||||||
pub static INFO_PLIST: [u8; 619] = *include_bytes!("Info.plist");
|
pub static INFO_PLIST: [u8; 619] = *include_bytes!("Info.plist");
|
||||||
|
|
||||||
#[link(name = "count_threads")]
|
#[link(name = "count_threads")]
|
||||||
extern {
|
extern "C" {
|
||||||
fn macos_count_running_threads() -> i32;
|
fn macos_count_running_threads() -> i32;
|
||||||
}
|
}
|
55
ports/glutin/skia_symbols.rs
Normal file
55
ports/glutin/skia_symbols.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! 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!()
|
||||||
|
}
|
29
ports/glutin/window_trait.rs
Normal file
29
ports/glutin/window_trait.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Definition of Window.
|
||||||
|
//! Implemented by headless and headed windows.
|
||||||
|
|
||||||
|
use glutin;
|
||||||
|
use servo::compositing::windowing::{WindowEvent, WindowMethods};
|
||||||
|
use servo::embedder_traits::Cursor;
|
||||||
|
use servo::webrender_api::{DeviceIntPoint, DeviceIntSize};
|
||||||
|
|
||||||
|
// This should vary by zoom level and maybe actual text size (focused or under cursor)
|
||||||
|
pub const LINE_HEIGHT: f32 = 38.0;
|
||||||
|
|
||||||
|
pub trait WindowPortsMethods: WindowMethods {
|
||||||
|
fn get_events(&self) -> Vec<WindowEvent>;
|
||||||
|
fn id(&self) -> Option<glutin::WindowId>;
|
||||||
|
fn has_events(&self) -> bool;
|
||||||
|
fn page_height(&self) -> f32;
|
||||||
|
fn get_fullscreen(&self) -> bool;
|
||||||
|
fn winit_event_to_servo_event(&self, event: glutin::WindowEvent);
|
||||||
|
fn is_animating(&self) -> bool;
|
||||||
|
fn set_title(&self, _title: &str) {}
|
||||||
|
fn set_inner_size(&self, _size: DeviceIntSize) {}
|
||||||
|
fn set_position(&self, _point: DeviceIntPoint) {}
|
||||||
|
fn set_fullscreen(&self, _state: bool) {}
|
||||||
|
fn set_cursor(&self, _cursor: Cursor) {}
|
||||||
|
}
|
|
@ -10,7 +10,8 @@ pub mod gl_glue;
|
||||||
pub use servo::script_traits::MouseButton;
|
pub use servo::script_traits::MouseButton;
|
||||||
|
|
||||||
use servo::compositing::windowing::{
|
use servo::compositing::windowing::{
|
||||||
AnimationState, EmbedderCoordinates, MouseWindowEvent, WindowEvent, WindowMethods,
|
AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowEvent,
|
||||||
|
WindowMethods,
|
||||||
};
|
};
|
||||||
use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
|
use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
|
||||||
use servo::embedder_traits::EmbedderMsg;
|
use servo::embedder_traits::EmbedderMsg;
|
||||||
|
@ -112,9 +113,9 @@ pub trait HostTrait {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ServoGlue {
|
pub struct ServoGlue {
|
||||||
servo: Servo<ServoCallbacks>,
|
servo: Servo<ServoWindowCallbacks>,
|
||||||
batch_mode: bool,
|
batch_mode: bool,
|
||||||
callbacks: Rc<ServoCallbacks>,
|
callbacks: Rc<ServoWindowCallbacks>,
|
||||||
/// id of the top level browsing context. It is unique as tabs
|
/// id of the top level browsing context. It is unique as tabs
|
||||||
/// are not supported yet. None until created.
|
/// are not supported yet. None until created.
|
||||||
browser_id: Option<BrowserId>,
|
browser_id: Option<BrowserId>,
|
||||||
|
@ -169,22 +170,25 @@ pub fn init(
|
||||||
gl.clear(gl::COLOR_BUFFER_BIT);
|
gl.clear(gl::COLOR_BUFFER_BIT);
|
||||||
gl.finish();
|
gl.finish();
|
||||||
|
|
||||||
let callbacks = Rc::new(ServoCallbacks {
|
let window_callbacks = Rc::new(ServoWindowCallbacks {
|
||||||
gl: gl.clone(),
|
gl: gl.clone(),
|
||||||
host_callbacks: callbacks,
|
host_callbacks: callbacks,
|
||||||
coordinates: RefCell::new(init_opts.coordinates),
|
coordinates: RefCell::new(init_opts.coordinates),
|
||||||
density: init_opts.density,
|
density: init_opts.density,
|
||||||
|
});
|
||||||
|
|
||||||
|
let embedder_callbacks = Box::new(ServoEmbedderCallbacks {
|
||||||
vr_pointer: init_opts.vr_pointer,
|
vr_pointer: init_opts.vr_pointer,
|
||||||
waker,
|
waker,
|
||||||
});
|
});
|
||||||
|
|
||||||
let servo = Servo::new(callbacks.clone());
|
let servo = Servo::new(embedder_callbacks, window_callbacks.clone());
|
||||||
|
|
||||||
SERVO.with(|s| {
|
SERVO.with(|s| {
|
||||||
let mut servo_glue = ServoGlue {
|
let mut servo_glue = ServoGlue {
|
||||||
servo,
|
servo,
|
||||||
batch_mode: false,
|
batch_mode: false,
|
||||||
callbacks,
|
callbacks: window_callbacks,
|
||||||
browser_id: None,
|
browser_id: None,
|
||||||
browsers: vec![],
|
browsers: vec![],
|
||||||
events: vec![],
|
events: vec![],
|
||||||
|
@ -546,16 +550,37 @@ impl ServoGlue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServoCallbacks {
|
struct ServoEmbedderCallbacks {
|
||||||
waker: Box<dyn EventLoopWaker>,
|
waker: Box<dyn EventLoopWaker>,
|
||||||
|
vr_pointer: Option<*mut c_void>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ServoWindowCallbacks {
|
||||||
gl: Rc<dyn gl::Gl>,
|
gl: Rc<dyn gl::Gl>,
|
||||||
host_callbacks: Box<dyn HostTrait>,
|
host_callbacks: Box<dyn HostTrait>,
|
||||||
coordinates: RefCell<Coordinates>,
|
coordinates: RefCell<Coordinates>,
|
||||||
density: f32,
|
density: f32,
|
||||||
vr_pointer: Option<*mut c_void>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowMethods for ServoCallbacks {
|
impl EmbedderMethods for ServoEmbedderCallbacks {
|
||||||
|
fn register_vr_services(
|
||||||
|
&self,
|
||||||
|
services: &mut VRServiceManager,
|
||||||
|
_: &mut Vec<Box<VRMainThreadHeartbeat>>,
|
||||||
|
) {
|
||||||
|
debug!("EmbedderMethods::register_vrexternal");
|
||||||
|
if let Some(ptr) = self.vr_pointer {
|
||||||
|
services.register_vrexternal(VRExternalShmemPtr::new(ptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> {
|
||||||
|
debug!("EmbedderMethods::create_event_loop_waker");
|
||||||
|
self.waker.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowMethods for ServoWindowCallbacks {
|
||||||
fn prepare_for_composite(&self) -> bool {
|
fn prepare_for_composite(&self) -> bool {
|
||||||
debug!("WindowMethods::prepare_for_composite");
|
debug!("WindowMethods::prepare_for_composite");
|
||||||
self.host_callbacks.make_current();
|
self.host_callbacks.make_current();
|
||||||
|
@ -567,11 +592,6 @@ impl WindowMethods for ServoCallbacks {
|
||||||
self.host_callbacks.flush();
|
self.host_callbacks.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> {
|
|
||||||
debug!("WindowMethods::create_event_loop_waker");
|
|
||||||
self.waker.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gl(&self) -> Rc<dyn gl::Gl> {
|
fn gl(&self) -> Rc<dyn gl::Gl> {
|
||||||
debug!("WindowMethods::gl");
|
debug!("WindowMethods::gl");
|
||||||
self.gl.clone()
|
self.gl.clone()
|
||||||
|
@ -594,16 +614,6 @@ impl WindowMethods for ServoCallbacks {
|
||||||
hidpi_factor: TypedScale::new(self.density),
|
hidpi_factor: TypedScale::new(self.density),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_vr_services(
|
|
||||||
&self,
|
|
||||||
services: &mut VRServiceManager,
|
|
||||||
_: &mut Vec<Box<VRMainThreadHeartbeat>>,
|
|
||||||
) {
|
|
||||||
if let Some(ptr) = self.vr_pointer {
|
|
||||||
services.register_vrexternal(VRExternalShmemPtr::new(ptr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResourceReaderInstance;
|
struct ResourceReaderInstance;
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
//! A simple application that uses glutin to open a window for Servo to display in.
|
|
||||||
|
|
||||||
pub mod keyutils;
|
|
||||||
pub mod window;
|
|
||||||
|
|
||||||
use servo::servo_config::opts;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
pub fn create_window() -> Rc<window::Window> {
|
|
||||||
// Read command-line options.
|
|
||||||
let opts = opts::get();
|
|
||||||
let foreground = opts.output_file.is_none() && !opts.headless;
|
|
||||||
|
|
||||||
// Open a window.
|
|
||||||
window::Window::new(foreground, opts.initial_window_size)
|
|
||||||
}
|
|
|
@ -1,854 +0,0 @@
|
||||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
//! A windowing implementation using winit.
|
|
||||||
|
|
||||||
use euclid::{TypedPoint2D, TypedVector2D, TypedScale, TypedSize2D};
|
|
||||||
use gleam::gl;
|
|
||||||
use glutin::{Api, ContextBuilder, GlContext, GlRequest, GlWindow};
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
||||||
use image;
|
|
||||||
use keyboard_types::{Key, KeyboardEvent, KeyState};
|
|
||||||
use rust_webvr::GlWindowVRService;
|
|
||||||
use servo::compositing::windowing::{AnimationState, MouseWindowEvent, WindowEvent};
|
|
||||||
use servo::compositing::windowing::{EmbedderCoordinates, WindowMethods};
|
|
||||||
use servo::embedder_traits::{Cursor, EventLoopWaker};
|
|
||||||
use servo::script_traits::TouchEventType;
|
|
||||||
use servo::servo_config::{opts, pref};
|
|
||||||
use servo::servo_geometry::DeviceIndependentPixel;
|
|
||||||
use servo::style_traits::DevicePixel;
|
|
||||||
use servo::webrender_api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, FramebufferIntSize, ScrollLocation};
|
|
||||||
use servo::webvr::VRServiceManager;
|
|
||||||
use servo::webvr_traits::WebVRMainThreadHeartbeat;
|
|
||||||
use std::cell::{Cell, RefCell};
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::mem;
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
use std::ptr;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread;
|
|
||||||
use std::time;
|
|
||||||
use super::keyutils::keyboard_event_from_winit;
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use winapi;
|
|
||||||
use glutin::{ElementState, Event, MouseButton, MouseScrollDelta, TouchPhase, KeyboardInput};
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
||||||
use glutin::Icon;
|
|
||||||
use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
use glutin::os::macos::{ActivationPolicy, WindowBuilderExt};
|
|
||||||
|
|
||||||
// This should vary by zoom level and maybe actual text size (focused or under cursor)
|
|
||||||
pub 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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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_ne!(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(GlWindow, RefCell<glutin::EventsLoop>),
|
|
||||||
Headless(HeadlessContext),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The type of a window.
|
|
||||||
pub struct Window {
|
|
||||||
kind: WindowKind,
|
|
||||||
screen_size: TypedSize2D<u32, DeviceIndependentPixel>,
|
|
||||||
inner_size: Cell<TypedSize2D<u32, DeviceIndependentPixel>>,
|
|
||||||
mouse_down_button: Cell<Option<glutin::MouseButton>>,
|
|
||||||
mouse_down_point: Cell<TypedPoint2D<i32, DevicePixel>>,
|
|
||||||
event_queue: RefCell<Vec<WindowEvent>>,
|
|
||||||
mouse_pos: Cell<TypedPoint2D<i32, DevicePixel>>,
|
|
||||||
last_pressed: Cell<Option<KeyboardEvent>>,
|
|
||||||
animation_state: Cell<AnimationState>,
|
|
||||||
fullscreen: Cell<bool>,
|
|
||||||
gl: Rc<dyn gl::Gl>,
|
|
||||||
suspended: Cell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
|
||||||
TypedScale::new(1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
|
||||||
let hdc = unsafe { winapi::um::winuser::GetDC(::std::ptr::null_mut()) };
|
|
||||||
let ppi = unsafe { winapi::um::wingdi::GetDeviceCaps(hdc, winapi::um::wingdi::LOGPIXELSY) };
|
|
||||||
TypedScale::new(ppi as f32 / 96.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn new(
|
|
||||||
is_foreground: bool,
|
|
||||||
window_size: TypedSize2D<u32, DeviceIndependentPixel>,
|
|
||||||
) -> Rc<Window> {
|
|
||||||
let win_size: DeviceIntSize =
|
|
||||||
(window_size.to_f32() * window_creation_scale_factor()).to_i32();
|
|
||||||
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 screen_size;
|
|
||||||
let inner_size;
|
|
||||||
let window_kind = if opts::get().headless {
|
|
||||||
screen_size = TypedSize2D::new(width as u32, height as u32);
|
|
||||||
inner_size = TypedSize2D::new(width as u32, height as u32);
|
|
||||||
WindowKind::Headless(HeadlessContext::new(width as u32, height as u32))
|
|
||||||
} else {
|
|
||||||
let events_loop = glutin::EventsLoop::new();
|
|
||||||
let mut window_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(LogicalSize::new(width as f64, height as f64))
|
|
||||||
.with_visibility(visible)
|
|
||||||
.with_multitouch();
|
|
||||||
|
|
||||||
window_builder = builder_with_platform_options(window_builder);
|
|
||||||
|
|
||||||
let mut context_builder = ContextBuilder::new()
|
|
||||||
.with_gl(Window::gl_version())
|
|
||||||
.with_vsync(opts::get().enable_vsync);
|
|
||||||
|
|
||||||
if opts::get().use_msaa {
|
|
||||||
context_builder = context_builder.with_multisampling(MULTISAMPLES)
|
|
||||||
}
|
|
||||||
|
|
||||||
let glutin_window = GlWindow::new(window_builder, context_builder, &events_loop)
|
|
||||||
.expect("Failed to create window.");
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
||||||
{
|
|
||||||
let icon_bytes = include_bytes!("../../../resources/servo64.png");
|
|
||||||
glutin_window.set_window_icon(Some(load_icon(icon_bytes)));
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
glutin_window
|
|
||||||
.context()
|
|
||||||
.make_current()
|
|
||||||
.expect("Couldn't make window current");
|
|
||||||
}
|
|
||||||
|
|
||||||
let PhysicalSize {
|
|
||||||
width: screen_width,
|
|
||||||
height: screen_height,
|
|
||||||
} = events_loop.get_primary_monitor().get_dimensions();
|
|
||||||
screen_size = TypedSize2D::new(screen_width as u32, screen_height as u32);
|
|
||||||
// TODO(ajeffrey): can this fail?
|
|
||||||
let LogicalSize { width, height } = glutin_window
|
|
||||||
.get_inner_size()
|
|
||||||
.expect("Failed to get window inner size.");
|
|
||||||
inner_size = TypedSize2D::new(width as u32, height as u32);
|
|
||||||
|
|
||||||
glutin_window.show();
|
|
||||||
|
|
||||||
WindowKind::Window(glutin_window, RefCell::new(events_loop))
|
|
||||||
};
|
|
||||||
|
|
||||||
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(TypedPoint2D::new(0, 0)),
|
|
||||||
mouse_pos: Cell::new(TypedPoint2D::new(0, 0)),
|
|
||||||
last_pressed: Cell::new(None),
|
|
||||||
gl: gl.clone(),
|
|
||||||
animation_state: Cell::new(AnimationState::Idle),
|
|
||||||
fullscreen: Cell::new(false),
|
|
||||||
inner_size: Cell::new(inner_size),
|
|
||||||
screen_size,
|
|
||||||
suspended: Cell::new(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
window.present();
|
|
||||||
|
|
||||||
Rc::new(window)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_events(&self) -> Vec<WindowEvent> {
|
|
||||||
mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn page_height(&self) -> f32 {
|
|
||||||
let dpr = self.servo_hidpi_factor();
|
|
||||||
match self.kind {
|
|
||||||
WindowKind::Window(ref window, _) => {
|
|
||||||
let size = window
|
|
||||||
.get_inner_size()
|
|
||||||
.expect("Failed to get window inner size.");
|
|
||||||
size.height as f32 * dpr.get()
|
|
||||||
},
|
|
||||||
WindowKind::Headless(ref context) => context.height as f32 * dpr.get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_title(&self, title: &str) {
|
|
||||||
if let WindowKind::Window(ref window, _) = self.kind {
|
|
||||||
window.set_title(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_inner_size(&self, size: DeviceIntSize) {
|
|
||||||
if let WindowKind::Window(ref window, _) = self.kind {
|
|
||||||
let size = size.to_f32() / self.device_hidpi_factor();
|
|
||||||
window.set_inner_size(LogicalSize::new(size.width.into(), size.height.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_position(&self, point: DeviceIntPoint) {
|
|
||||||
if let WindowKind::Window(ref window, _) = self.kind {
|
|
||||||
let point = point.to_f32() / self.device_hidpi_factor();
|
|
||||||
window.set_position(LogicalPosition::new(point.x.into(), point.y.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, state: bool) {
|
|
||||||
match self.kind {
|
|
||||||
WindowKind::Window(ref window, ..) => {
|
|
||||||
if self.fullscreen.get() != state {
|
|
||||||
window.set_fullscreen(Some(window.get_primary_monitor()));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
WindowKind::Headless(..) => {},
|
|
||||||
}
|
|
||||||
self.fullscreen.set(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_fullscreen(&self) -> bool {
|
|
||||||
return self.fullscreen.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_animating(&self) -> bool {
|
|
||||||
self.animation_state.get() == AnimationState::Animating && !self.suspended.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<T>(&self, mut servo_callback: T)
|
|
||||||
where
|
|
||||||
T: FnMut() -> bool,
|
|
||||||
{
|
|
||||||
match self.kind {
|
|
||||||
WindowKind::Window(_, ref events_loop) => {
|
|
||||||
let mut stop = false;
|
|
||||||
loop {
|
|
||||||
if self.is_animating() {
|
|
||||||
// We block on compositing (servo_callback ends up calling swap_buffers)
|
|
||||||
events_loop.borrow_mut().poll_events(|e| {
|
|
||||||
self.winit_event_to_servo_event(e);
|
|
||||||
});
|
|
||||||
stop = servo_callback();
|
|
||||||
} else {
|
|
||||||
// We block on winit's event loop (window events)
|
|
||||||
events_loop.borrow_mut().run_forever(|e| {
|
|
||||||
self.winit_event_to_servo_event(e);
|
|
||||||
if !self.event_queue.borrow().is_empty() {
|
|
||||||
if !self.suspended.get() {
|
|
||||||
stop = servo_callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stop || self.is_animating() {
|
|
||||||
glutin::ControlFlow::Break
|
|
||||||
} else {
|
|
||||||
glutin::ControlFlow::Continue
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if stop {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
WindowKind::Headless(..) => {
|
|
||||||
loop {
|
|
||||||
// Sleep the main thread to avoid using 100% CPU
|
|
||||||
// This can be done better, see comments in #18777
|
|
||||||
if self.event_queue.borrow().is_empty() {
|
|
||||||
thread::sleep(time::Duration::from_millis(5));
|
|
||||||
}
|
|
||||||
let stop = servo_callback();
|
|
||||||
if stop {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_received_character(&self, mut ch: char) {
|
|
||||||
info!("winit received character: {:?}", ch);
|
|
||||||
if ch.is_control() {
|
|
||||||
if ch as u8 >= 32 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// shift ASCII control characters to lowercase
|
|
||||||
ch = (ch as u8 + 96) as char;
|
|
||||||
}
|
|
||||||
let mut event = if let Some(event) = self.last_pressed.replace(None) {
|
|
||||||
event
|
|
||||||
} else if ch.is_ascii() {
|
|
||||||
// Some keys like Backspace emit a control character in winit
|
|
||||||
// but they are already dealt with in handle_keyboard_input
|
|
||||||
// so just ignore the character.
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// For combined characters like the letter e with an acute accent
|
|
||||||
// no keyboard event is emitted. A dummy event is created in this case.
|
|
||||||
KeyboardEvent::default()
|
|
||||||
};
|
|
||||||
event.key = Key::Character(ch.to_string());
|
|
||||||
self.event_queue
|
|
||||||
.borrow_mut()
|
|
||||||
.push(WindowEvent::Keyboard(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_keyboard_input(
|
|
||||||
&self,
|
|
||||||
input: KeyboardInput,
|
|
||||||
) {
|
|
||||||
let event = keyboard_event_from_winit(input);
|
|
||||||
if event.state == KeyState::Down && event.key == Key::Unidentified {
|
|
||||||
// If pressed and probably printable, we expect a ReceivedCharacter event.
|
|
||||||
self.last_pressed.set(Some(event));
|
|
||||||
} else if event.key != Key::Unidentified {
|
|
||||||
self.last_pressed.set(None);
|
|
||||||
self.event_queue
|
|
||||||
.borrow_mut()
|
|
||||||
.push(WindowEvent::Keyboard(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn winit_event_to_servo_event(&self, event: glutin::Event) {
|
|
||||||
if let WindowKind::Window(ref window, _) = self.kind {
|
|
||||||
if let Event::WindowEvent { window_id, .. } = event {
|
|
||||||
if window.id() != window_id {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::ReceivedCharacter(ch),
|
|
||||||
..
|
|
||||||
} => self.handle_received_character(ch),
|
|
||||||
Event::WindowEvent {
|
|
||||||
event:
|
|
||||||
glutin::WindowEvent::KeyboardInput {
|
|
||||||
input,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => self.handle_keyboard_input(input),
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::MouseInput { state, button, .. },
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if button == MouseButton::Left || button == MouseButton::Right {
|
|
||||||
self.handle_mouse(button, state, self.mouse_pos.get());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::CursorMoved { position, .. },
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let pos = position.to_physical(self.device_hidpi_factor().get() as f64);
|
|
||||||
let (x, y): (i32, i32) = pos.into();
|
|
||||||
self.mouse_pos.set(TypedPoint2D::new(x, y));
|
|
||||||
self.event_queue
|
|
||||||
.borrow_mut()
|
|
||||||
.push(WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new(
|
|
||||||
x as f32, y as f32,
|
|
||||||
)));
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::MouseWheel { delta, phase, .. },
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let (mut dx, mut dy) = match delta {
|
|
||||||
MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
|
|
||||||
MouseScrollDelta::PixelDelta(position) => {
|
|
||||||
let position =
|
|
||||||
position.to_physical(self.device_hidpi_factor().get() as f64);
|
|
||||||
(position.x as f32, position.y as f32)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Scroll events snap to the major axis of movement, with vertical
|
|
||||||
// preferred over horizontal.
|
|
||||||
if dy.abs() >= dx.abs() {
|
|
||||||
dx = 0.0;
|
|
||||||
} else {
|
|
||||||
dy = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scroll_location = ScrollLocation::Delta(TypedVector2D::new(dx, dy));
|
|
||||||
let phase = winit_phase_to_touch_event_type(phase);
|
|
||||||
let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase);
|
|
||||||
self.event_queue.borrow_mut().push(event);
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::Touch(touch),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
use servo::script_traits::TouchId;
|
|
||||||
|
|
||||||
let phase = winit_phase_to_touch_event_type(touch.phase);
|
|
||||||
let id = TouchId(touch.id as i32);
|
|
||||||
let position = touch
|
|
||||||
.location
|
|
||||||
.to_physical(self.device_hidpi_factor().get() as f64);
|
|
||||||
let point = TypedPoint2D::new(position.x as f32, position.y as f32);
|
|
||||||
self.event_queue
|
|
||||||
.borrow_mut()
|
|
||||||
.push(WindowEvent::Touch(phase, id, point));
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::Refresh,
|
|
||||||
..
|
|
||||||
} => self.event_queue.borrow_mut().push(WindowEvent::Refresh),
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
self.event_queue.borrow_mut().push(WindowEvent::Quit);
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: glutin::WindowEvent::Resized(size),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
// size is DeviceIndependentPixel.
|
|
||||||
// window.resize() takes DevicePixel.
|
|
||||||
if let WindowKind::Window(ref window, _) = self.kind {
|
|
||||||
let size = size.to_physical(self.device_hidpi_factor().get() as f64);
|
|
||||||
window.resize(size);
|
|
||||||
}
|
|
||||||
// window.set_inner_size() takes DeviceIndependentPixel.
|
|
||||||
let (width, height) = size.into();
|
|
||||||
let new_size = TypedSize2D::new(width, height);
|
|
||||||
if self.inner_size.get() != new_size {
|
|
||||||
self.inner_size.set(new_size);
|
|
||||||
self.event_queue.borrow_mut().push(WindowEvent::Resize);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::Suspended(suspended) => {
|
|
||||||
self.suspended.set(suspended);
|
|
||||||
if !suspended {
|
|
||||||
self.event_queue.borrow_mut().push(WindowEvent::Idle);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::Awakened => {
|
|
||||||
self.event_queue.borrow_mut().push(WindowEvent::Idle);
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to handle a click
|
|
||||||
fn handle_mouse(
|
|
||||||
&self,
|
|
||||||
button: glutin::MouseButton,
|
|
||||||
action: glutin::ElementState,
|
|
||||||
coords: TypedPoint2D<i32, DevicePixel>,
|
|
||||||
) {
|
|
||||||
use servo::script_traits::MouseButton;
|
|
||||||
|
|
||||||
let max_pixel_dist = 10.0 * self.servo_hidpi_factor().get();
|
|
||||||
let event = match action {
|
|
||||||
ElementState::Pressed => {
|
|
||||||
self.mouse_down_point.set(coords);
|
|
||||||
self.mouse_down_button.set(Some(button));
|
|
||||||
MouseWindowEvent::MouseDown(MouseButton::Left, coords.to_f32())
|
|
||||||
},
|
|
||||||
ElementState::Released => {
|
|
||||||
let mouse_up_event = MouseWindowEvent::MouseUp(MouseButton::Left, coords.to_f32());
|
|
||||||
match self.mouse_down_button.get() {
|
|
||||||
None => mouse_up_event,
|
|
||||||
Some(but) if button == but => {
|
|
||||||
let pixel_dist = self.mouse_down_point.get() - coords;
|
|
||||||
let pixel_dist =
|
|
||||||
((pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y) as f32)
|
|
||||||
.sqrt();
|
|
||||||
if pixel_dist < max_pixel_dist {
|
|
||||||
self.event_queue
|
|
||||||
.borrow_mut()
|
|
||||||
.push(WindowEvent::MouseWindowEventClass(mouse_up_event));
|
|
||||||
MouseWindowEvent::Click(MouseButton::Left, coords.to_f32())
|
|
||||||
} else {
|
|
||||||
mouse_up_event
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(_) => mouse_up_event,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
self.event_queue
|
|
||||||
.borrow_mut()
|
|
||||||
.push(WindowEvent::MouseWindowEventClass(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
|
||||||
match self.kind {
|
|
||||||
WindowKind::Window(ref window, ..) => TypedScale::new(window.get_hidpi_factor() as f32),
|
|
||||||
WindowKind::Headless(..) => TypedScale::new(1.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn servo_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
|
|
||||||
match opts::get().device_pixels_per_px {
|
|
||||||
Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px),
|
|
||||||
_ => match opts::get().output_file {
|
|
||||||
Some(_) => TypedScale::new(1.0),
|
|
||||||
None => self.device_hidpi_factor(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor(&self, cursor: Cursor) {
|
|
||||||
match self.kind {
|
|
||||||
WindowKind::Window(ref window, ..) => {
|
|
||||||
use glutin::MouseCursor;
|
|
||||||
|
|
||||||
let winit_cursor = match cursor {
|
|
||||||
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,
|
|
||||||
_ => MouseCursor::Default,
|
|
||||||
};
|
|
||||||
window.set_cursor(winit_cursor);
|
|
||||||
},
|
|
||||||
WindowKind::Headless(..) => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowMethods for Window {
|
|
||||||
fn gl(&self) -> Rc<dyn gl::Gl> {
|
|
||||||
self.gl.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_coordinates(&self) -> EmbedderCoordinates {
|
|
||||||
match self.kind {
|
|
||||||
WindowKind::Window(ref window, _) => {
|
|
||||||
// TODO(ajeffrey): can this fail?
|
|
||||||
let dpr = self.device_hidpi_factor();
|
|
||||||
let LogicalSize { width, height } = window
|
|
||||||
.get_outer_size()
|
|
||||||
.expect("Failed to get window outer size.");
|
|
||||||
let LogicalPosition { x, y } = window
|
|
||||||
.get_position()
|
|
||||||
.unwrap_or(LogicalPosition::new(0., 0.));
|
|
||||||
let win_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32();
|
|
||||||
let win_origin = (TypedPoint2D::new(x as f32, y as f32) * dpr).to_i32();
|
|
||||||
let screen = (self.screen_size.to_f32() * dpr).to_i32();
|
|
||||||
|
|
||||||
let LogicalSize { width, height } = window
|
|
||||||
.get_inner_size()
|
|
||||||
.expect("Failed to get window inner size.");
|
|
||||||
let inner_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32();
|
|
||||||
let viewport = DeviceIntRect::new(TypedPoint2D::zero(), inner_size);
|
|
||||||
let framebuffer = FramebufferIntSize::from_untyped(&viewport.size.to_untyped());
|
|
||||||
|
|
||||||
EmbedderCoordinates {
|
|
||||||
viewport,
|
|
||||||
framebuffer,
|
|
||||||
window: (win_size, win_origin),
|
|
||||||
screen: screen,
|
|
||||||
// FIXME: Glutin doesn't have API for available size. Fallback to screen size
|
|
||||||
screen_avail: screen,
|
|
||||||
hidpi_factor: self.servo_hidpi_factor(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
WindowKind::Headless(ref context) => {
|
|
||||||
let dpr = self.servo_hidpi_factor();
|
|
||||||
let size =
|
|
||||||
(TypedSize2D::new(context.width, context.height).to_f32() * dpr).to_i32();
|
|
||||||
let viewport = DeviceIntRect::new(TypedPoint2D::zero(), size);
|
|
||||||
let framebuffer = FramebufferIntSize::from_untyped(&size.to_untyped());
|
|
||||||
EmbedderCoordinates {
|
|
||||||
viewport,
|
|
||||||
framebuffer,
|
|
||||||
window: (size, TypedPoint2D::zero()),
|
|
||||||
screen: size,
|
|
||||||
screen_avail: size,
|
|
||||||
hidpi_factor: dpr,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_event_loop_waker(&self) -> Box<dyn EventLoopWaker> {
|
|
||||||
struct GlutinEventLoopWaker {
|
|
||||||
proxy: Option<Arc<glutin::EventsLoopProxy>>,
|
|
||||||
}
|
|
||||||
impl GlutinEventLoopWaker {
|
|
||||||
fn new(window: &Window) -> GlutinEventLoopWaker {
|
|
||||||
let proxy = match window.kind {
|
|
||||||
WindowKind::Window(_, ref events_loop) => {
|
|
||||||
Some(Arc::new(events_loop.borrow().create_proxy()))
|
|
||||||
},
|
|
||||||
WindowKind::Headless(..) => None,
|
|
||||||
};
|
|
||||||
GlutinEventLoopWaker { proxy }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl EventLoopWaker for GlutinEventLoopWaker {
|
|
||||||
fn wake(&self) {
|
|
||||||
// kick the OS event loop awake.
|
|
||||||
if let Some(ref proxy) = self.proxy {
|
|
||||||
if let Err(err) = proxy.wakeup() {
|
|
||||||
warn!("Failed to wake up event loop ({}).", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn clone(&self) -> Box<dyn EventLoopWaker + Send> {
|
|
||||||
Box::new(GlutinEventLoopWaker {
|
|
||||||
proxy: self.proxy.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box::new(GlutinEventLoopWaker::new(&self))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_animation_state(&self, state: AnimationState) {
|
|
||||||
self.animation_state.set(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_for_composite(&self) -> bool {
|
|
||||||
if let WindowKind::Window(ref window, ..) = self.kind {
|
|
||||||
if let Err(err) = unsafe { window.context().make_current() } {
|
|
||||||
warn!("Couldn't make window current: {}", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_vr_services(
|
|
||||||
&self,
|
|
||||||
services: &mut VRServiceManager,
|
|
||||||
heartbeats: &mut Vec<Box<WebVRMainThreadHeartbeat>>
|
|
||||||
) {
|
|
||||||
if pref!(dom.webvr.test) {
|
|
||||||
warn!("Creating test VR display");
|
|
||||||
// TODO: support dom.webvr.test in headless environments
|
|
||||||
if let WindowKind::Window(_, ref events_loop) = self.kind {
|
|
||||||
// This is safe, because register_vr_services is called from the main thread.
|
|
||||||
let name = String::from("Test VR Display");
|
|
||||||
let size = self.inner_size.get().to_f64();
|
|
||||||
let size = LogicalSize::new(size.width, size.height);
|
|
||||||
let mut window_builder = glutin::WindowBuilder::new()
|
|
||||||
.with_title(name.clone())
|
|
||||||
.with_dimensions(size)
|
|
||||||
.with_visibility(false)
|
|
||||||
.with_multitouch();
|
|
||||||
window_builder = builder_with_platform_options(window_builder);
|
|
||||||
let context_builder = ContextBuilder::new()
|
|
||||||
.with_gl(Window::gl_version())
|
|
||||||
.with_vsync(false); // Assume the browser vsync is the same as the test VR window vsync
|
|
||||||
let gl_window = GlWindow::new(window_builder, context_builder, &*events_loop.borrow())
|
|
||||||
.expect("Failed to create window.");
|
|
||||||
let gl = self.gl.clone();
|
|
||||||
let (service, heartbeat) = GlWindowVRService::new(name, gl_window, gl);
|
|
||||||
|
|
||||||
services.register(Box::new(service));
|
|
||||||
heartbeats.push(Box::new(heartbeat));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn winit_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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
||||||
fn load_icon(icon_bytes: &[u8]) -> Icon {
|
|
||||||
let (icon_rgba, icon_width, icon_height) = {
|
|
||||||
use image::{GenericImageView, Pixel};
|
|
||||||
let image = image::load_from_memory(icon_bytes).expect("Failed to load icon");;
|
|
||||||
let (width, height) = image.dimensions();
|
|
||||||
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
|
|
||||||
for (_, _, pixel) in image.pixels() {
|
|
||||||
rgba.extend_from_slice(&pixel.to_rgba().data);
|
|
||||||
}
|
|
||||||
(rgba, width, height)
|
|
||||||
};
|
|
||||||
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon")
|
|
||||||
}
|
|
|
@ -761,4 +761,4 @@ class MachCommands(CommandBase):
|
||||||
opts += ["-v"]
|
opts += ["-v"]
|
||||||
opts += params
|
opts += params
|
||||||
return check_call(["cargo", "clean"] + opts,
|
return check_call(["cargo", "clean"] + opts,
|
||||||
env=self.build_env(), cwd=self.ports_servo_crate(), verbose=verbose)
|
env=self.build_env(), cwd=self.ports_glutin_crate(), verbose=verbose)
|
||||||
|
|
|
@ -723,20 +723,20 @@ install them, let us know by filing a bug!")
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def ports_servo_crate(self):
|
def ports_glutin_crate(self):
|
||||||
return path.join(self.context.topdir, "ports", "servo")
|
return path.join(self.context.topdir, "ports", "glutin")
|
||||||
|
|
||||||
def add_manifest_path(self, args, android=False, libsimpleservo=False):
|
def add_manifest_path(self, args, android=False, libsimpleservo=False):
|
||||||
if "--manifest-path" not in args:
|
if "--manifest-path" not in args:
|
||||||
if libsimpleservo or android:
|
if libsimpleservo or android:
|
||||||
manifest = self.ports_libsimpleservo_manifest(android)
|
manifest = self.ports_libsimpleservo_manifest(android)
|
||||||
else:
|
else:
|
||||||
manifest = self.ports_servo_manifest()
|
manifest = self.ports_glutin_manifest()
|
||||||
args.append("--manifest-path")
|
args.append("--manifest-path")
|
||||||
args.append(manifest)
|
args.append(manifest)
|
||||||
|
|
||||||
def ports_servo_manifest(self):
|
def ports_glutin_manifest(self):
|
||||||
return path.join(self.context.topdir, "ports", "servo", "Cargo.toml")
|
return path.join(self.context.topdir, "ports", "glutin", "Cargo.toml")
|
||||||
|
|
||||||
def ports_libsimpleservo_manifest(self, android=False):
|
def ports_libsimpleservo_manifest(self, android=False):
|
||||||
if android:
|
if android:
|
||||||
|
|
|
@ -265,7 +265,7 @@ class PostBuildCommands(CommandBase):
|
||||||
copy2(full_name, destination)
|
copy2(full_name, destination)
|
||||||
|
|
||||||
returncode = self.call_rustup_run(
|
returncode = self.call_rustup_run(
|
||||||
["cargo", "doc", "--manifest-path", self.ports_servo_manifest()] + params,
|
["cargo", "doc", "--manifest-path", self.ports_glutin_manifest()] + params,
|
||||||
env=self.build_env())
|
env=self.build_env())
|
||||||
if returncode:
|
if returncode:
|
||||||
return returncode
|
return returncode
|
||||||
|
|
|
@ -279,7 +279,7 @@ class MachCommands(CommandBase):
|
||||||
|
|
||||||
features = self.servo_features()
|
features = self.servo_features()
|
||||||
if len(packages) > 0 or len(in_crate_packages) > 0:
|
if len(packages) > 0 or len(in_crate_packages) > 0:
|
||||||
args = ["cargo", "bench" if bench else "test", "--manifest-path", self.ports_servo_manifest()]
|
args = ["cargo", "bench" if bench else "test", "--manifest-path", self.ports_glutin_manifest()]
|
||||||
for crate in packages:
|
for crate in packages:
|
||||||
args += ["-p", "%s_tests" % crate]
|
args += ["-p", "%s_tests" % crate]
|
||||||
for crate in in_crate_packages:
|
for crate in in_crate_packages:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue