use std::{
mem,
os::raw::{c_int, c_uint},
sync::Weak,
time::{Duration, Instant},
};
use gtk::glib::{self, ffi, translate::ToGlibPtr};
use super::PumpState;
#[repr(C)]
struct WorkSource {
source: ffi::GSource,
source_state: *mut SourceState,
}
struct SourceState {
state: Weak<PumpState>,
delayed_work_time: Option<Instant>,
wakeup_pipe_read: c_int,
wakeup_pipe_write: c_int,
wakeup_gpollfd: Box<ffi::GPollFD>,
}
pub(super) struct PlatformPump {
work_source: *mut ffi::GSource,
source_state: Box<SourceState>,
}
unsafe impl Send for PlatformPump {}
impl PlatformPump {
pub(super) fn new(state: Weak<PumpState>) -> Self {
let context = glib::MainContext::default();
let mut fds = [0; 2];
let ret = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(ret, 0, "failed to create CEF message pump wakeup pipe");
let mut source_state = Box::new(SourceState {
state,
delayed_work_time: None,
wakeup_pipe_read: fds[0],
wakeup_pipe_write: fds[1],
wakeup_gpollfd: Box::new(ffi::GPollFD {
fd: fds[0],
events: ffi::G_IO_IN as _,
revents: 0,
}),
});
let work_source = unsafe {
let source = ffi::g_source_new(
&raw mut WORK_SOURCE_FUNCS,
mem::size_of::<WorkSource>() as c_uint,
);
assert!(
!source.is_null(),
"failed to create CEF message pump GSource"
);
(*(source as *mut WorkSource)).source_state = &mut *source_state;
ffi::g_source_add_poll(source, &mut *source_state.wakeup_gpollfd);
ffi::g_source_set_priority(source, ffi::G_PRIORITY_DEFAULT_IDLE);
ffi::g_source_set_can_recurse(source, ffi::GTRUE);
ffi::g_source_attach(source, context.to_glib_none().0);
source
};
Self {
work_source,
source_state,
}
}
pub(super) fn on_schedule_message_pump_work(&mut self, delay_ms: i64) {
let written = retry_eintr(|| unsafe {
libc::write(
self.source_state.wakeup_pipe_write,
(&delay_ms as *const i64).cast(),
mem::size_of::<i64>(),
)
});
if written != mem::size_of::<i64>() as isize {
log::error!("could not write to the CEF message pump wakeup pipe");
}
}
pub(super) fn set_timer(&mut self, delay_ms: i64) {
debug_assert!(delay_ms > 0);
let now = Instant::now();
self.source_state.delayed_work_time = Some(now + Duration::from_millis(delay_ms as u64));
}
pub(super) fn kill_timer(&mut self) {
self.source_state.delayed_work_time = None;
}
pub(super) fn is_timer_pending(&self) -> bool {
get_time_interval_milliseconds(self.source_state.delayed_work_time) > 0
}
}
impl Drop for PlatformPump {
fn drop(&mut self) {
unsafe {
ffi::g_source_destroy(self.work_source);
ffi::g_source_unref(self.work_source);
libc::close(self.source_state.wakeup_pipe_read);
libc::close(self.source_state.wakeup_pipe_write);
}
}
}
fn get_time_interval_milliseconds(from: Option<Instant>) -> c_int {
let Some(from) = from else {
return -1;
};
let now = Instant::now();
let delay = from
.checked_duration_since(now)
.map(|duration| (duration.as_secs_f64() * 1000.0).ceil() as c_int)
.unwrap_or(-1);
if delay < 0 { 0 } else { delay }
}
fn retry_eintr<F>(mut f: F) -> isize
where
F: FnMut() -> isize,
{
loop {
let result = f();
if result != -1 || std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR) {
return result;
}
}
}
unsafe fn source_state(source: *mut ffi::GSource) -> *mut SourceState {
unsafe { (*(source as *mut WorkSource)).source_state }
}
unsafe fn handle_prepare(source_state: *mut SourceState) -> c_int {
let delayed_work_time = unsafe { (*source_state).delayed_work_time };
get_time_interval_milliseconds(delayed_work_time)
}
unsafe fn handle_check(source_state: *mut SourceState) -> bool {
let have_wakeup = {
let wakeup_gpollfd = unsafe { &*(*source_state).wakeup_gpollfd };
(wakeup_gpollfd.revents & ffi::G_IO_IN as u16) != 0
};
if have_wakeup {
let mut delay_ms = [0_i64; 2];
let num_bytes = retry_eintr(|| unsafe {
libc::read(
(*source_state).wakeup_pipe_read,
delay_ms.as_mut_ptr().cast(),
mem::size_of::<i64>() * 2,
)
});
if num_bytes < mem::size_of::<i64>() as isize {
log::error!("error reading from the CEF message pump wakeup pipe");
}
if num_bytes == mem::size_of::<i64>() as isize
&& let Some(state) = unsafe { (*source_state).state.upgrade() }
{
state.on_schedule_work(delay_ms[0]);
}
if num_bytes == (mem::size_of::<i64>() * 2) as isize
&& let Some(state) = unsafe { (*source_state).state.upgrade() }
{
state.on_schedule_work(delay_ms[1]);
}
}
let delayed_work_time = unsafe { (*source_state).delayed_work_time };
if get_time_interval_milliseconds(delayed_work_time) == 0 {
return true;
}
false
}
unsafe fn handle_dispatch(source_state: *mut SourceState) {
if let Some(state) = unsafe { (*source_state).state.upgrade() } {
state.on_timer_timeout();
}
}
unsafe extern "C" fn work_source_prepare(
source: *mut ffi::GSource,
timeout_ms: *mut c_int,
) -> ffi::gboolean {
if !timeout_ms.is_null() {
let source_state = unsafe { source_state(source) };
unsafe { *timeout_ms = handle_prepare(source_state) };
}
ffi::GFALSE
}
unsafe extern "C" fn work_source_check(source: *mut ffi::GSource) -> ffi::gboolean {
let source_state = unsafe { source_state(source) };
if unsafe { handle_check(source_state) } {
ffi::GTRUE
} else {
ffi::GFALSE
}
}
unsafe extern "C" fn work_source_dispatch(
source: *mut ffi::GSource,
_callback: ffi::GSourceFunc,
_user_data: ffi::gpointer,
) -> ffi::gboolean {
let source_state = unsafe { source_state(source) };
unsafe { handle_dispatch(source_state) };
ffi::GTRUE
}
static mut WORK_SOURCE_FUNCS: ffi::GSourceFuncs = ffi::GSourceFuncs {
prepare: Some(work_source_prepare),
check: Some(work_source_check),
dispatch: Some(work_source_dispatch),
finalize: None,
closure_callback: None,
closure_marshal: None,
};