mirror of
https://github.com/servo/servo.git
synced 2025-07-23 23:33:43 +01:00
Remove ports/libmlservo
This commit is contained in:
parent
e3fed81a2f
commit
f0690ec7c1
2 changed files with 0 additions and 599 deletions
|
@ -1,28 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "libmlservo"
|
|
||||||
version = "0.0.1"
|
|
||||||
authors = ["The Servo Project Developers"]
|
|
||||||
license = "MPL-2.0"
|
|
||||||
edition = "2018"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "mlservo"
|
|
||||||
crate-type = ["staticlib"]
|
|
||||||
test = false
|
|
||||||
bench = false
|
|
||||||
|
|
||||||
[features]
|
|
||||||
egl = ["simpleservo/egl"]
|
|
||||||
layout-2013 = ["simpleservo/layout-2013"]
|
|
||||||
layout-2020 = ["simpleservo/layout-2020"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
libc = { workspace = true }
|
|
||||||
libservo = { path = "../../components/servo", features = ["no_static_freetype"] }
|
|
||||||
log = { workspace = true }
|
|
||||||
servo-egl = "0.2"
|
|
||||||
simpleservo = { path = "../libsimpleservo/api", features = ["no_static_freetype"] }
|
|
||||||
smallvec = { workspace = true }
|
|
||||||
webxr = { git = "https://github.com/servo/webxr", features = ["ipc", "magicleap"] }
|
|
||||||
webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] }
|
|
|
@ -1,571 +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/. */
|
|
||||||
|
|
||||||
use egl::egl::EGLContext;
|
|
||||||
use egl::egl::EGLDisplay;
|
|
||||||
use egl::egl::EGLSurface;
|
|
||||||
use egl::egl::MakeCurrent;
|
|
||||||
use egl::egl::SwapBuffers;
|
|
||||||
use libc::{dup2, pipe, read};
|
|
||||||
use log::info;
|
|
||||||
use log::warn;
|
|
||||||
use rust_webvr::api::MagicLeapVRService;
|
|
||||||
use servo::euclid::Scale;
|
|
||||||
use servo::keyboard_types::Key;
|
|
||||||
use servo::servo_url::ServoUrl;
|
|
||||||
use servo::webrender_api::units::{DeviceIntRect, DevicePixel, DevicePoint, LayoutPixel};
|
|
||||||
use simpleservo::{self, deinit, gl_glue, MouseButton, ServoGlue, SERVO};
|
|
||||||
use simpleservo::{
|
|
||||||
Coordinates, EventLoopWaker, HostTrait, InitOptions, InputMethodType, PromptResult,
|
|
||||||
VRInitOptions,
|
|
||||||
};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::os::raw::c_int;
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::time::Instant;
|
|
||||||
use webxr::magicleap::MagicLeapDiscovery;
|
|
||||||
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum MLLogLevel {
|
|
||||||
Fatal = 0,
|
|
||||||
Error = 1,
|
|
||||||
Warning = 2,
|
|
||||||
Info = 3,
|
|
||||||
Debug = 4,
|
|
||||||
Verbose = 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub enum MLKeyType {
|
|
||||||
kNone,
|
|
||||||
kCharacter,
|
|
||||||
kBackspace,
|
|
||||||
kShift,
|
|
||||||
kSpeechToText,
|
|
||||||
kPageEmoji,
|
|
||||||
kPageLowerLetters,
|
|
||||||
kPageNumericSymbols,
|
|
||||||
kCancel,
|
|
||||||
kSubmit,
|
|
||||||
kPrevious,
|
|
||||||
kNext,
|
|
||||||
kClear,
|
|
||||||
kClose,
|
|
||||||
kEnter,
|
|
||||||
kCustom1,
|
|
||||||
kCustom2,
|
|
||||||
kCustom3,
|
|
||||||
kCustom4,
|
|
||||||
kCustom5,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct MLLogger(Option<extern "C" fn(MLLogLevel, *const c_char)>);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct MLHistoryUpdate(Option<extern "C" fn(MLApp, bool, bool)>);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct MLURLUpdate(Option<extern "C" fn(MLApp, *const c_char)>);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct MLKeyboard(Option<extern "C" fn(MLApp, bool)>);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct MLApp(*mut c_void);
|
|
||||||
|
|
||||||
const LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info;
|
|
||||||
|
|
||||||
fn call<F, T>(f: F) -> Result<T, &'static str>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut ServoGlue) -> Result<T, &'static str>,
|
|
||||||
{
|
|
||||||
SERVO.with(|s| match s.borrow_mut().as_mut() {
|
|
||||||
Some(ref mut s) => (f)(s),
|
|
||||||
None => Err("Servo is not available in this thread"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn init_servo(
|
|
||||||
ctxt: EGLContext,
|
|
||||||
surf: EGLSurface,
|
|
||||||
disp: EGLDisplay,
|
|
||||||
landscape: bool,
|
|
||||||
app: MLApp,
|
|
||||||
logger: MLLogger,
|
|
||||||
history_update: MLHistoryUpdate,
|
|
||||||
url_update: MLURLUpdate,
|
|
||||||
keyboard: MLKeyboard,
|
|
||||||
url: *const c_char,
|
|
||||||
default_args: *const c_char,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
hidpi: f32,
|
|
||||||
) -> *mut ServoInstance {
|
|
||||||
redirect_stdout_to_log(logger);
|
|
||||||
let _ = log::set_boxed_logger(Box::new(logger));
|
|
||||||
log::set_max_level(LOG_LEVEL);
|
|
||||||
|
|
||||||
let gl = gl_glue::egl::init().expect("EGL initialization failure");
|
|
||||||
|
|
||||||
let coordinates = Coordinates::new(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
width as i32,
|
|
||||||
height as i32,
|
|
||||||
width as i32,
|
|
||||||
height as i32,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut url = CStr::from_ptr(url).to_str().unwrap_or("about:blank");
|
|
||||||
|
|
||||||
// If the URL has a space in it, then treat everything before the space as arguments
|
|
||||||
let args = if let Some(i) = url.rfind(' ') {
|
|
||||||
let (front, back) = url.split_at(i);
|
|
||||||
url = back;
|
|
||||||
front.split(' ').map(|s| s.to_owned()).collect()
|
|
||||||
} else if !default_args.is_null() {
|
|
||||||
CStr::from_ptr(default_args)
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or("")
|
|
||||||
.split(' ')
|
|
||||||
.map(|s| s.to_owned())
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("got args: {:?}", args);
|
|
||||||
|
|
||||||
let vr_init = if !landscape {
|
|
||||||
let name = String::from("Magic Leap VR Display");
|
|
||||||
let (service, heartbeat) = MagicLeapVRService::new(name, ctxt, gl.gl_wrapper.clone())
|
|
||||||
.expect("Failed to create VR service");
|
|
||||||
let service = Box::new(service);
|
|
||||||
let heartbeat = Box::new(heartbeat);
|
|
||||||
VRInitOptions::VRService(service, heartbeat)
|
|
||||||
} else {
|
|
||||||
VRInitOptions::None
|
|
||||||
};
|
|
||||||
|
|
||||||
let xr_discovery: Option<Box<dyn webxr_api::Discovery>> = if !landscape {
|
|
||||||
let discovery = MagicLeapDiscovery::new(ctxt, gl.gl_wrapper.clone());
|
|
||||||
Some(Box::new(discovery))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let opts = InitOptions {
|
|
||||||
args,
|
|
||||||
density: hidpi,
|
|
||||||
xr_discovery,
|
|
||||||
coordinates,
|
|
||||||
gl_context_pointer: Some(ctxt),
|
|
||||||
native_display_pointer: Some(disp),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let wakeup = Box::new(EventLoopWakerInstance);
|
|
||||||
let shut_down_complete = Rc::new(Cell::new(false));
|
|
||||||
let callbacks = Box::new(HostCallbacks {
|
|
||||||
app,
|
|
||||||
ctxt,
|
|
||||||
surf,
|
|
||||||
disp,
|
|
||||||
landscape,
|
|
||||||
shut_down_complete: shut_down_complete.clone(),
|
|
||||||
history_update,
|
|
||||||
url_update,
|
|
||||||
keyboard,
|
|
||||||
});
|
|
||||||
info!("Starting servo");
|
|
||||||
simpleservo::init(opts, gl.gl_wrapper, wakeup, callbacks).expect("error initializing Servo");
|
|
||||||
|
|
||||||
let result = Box::new(ServoInstance {
|
|
||||||
scroll_state: ScrollState::TriggerUp,
|
|
||||||
scroll_scale: Scale::new(SCROLL_SCALE / hidpi),
|
|
||||||
shut_down_complete,
|
|
||||||
});
|
|
||||||
Box::into_raw(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn heartbeat_servo(_servo: *mut ServoInstance) {
|
|
||||||
let _ = call(|s| s.perform_updates());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn keyboard_servo(
|
|
||||||
_servo: *mut ServoInstance,
|
|
||||||
key_code: char,
|
|
||||||
key_type: MLKeyType,
|
|
||||||
) {
|
|
||||||
let key = match key_type {
|
|
||||||
MLKeyType::kCharacter => Key::Character([key_code].iter().collect()),
|
|
||||||
MLKeyType::kBackspace => Key::Backspace,
|
|
||||||
MLKeyType::kEnter => Key::Enter,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
// TODO: can the ML1 generate separate press and release events?
|
|
||||||
let key2 = key.clone();
|
|
||||||
let _ = call(move |s| s.key_down(key2));
|
|
||||||
let _ = call(move |s| s.key_up(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some magic numbers.
|
|
||||||
|
|
||||||
// How far does the cursor have to move for it to count as a drag rather than a click?
|
|
||||||
// (In device pixels squared, to avoid taking a sqrt when calculating move distance.)
|
|
||||||
const DRAG_CUTOFF_SQUARED: f32 = 900.0;
|
|
||||||
|
|
||||||
// How much should we scale scrolling by?
|
|
||||||
const SCROLL_SCALE: f32 = 3.0;
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn move_servo(servo: *mut ServoInstance, x: f32, y: f32) {
|
|
||||||
// Servo's cursor was moved
|
|
||||||
if let Some(servo) = servo.as_mut() {
|
|
||||||
let point = DevicePoint::new(x, y);
|
|
||||||
match servo.scroll_state {
|
|
||||||
ScrollState::TriggerUp => {
|
|
||||||
servo.scroll_state = ScrollState::TriggerUp;
|
|
||||||
let _ = call(|s| s.mouse_move(x, y));
|
|
||||||
},
|
|
||||||
ScrollState::TriggerDown(start)
|
|
||||||
if (start - point).square_length() < DRAG_CUTOFF_SQUARED =>
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
ScrollState::TriggerDown(start) => {
|
|
||||||
servo.scroll_state = ScrollState::TriggerDragging(start, point);
|
|
||||||
let _ = call(|s| s.mouse_move(x, y));
|
|
||||||
let delta = (point - start) * servo.scroll_scale;
|
|
||||||
let start = start.to_i32();
|
|
||||||
let _ = call(|s| s.scroll_start(delta.x, delta.y, start.x, start.y));
|
|
||||||
},
|
|
||||||
ScrollState::TriggerDragging(start, prev) => {
|
|
||||||
servo.scroll_state = ScrollState::TriggerDragging(start, point);
|
|
||||||
let _ = call(|s| s.mouse_move(x, y));
|
|
||||||
let delta = (point - prev) * servo.scroll_scale;
|
|
||||||
let start = start.to_i32();
|
|
||||||
let _ = call(|s| s.scroll(delta.x, delta.y, start.x, start.y));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn trigger_servo(servo: *mut ServoInstance, x: f32, y: f32, down: bool) {
|
|
||||||
// Servo was triggered
|
|
||||||
if let Some(servo) = servo.as_mut() {
|
|
||||||
let point = DevicePoint::new(x, y);
|
|
||||||
match servo.scroll_state {
|
|
||||||
ScrollState::TriggerUp if down => {
|
|
||||||
servo.scroll_state = ScrollState::TriggerDown(point);
|
|
||||||
let _ = call(|s| s.mouse_down(x, y, MouseButton::Left));
|
|
||||||
},
|
|
||||||
ScrollState::TriggerDown(start) if !down => {
|
|
||||||
servo.scroll_state = ScrollState::TriggerUp;
|
|
||||||
let _ = call(|s| s.mouse_up(start.x, start.y, MouseButton::Left));
|
|
||||||
let _ = call(|s| s.click(start.x as f32, start.y as f32));
|
|
||||||
let _ = call(|s| s.mouse_move(start.x, start.y));
|
|
||||||
},
|
|
||||||
ScrollState::TriggerDragging(start, prev) if !down => {
|
|
||||||
servo.scroll_state = ScrollState::TriggerUp;
|
|
||||||
let delta = (point - prev) * servo.scroll_scale;
|
|
||||||
let start = start.to_i32();
|
|
||||||
let _ = call(|s| s.scroll_end(delta.x, delta.y, start.x, start.y));
|
|
||||||
let _ = call(|s| s.mouse_up(x, y, MouseButton::Left));
|
|
||||||
},
|
|
||||||
_ => return,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn traverse_servo(_servo: *mut ServoInstance, delta: i32) {
|
|
||||||
// Traverse the session history
|
|
||||||
if delta == 0 {
|
|
||||||
let _ = call(|s| s.reload());
|
|
||||||
} else if delta < 0 {
|
|
||||||
let _ = call(|s| s.go_back());
|
|
||||||
} else {
|
|
||||||
let _ = call(|s| s.go_forward());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn navigate_servo(_servo: *mut ServoInstance, text: *const c_char) {
|
|
||||||
let text = CStr::from_ptr(text)
|
|
||||||
.to_str()
|
|
||||||
.expect("Failed to convert text to UTF-8");
|
|
||||||
let url = ServoUrl::parse(text).unwrap_or_else(|_| {
|
|
||||||
let mut search = ServoUrl::parse("https://duckduckgo.com")
|
|
||||||
.expect("Failed to parse search URL")
|
|
||||||
.into_url();
|
|
||||||
search.query_pairs_mut().append_pair("q", text);
|
|
||||||
ServoUrl::from_url(search)
|
|
||||||
});
|
|
||||||
let _ = call(|s| s.load_uri(url.as_str()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some magic numbers for shutdown
|
|
||||||
const SHUTDOWN_DURATION: Duration = Duration::from_secs(10);
|
|
||||||
const SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(100);
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn discard_servo(servo: *mut ServoInstance) {
|
|
||||||
if let Some(servo) = servo.as_mut() {
|
|
||||||
let servo = Box::from_raw(servo);
|
|
||||||
let finish = Instant::now() + SHUTDOWN_DURATION;
|
|
||||||
let _ = call(|s| s.request_shutdown());
|
|
||||||
while !servo.shut_down_complete.get() {
|
|
||||||
let _ = call(|s| s.perform_updates());
|
|
||||||
if Instant::now() > finish {
|
|
||||||
warn!("Incomplete shutdown.");
|
|
||||||
}
|
|
||||||
thread::sleep(SHUTDOWN_POLL_INTERVAL);
|
|
||||||
}
|
|
||||||
deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HostCallbacks {
|
|
||||||
ctxt: EGLContext,
|
|
||||||
surf: EGLSurface,
|
|
||||||
disp: EGLDisplay,
|
|
||||||
landscape: bool,
|
|
||||||
shut_down_complete: Rc<Cell<bool>>,
|
|
||||||
history_update: MLHistoryUpdate,
|
|
||||||
url_update: MLURLUpdate,
|
|
||||||
app: MLApp,
|
|
||||||
keyboard: MLKeyboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HostTrait for HostCallbacks {
|
|
||||||
fn flush(&self) {
|
|
||||||
// Immersive and landscape apps have different requirements for who calls SwapBuffers.
|
|
||||||
if self.landscape {
|
|
||||||
SwapBuffers(self.disp, self.surf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_current(&self) {
|
|
||||||
MakeCurrent(self.disp, self.surf, self.surf, self.ctxt);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_alert(&self, message: String, _trusted: bool) {
|
|
||||||
warn!("Prompt Alert: {}", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_ok_cancel(&self, message: String, _trusted: bool) -> PromptResult {
|
|
||||||
warn!("Prompt not implemented. Cancelled. {}", message);
|
|
||||||
PromptResult::Secondary
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_yes_no(&self, message: String, _trusted: bool) -> PromptResult {
|
|
||||||
warn!("Prompt not implemented. Cancelled. {}", message);
|
|
||||||
PromptResult::Secondary
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_input(&self, message: String, default: String, _trusted: bool) -> Option<String> {
|
|
||||||
warn!("Input prompt not implemented. {}", message);
|
|
||||||
Some(default)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_load_started(&self) {}
|
|
||||||
fn on_load_ended(&self) {}
|
|
||||||
fn on_title_changed(&self, _title: String) {}
|
|
||||||
fn on_allow_navigation(&self, _url: String) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn on_url_changed(&self, url: String) {
|
|
||||||
if let Ok(cstr) = CString::new(url.as_str()) {
|
|
||||||
if let Some(url_update) = self.url_update.0 {
|
|
||||||
url_update(self.app, cstr.as_ptr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool) {
|
|
||||||
if let Some(history_update) = self.history_update.0 {
|
|
||||||
history_update(self.app, can_go_back, can_go_forward);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_animating_changed(&self, _animating: bool) {}
|
|
||||||
|
|
||||||
fn on_shutdown_complete(&self) {
|
|
||||||
self.shut_down_complete.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_ime_show(
|
|
||||||
&self,
|
|
||||||
_input_type: InputMethodType,
|
|
||||||
_text: Option<(String, i32)>,
|
|
||||||
_multiline: bool,
|
|
||||||
_bounds: DeviceIntRect,
|
|
||||||
) {
|
|
||||||
if let Some(keyboard) = self.keyboard.0 {
|
|
||||||
keyboard(self.app, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_ime_hide(&self) {
|
|
||||||
if let Some(keyboard) = self.keyboard.0 {
|
|
||||||
keyboard(self.app, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_clipboard_contents(&self) -> Option<String> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_clipboard_contents(&self, _contents: String) {}
|
|
||||||
|
|
||||||
fn on_devtools_started(&self, port: Result<u16, ()>) {
|
|
||||||
match port {
|
|
||||||
Ok(p) => info!("Devtools Server running on port {}", p),
|
|
||||||
Err(()) => error!("Error running Devtools server"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ServoInstance {
|
|
||||||
scroll_state: ScrollState,
|
|
||||||
scroll_scale: Scale<f32, DevicePixel, LayoutPixel>,
|
|
||||||
shut_down_complete: Rc<Cell<bool>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum ScrollState {
|
|
||||||
TriggerUp,
|
|
||||||
TriggerDown(DevicePoint),
|
|
||||||
TriggerDragging(DevicePoint, DevicePoint),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventLoopWakerInstance;
|
|
||||||
|
|
||||||
impl EventLoopWaker for EventLoopWakerInstance {
|
|
||||||
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
|
||||||
Box::new(EventLoopWakerInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wake(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl log::Log for MLLogger {
|
|
||||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
||||||
metadata.level() <= LOG_LEVEL
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
|
||||||
if let Some(log) = self.0 {
|
|
||||||
let lvl = match record.level() {
|
|
||||||
log::Level::Error => MLLogLevel::Error,
|
|
||||||
log::Level::Warn => MLLogLevel::Warning,
|
|
||||||
log::Level::Info => MLLogLevel::Info,
|
|
||||||
log::Level::Debug => MLLogLevel::Debug,
|
|
||||||
log::Level::Trace => MLLogLevel::Verbose,
|
|
||||||
};
|
|
||||||
let mut msg = SmallVec::<[u8; 128]>::new();
|
|
||||||
write!(msg, "{}\0", record.args()).unwrap();
|
|
||||||
log(lvl, &msg[0] as *const _ as *const _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redirect_stdout_to_log(logger: MLLogger) {
|
|
||||||
let log = match logger.0 {
|
|
||||||
None => return,
|
|
||||||
Some(log) => log,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The first step is to redirect stdout and stderr to the logs.
|
|
||||||
// We redirect stdout and stderr to a custom descriptor.
|
|
||||||
let mut pfd: [c_int; 2] = [0, 0];
|
|
||||||
unsafe {
|
|
||||||
pipe(pfd.as_mut_ptr());
|
|
||||||
dup2(pfd[1], 1);
|
|
||||||
dup2(pfd[1], 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
let descriptor = pfd[0];
|
|
||||||
|
|
||||||
// Then we spawn a thread whose only job is to read from the other side of the
|
|
||||||
// pipe and redirect to the logs.
|
|
||||||
let _detached = thread::spawn(move || {
|
|
||||||
const BUF_LENGTH: usize = 512;
|
|
||||||
let mut buf = vec![b'\0' as c_char; BUF_LENGTH];
|
|
||||||
|
|
||||||
// Always keep at least one null terminator
|
|
||||||
const BUF_AVAILABLE: usize = BUF_LENGTH - 1;
|
|
||||||
let buf = &mut buf[..BUF_AVAILABLE];
|
|
||||||
|
|
||||||
let mut cursor = 0_usize;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let result = {
|
|
||||||
let read_into = &mut buf[cursor..];
|
|
||||||
unsafe {
|
|
||||||
read(
|
|
||||||
descriptor,
|
|
||||||
read_into.as_mut_ptr() as *mut _,
|
|
||||||
read_into.len(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let end = if result == 0 {
|
|
||||||
return;
|
|
||||||
} else if result < 0 {
|
|
||||||
log(
|
|
||||||
MLLogLevel::Error,
|
|
||||||
b"error in log thread; closing\0".as_ptr() as *const _,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
result as usize + cursor
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only modify the portion of the buffer that contains real data.
|
|
||||||
let buf = &mut buf[0..end];
|
|
||||||
|
|
||||||
if let Some(last_newline_pos) = buf.iter().rposition(|&c| c == b'\n' as c_char) {
|
|
||||||
buf[last_newline_pos] = b'\0' as c_char;
|
|
||||||
log(MLLogLevel::Info, buf.as_ptr());
|
|
||||||
if last_newline_pos < buf.len() - 1 {
|
|
||||||
let pos_after_newline = last_newline_pos + 1;
|
|
||||||
let len_not_logged_yet = buf[pos_after_newline..].len();
|
|
||||||
for j in 0..len_not_logged_yet as usize {
|
|
||||||
buf[j] = buf[pos_after_newline + j];
|
|
||||||
}
|
|
||||||
cursor = len_not_logged_yet;
|
|
||||||
} else {
|
|
||||||
cursor = 0;
|
|
||||||
}
|
|
||||||
} else if end == BUF_AVAILABLE {
|
|
||||||
// No newline found but the buffer is full, flush it anyway.
|
|
||||||
// `buf.as_ptr()` is null-terminated by BUF_LENGTH being 1 less than BUF_AVAILABLE.
|
|
||||||
log(MLLogLevel::Info, buf.as_ptr());
|
|
||||||
cursor = 0;
|
|
||||||
} else {
|
|
||||||
cursor = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue