gecko-dev/servo/ports/glutin/window.rs

1458 строки
53 KiB
Rust

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