use std::ffi::{c_char, c_int, c_void, CString};
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
use super::abi::{RawStub, TclPlatStubs, TclStubs, TclTime};
use super::generated::{TCL_NAMES, TCL_PLAT_NAMES};
use super::trace::{record, Table};
pub const TCL_DONT_WAIT: c_int = 1 << 1;
pub const TCL_WINDOW_EVENTS: c_int = 1 << 2;
pub const TCL_FILE_EVENTS: c_int = 1 << 3;
pub const TCL_TIMER_EVENTS: c_int = 1 << 4;
pub const TCL_IDLE_EVENTS: c_int = 1 << 5;
pub const TCL_ALL_EVENTS: c_int = !TCL_DONT_WAIT;
pub const TCL_QUEUE_TAIL: c_int = 0;
pub const TCL_QUEUE_HEAD: c_int = 1;
pub const TCL_QUEUE_MARK: c_int = 2;
pub const TCL_QUEUE_ALERT_IF_EMPTY: c_int = 4;
pub const TCL_SERVICE_NONE: c_int = 0;
pub const TCL_SERVICE_ALL: c_int = 1;
pub const TCL_READABLE: c_int = 1 << 1;
pub const TCL_WRITABLE: c_int = 1 << 2;
pub const TCL_EXCEPTION: c_int = 1 << 3;
#[repr(C)]
pub struct TclEvent {
pub proc_: Option<TclEventProc>,
pub next_ptr: *mut TclEvent,
}
pub type TclEventProc = unsafe extern "C" fn(*mut TclEvent, c_int) -> c_int;
pub type TclEventSetupProc = unsafe extern "C" fn(*mut c_void, c_int);
pub type TclEventCheckProc = unsafe extern "C" fn(*mut c_void, c_int);
pub type TclEventDeleteProc = unsafe extern "C" fn(*mut TclEvent, *mut c_void) -> c_int;
pub type TclTimerProc = unsafe extern "C" fn(*mut c_void);
pub type TclIdleProc = unsafe extern "C" fn(*mut c_void);
pub type TclFileProc = unsafe extern "C" fn(*mut c_void, c_int);
type CFRunLoopRef = *mut c_void;
type CFRunLoopSourceRef = *mut c_void;
type CFRunLoopTimerRef = *mut c_void;
type CFFileDescriptorRef = *mut c_void;
type CFStringRef = *const c_void;
type CFAllocatorRef = *const c_void;
type CFIndex = isize;
type CFOptionFlags = usize;
type CFHashCode = usize;
type CFTimeInterval = f64;
type CFAbsoluteTime = f64;
#[repr(C)]
struct CFRunLoopSourceContext {
version: CFIndex,
info: *mut c_void,
retain: Option<unsafe extern "C" fn(*const c_void) -> *const c_void>,
release: Option<unsafe extern "C" fn(*const c_void)>,
copy_description: Option<unsafe extern "C" fn(*const c_void) -> CFStringRef>,
equal: Option<unsafe extern "C" fn(*const c_void, *const c_void) -> u8>,
hash: Option<unsafe extern "C" fn(*const c_void) -> CFHashCode>,
schedule: Option<unsafe extern "C" fn(*mut c_void, CFRunLoopRef, CFStringRef)>,
cancel: Option<unsafe extern "C" fn(*mut c_void, CFRunLoopRef, CFStringRef)>,
perform: Option<unsafe extern "C" fn(*mut c_void)>,
}
#[repr(C)]
struct CFContext {
version: CFIndex,
info: *mut c_void,
retain: Option<unsafe extern "C" fn(*const c_void) -> *const c_void>,
release: Option<unsafe extern "C" fn(*const c_void)>,
copy_description: Option<unsafe extern "C" fn(*const c_void) -> CFStringRef>,
}
const K_CF_RUN_LOOP_RUN_FINISHED: i32 = 1;
const K_CF_RUN_LOOP_RUN_STOPPED: i32 = 2;
const K_CF_RUN_LOOP_RUN_TIMED_OUT: i32 = 3;
const K_CF_RUN_LOOP_RUN_HANDLED_SOURCE: i32 = 4;
const K_CF_FD_READ: CFOptionFlags = 1 << 0;
const K_CF_FD_WRITE: CFOptionFlags = 1 << 1;
const CF_TIMEINTERVAL_FOREVER: CFTimeInterval = 5.05e8;
const TCL_EVENTS_ONLY_RUN_LOOP_MODE: &str = "com.tcltk.tclEventsOnlyRunLoopMode";
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
static kCFRunLoopDefaultMode: CFStringRef;
static kCFRunLoopCommonModes: CFStringRef;
fn CFRunLoopGetCurrent() -> CFRunLoopRef;
fn CFRunLoopGetMain() -> CFRunLoopRef;
fn CFRunLoopRunInMode(mode: CFStringRef, seconds: CFTimeInterval, return_after: u8) -> i32;
fn CFRunLoopWakeUp(rl: CFRunLoopRef);
fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFStringRef);
fn CFRunLoopRemoveSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFStringRef);
fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFStringRef);
fn CFRunLoopSourceCreate(
allocator: CFAllocatorRef,
order: CFIndex,
context: *mut CFRunLoopSourceContext,
) -> CFRunLoopSourceRef;
fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
fn CFRunLoopTimerCreate(
allocator: CFAllocatorRef,
fire_date: CFAbsoluteTime,
interval: CFTimeInterval,
flags: CFOptionFlags,
order: CFIndex,
callout: unsafe extern "C" fn(CFRunLoopTimerRef, *mut c_void),
context: *mut CFContext,
) -> CFRunLoopTimerRef;
fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, at: CFAbsoluteTime);
fn CFRunLoopTimerGetNextFireDate(timer: CFRunLoopTimerRef) -> CFAbsoluteTime;
fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
fn CFStringCreateWithCString(
allocator: CFAllocatorRef,
cstr: *const c_char,
encoding: u32,
) -> CFStringRef;
fn CFRelease(cf: *const c_void);
fn CFFileDescriptorCreate(
allocator: CFAllocatorRef,
fd: c_int,
close_on_invalidate: u8,
callout: unsafe extern "C" fn(CFFileDescriptorRef, CFOptionFlags, *mut c_void),
context: *mut CFContext,
) -> CFFileDescriptorRef;
fn CFFileDescriptorEnableCallBacks(f: CFFileDescriptorRef, types: CFOptionFlags);
fn CFFileDescriptorDisableCallBacks(f: CFFileDescriptorRef, types: CFOptionFlags);
fn CFFileDescriptorInvalidate(f: CFFileDescriptorRef);
fn CFFileDescriptorCreateRunLoopSource(
allocator: CFAllocatorRef,
f: CFFileDescriptorRef,
order: CFIndex,
) -> CFRunLoopSourceRef;
}
const K_CF_STRING_ENCODING_UTF8: u32 = 0x0800_0100;
#[derive(Clone, Copy)]
struct EventSource {
setup: Option<TclEventSetupProc>,
check: Option<TclEventCheckProc>,
client_data: *mut c_void,
}
impl EventSource {
fn same(&self, other: &EventSource) -> bool {
self.setup.map(|f| f as usize) == other.setup.map(|f| f as usize)
&& self.check.map(|f| f as usize) == other.check.map(|f| f as usize)
&& self.client_data == other.client_data
}
}
struct TimerHandler {
time: TclTime,
proc_: TclTimerProc,
client_data: *mut c_void,
token: c_int,
}
struct IdleHandler {
proc_: TclIdleProc,
client_data: *mut c_void,
generation: c_int,
}
struct FileHandler {
fd: c_int,
mask: c_int,
ready_mask: c_int,
event_queued: bool,
proc_: TclFileProc,
client_data: *mut c_void,
cf_fd: CFFileDescriptorRef,
cf_source: CFRunLoopSourceRef,
}
#[repr(C)]
struct FileHandlerEvent {
header: TclEvent,
fd: c_int,
}
pub struct Notifier {
first_event: *mut TclEvent,
last_event: *mut TclEvent,
marker_event: *mut TclEvent,
event_count: isize,
service_mode: c_int,
block_time_set: bool,
block_time: TclTime,
in_traversal: bool,
sources: Vec<EventSource>,
thread_id: *mut c_void,
timers: Vec<TimerHandler>,
last_timer_id: c_int,
timer_pending: bool,
idles: Vec<IdleHandler>,
idle_generation: c_int,
timer_source_registered: bool,
run_loop: CFRunLoopRef,
run_loop_source: CFRunLoopSourceRef,
run_loop_timer: CFRunLoopTimerRef,
events_only_mode: CFStringRef,
run_loop_source_performed: bool,
run_loop_running: bool,
sleeping: bool,
files: Vec<FileHandler>,
}
thread_local! {
static NOTIFIER: std::cell::Cell<*mut Notifier> = const { std::cell::Cell::new(ptr::null_mut()) };
}
pub unsafe fn state() -> &'static mut Notifier {
let p = NOTIFIER.with(|c| c.get());
if !p.is_null() {
return &mut *p;
}
let fresh = Box::into_raw(Box::new(Notifier {
first_event: ptr::null_mut(),
last_event: ptr::null_mut(),
marker_event: ptr::null_mut(),
event_count: 0,
service_mode: TCL_SERVICE_NONE,
block_time_set: false,
block_time: TclTime { sec: 0, usec: 0 },
in_traversal: false,
sources: Vec::new(),
thread_id: libc::pthread_self() as *mut c_void,
timers: Vec::new(),
last_timer_id: 0,
timer_pending: false,
idles: Vec::new(),
idle_generation: 0,
timer_source_registered: false,
run_loop: ptr::null_mut(),
run_loop_source: ptr::null_mut(),
run_loop_timer: ptr::null_mut(),
events_only_mode: ptr::null(),
run_loop_source_performed: false,
run_loop_running: false,
sleeping: false,
files: Vec::new(),
}));
NOTIFIER.with(|c| c.set(fresh));
init_platform(&mut *fresh);
&mut *fresh
}
pub fn on_main_run_loop() -> bool {
unsafe { CFRunLoopGetCurrent() == CFRunLoopGetMain() }
}
unsafe fn init_platform(n: &mut Notifier) {
let run_loop = CFRunLoopGetCurrent();
let mode = CString::new(TCL_EVENTS_ONLY_RUN_LOOP_MODE).expect("mode name has no NUL");
n.events_only_mode =
CFStringCreateWithCString(ptr::null(), mode.as_ptr(), K_CF_STRING_ENCODING_UTF8);
let mut context = CFRunLoopSourceContext {
version: 0,
info: n as *mut Notifier as *mut c_void,
retain: None,
release: None,
copy_description: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: Some(queue_file_events),
};
let source = CFRunLoopSourceCreate(ptr::null(), CFIndex::MIN, &mut context);
assert!(!source.is_null(), "could not create CFRunLoopSource");
CFRunLoopAddSource(run_loop, source, kCFRunLoopCommonModes);
CFRunLoopAddSource(run_loop, source, n.events_only_mode);
n.run_loop = run_loop;
n.run_loop_source = source;
}
unsafe extern "C" fn timer_wake_up(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe fn set_timer_impl(time: *const TclTime) {
let n = state();
if n.run_loop_timer.is_null() {
return;
}
let wait = if time.is_null() {
CF_TIMEINTERVAL_FOREVER
} else if (*time).sec != 0 || (*time).usec != 0 {
(*time).sec as f64 + 1.0e-6 * (*time).usec as f64
} else {
0.0
};
CFRunLoopTimerSetNextFireDate(n.run_loop_timer, CFAbsoluteTimeGetCurrent() + wait);
}
unsafe fn service_mode_hook_impl(mode: c_int) {
let n = state();
if mode == TCL_SERVICE_ALL && n.run_loop_timer.is_null() {
assert!(
!n.run_loop.is_null(),
"Tcl_ServiceModeHook: notifier not initialized"
);
let mut context = CFContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copy_description: None,
};
let timer = CFRunLoopTimerCreate(
ptr::null(),
CFAbsoluteTimeGetCurrent() + CF_TIMEINTERVAL_FOREVER,
CF_TIMEINTERVAL_FOREVER,
0,
0,
timer_wake_up,
&mut context,
);
if !timer.is_null() {
CFRunLoopAddTimer(n.run_loop, timer, kCFRunLoopCommonModes);
n.run_loop_timer = timer;
}
}
}
unsafe fn wait_for_event_impl(time: *const TclTime) -> c_int {
let n = state();
assert!(
!n.run_loop.is_null(),
"Tcl_WaitForEvent: notifier not initialized"
);
let mut wait_time = CF_TIMEINTERVAL_FOREVER;
if !time.is_null() {
if (*time).sec != 0 || (*time).usec != 0 {
wait_time = (*time).sec as f64 + 1.0e-6 * (*time).usec as f64;
} else {
wait_time = if n.run_loop_running { 0.0 } else { 0.0001 };
}
}
n.run_loop_source_performed = false;
let was_running = n.run_loop_running;
n.run_loop_running = true;
let mode = if was_running {
n.events_only_mode
} else {
kCFRunLoopDefaultMode
};
let status = CFRunLoopRunInMode(mode, wait_time, 1);
let n = state();
n.run_loop_running = was_running;
match status {
K_CF_RUN_LOOP_RUN_FINISHED => panic!("Tcl_WaitForEvent: CFRunLoop finished"),
K_CF_RUN_LOOP_RUN_TIMED_OUT => {
queue_file_events(ptr::null_mut());
0
}
K_CF_RUN_LOOP_RUN_STOPPED | K_CF_RUN_LOOP_RUN_HANDLED_SOURCE => {
if state().run_loop_source_performed {
0
} else {
1
}
}
_ => -1,
}
}
unsafe fn alert_impl(n: &mut Notifier) {
if !n.run_loop.is_null() {
CFRunLoopSourceSignal(n.run_loop_source);
CFRunLoopWakeUp(n.run_loop);
}
}
unsafe extern "C" fn queue_file_events(_info: *mut c_void) {
let n = state();
n.run_loop_source_performed = true;
let ready: Vec<(usize, c_int)> = n
.files
.iter()
.enumerate()
.filter(|(_, f)| f.ready_mask != 0)
.map(|(i, f)| (i, f.fd))
.collect();
for (i, fd) in ready {
if n.files[i].ready_mask == 0 {
continue;
}
if !n.files[i].event_queued {
let ev = libc::malloc(std::mem::size_of::<FileHandlerEvent>()) as *mut FileHandlerEvent;
assert!(!ev.is_null(), "out of memory queueing a file event");
ptr::write(
ev,
FileHandlerEvent {
header: TclEvent {
proc_: Some(file_handler_event_proc),
next_ptr: ptr::null_mut(),
},
fd,
},
);
n.files[i].event_queued = true;
queue_event(n, ev as *mut TclEvent, TCL_QUEUE_TAIL);
}
}
}
unsafe extern "C" fn file_ready(f: CFFileDescriptorRef, types: CFOptionFlags, _info: *mut c_void) {
let n = state();
let Some(i) = n.files.iter().position(|h| h.cf_fd == f) else {
return;
};
let mut mask = 0;
if types & K_CF_FD_READ != 0 {
mask |= TCL_READABLE;
}
if types & K_CF_FD_WRITE != 0 {
mask |= TCL_WRITABLE;
}
n.files[i].ready_mask |= mask & n.files[i].mask;
alert_impl(n);
}
unsafe extern "C" fn file_handler_event_proc(ev: *mut TclEvent, flags: c_int) -> c_int {
if flags & TCL_FILE_EVENTS == 0 {
return 0;
}
let fd = (*(ev as *mut FileHandlerEvent)).fd;
let n = state();
let Some(i) = n.files.iter().position(|h| h.fd == fd) else {
return 1;
};
n.files[i].event_queued = false;
let mask = n.files[i].ready_mask & n.files[i].mask;
n.files[i].ready_mask = 0;
if mask != 0 {
let (proc_, client_data) = (n.files[i].proc_, n.files[i].client_data);
rearm(&n.files[i]);
proc_(client_data, mask);
} else {
rearm(&n.files[i]);
}
1
}
unsafe fn rearm(h: &FileHandler) {
let mut types = 0;
if h.mask & TCL_READABLE != 0 {
types |= K_CF_FD_READ;
}
if h.mask & TCL_WRITABLE != 0 {
types |= K_CF_FD_WRITE;
}
if types != 0 {
CFFileDescriptorEnableCallBacks(h.cf_fd, types);
}
}
unsafe fn queue_event(n: &mut Notifier, ev: *mut TclEvent, position: c_int) -> bool {
match position & 3 {
TCL_QUEUE_TAIL => {
(*ev).next_ptr = ptr::null_mut();
if n.first_event.is_null() {
n.first_event = ev;
} else {
(*n.last_event).next_ptr = ev;
}
n.last_event = ev;
}
TCL_QUEUE_HEAD => {
(*ev).next_ptr = n.first_event;
if n.first_event.is_null() {
n.last_event = ev;
}
n.first_event = ev;
}
TCL_QUEUE_MARK => {
if n.marker_event.is_null() {
(*ev).next_ptr = n.first_event;
n.first_event = ev;
} else {
(*ev).next_ptr = (*n.marker_event).next_ptr;
(*n.marker_event).next_ptr = ev;
}
n.marker_event = ev;
if (*ev).next_ptr.is_null() {
n.last_event = ev;
}
}
_ => return false,
}
let was_empty = position & TCL_QUEUE_ALERT_IF_EMPTY != 0 && n.event_count <= 0;
n.event_count += 1;
was_empty
}
unsafe fn service_event(mut flags: c_int) -> c_int {
if flags & TCL_ALL_EVENTS == 0 {
flags |= TCL_ALL_EVENTS;
}
let n = state();
let mut ev = n.first_event;
while !ev.is_null() {
let next = (*ev).next_ptr;
let Some(proc_) = (*ev).proc_ else {
ev = next;
continue;
};
(*ev).proc_ = None;
let saved = n.event_count;
n.event_count = 0;
let handled = proc_(ev, flags);
let n = state();
n.event_count += saved;
if handled == 0 {
(*ev).proc_ = Some(proc_);
ev = (*ev).next_ptr;
continue;
}
let mut unlinked = false;
if n.first_event == ev {
n.first_event = (*ev).next_ptr;
if (*ev).next_ptr.is_null() {
n.last_event = ptr::null_mut();
}
if n.marker_event == ev {
n.marker_event = ptr::null_mut();
}
unlinked = true;
} else {
let mut prev = n.first_event;
while !prev.is_null() && (*prev).next_ptr != ev {
prev = (*prev).next_ptr;
}
if !prev.is_null() {
(*prev).next_ptr = (*ev).next_ptr;
if (*ev).next_ptr.is_null() {
n.last_event = prev;
}
if n.marker_event == ev {
n.marker_event = prev;
}
unlinked = true;
}
}
if unlinked {
libc::free(ev as *mut c_void);
n.event_count -= 1;
}
return 1;
}
0
}
unsafe fn source_snapshot(n: &Notifier) -> Vec<EventSource> {
n.sources.clone()
}
unsafe fn setup_sources(flags: c_int) {
let n = state();
n.in_traversal = true;
for s in source_snapshot(n) {
let n = state();
if !n.sources.iter().any(|live| live.same(&s)) {
continue;
}
if let Some(f) = s.setup {
f(s.client_data, flags);
}
}
state().in_traversal = false;
}
unsafe fn check_sources(flags: c_int) {
let n = state();
for s in source_snapshot(n) {
let n = state();
if !n.sources.iter().any(|live| live.same(&s)) {
continue;
}
if let Some(f) = s.check {
f(s.client_data, flags);
}
}
}
unsafe fn do_one_event(mut flags: c_int) -> c_int {
let mut result = 0;
if flags & TCL_ALL_EVENTS == 0 {
flags |= TCL_ALL_EVENTS;
}
let old_mode = state().service_mode;
state().service_mode = TCL_SERVICE_NONE;
loop {
let idle_only = flags & TCL_ALL_EVENTS == TCL_IDLE_EVENTS;
if idle_only {
flags = TCL_IDLE_EVENTS | TCL_DONT_WAIT;
} else {
if service_event(flags) != 0 {
result = 1;
break;
}
let n = state();
if flags & TCL_DONT_WAIT != 0 {
n.block_time = TclTime { sec: 0, usec: 0 };
n.block_time_set = true;
} else {
n.block_time_set = false;
}
setup_sources(flags);
let n = state();
let wait_for = if flags & TCL_DONT_WAIT != 0 || n.block_time_set {
&n.block_time as *const TclTime
} else {
ptr::null()
};
result = wait_for_event_impl(wait_for);
if result < 0 {
result = 0;
break;
}
check_sources(flags);
if service_event(flags) != 0 {
result = 1;
break;
}
}
if flags & TCL_IDLE_EVENTS != 0 && service_idle() != 0 {
result = 1;
break;
}
if flags & TCL_DONT_WAIT != 0 {
break;
}
if result != 0 {
break;
}
}
state().service_mode = old_mode;
result
}
unsafe fn service_all() -> c_int {
let mut result = 0;
let n = state();
if n.service_mode == TCL_SERVICE_NONE {
return 0;
}
n.service_mode = TCL_SERVICE_NONE;
n.in_traversal = true;
n.block_time_set = false;
setup_sources_all();
check_sources(TCL_ALL_EVENTS);
while service_event(0) != 0 {
result = 1;
}
if service_idle() != 0 {
result = 1;
}
let n = state();
if n.block_time_set {
let t = TclTime {
sec: n.block_time.sec,
usec: n.block_time.usec,
};
set_timer_impl(&t);
} else {
set_timer_impl(ptr::null());
}
let n = state();
n.in_traversal = false;
n.service_mode = TCL_SERVICE_ALL;
result
}
unsafe fn setup_sources_all() {
let n = state();
for s in source_snapshot(n) {
let n = state();
if !n.sources.iter().any(|live| live.same(&s)) {
continue;
}
if let Some(f) = s.setup {
f(s.client_data, TCL_ALL_EVENTS);
}
}
}
unsafe fn set_max_block_time(time: *const TclTime) {
let n = state();
if !n.block_time_set
|| (*time).sec < n.block_time.sec
|| ((*time).sec == n.block_time.sec && (*time).usec < n.block_time.usec)
{
n.block_time = TclTime {
sec: (*time).sec,
usec: (*time).usec,
};
n.block_time_set = true;
}
if !n.in_traversal {
let t = TclTime {
sec: n.block_time.sec,
usec: n.block_time.usec,
};
set_timer_impl(&t);
}
}
unsafe fn init_timer() -> &'static mut Notifier {
let n = state();
if !n.timer_source_registered {
n.timer_source_registered = true;
n.sources.push(EventSource {
setup: Some(timer_setup_proc),
check: Some(timer_check_proc),
client_data: ptr::null_mut(),
});
}
n
}
unsafe fn now() -> TclTime {
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
libc::gettimeofday(&mut tv, ptr::null_mut());
TclTime {
sec: tv.tv_sec,
usec: tv.tv_usec as std::ffi::c_long,
}
}
fn time_before(a: &TclTime, b: &TclTime) -> bool {
a.sec < b.sec || (a.sec == b.sec && a.usec < b.usec)
}
fn until(at: &TclTime, from: &TclTime) -> TclTime {
let mut sec = at.sec - from.sec;
let mut usec = at.usec - from.usec;
if usec < 0 {
sec -= 1;
usec += 1_000_000;
}
if sec < 0 {
return TclTime { sec: 0, usec: 0 };
}
TclTime { sec, usec }
}
unsafe fn create_timer_at(at: TclTime, proc_: TclTimerProc, client_data: *mut c_void) -> c_int {
let n = init_timer();
n.last_timer_id += 1;
let token = n.last_timer_id;
let pos = n
.timers
.iter()
.position(|t| time_before(&at, &t.time))
.unwrap_or(n.timers.len());
n.timers.insert(
pos,
TimerHandler {
time: at,
proc_,
client_data,
token,
},
);
timer_setup_proc(ptr::null_mut(), TCL_ALL_EVENTS);
token
}
unsafe extern "C" fn timer_setup_proc(_client_data: *mut c_void, flags: c_int) {
let n = state();
let block = if (flags & TCL_IDLE_EVENTS != 0 && !n.idles.is_empty())
|| (flags & TCL_TIMER_EVENTS != 0 && n.timer_pending)
{
TclTime { sec: 0, usec: 0 }
} else if flags & TCL_TIMER_EVENTS != 0 && !n.timers.is_empty() {
until(&n.timers[0].time, &now())
} else {
return;
};
set_max_block_time(&block);
}
unsafe extern "C" fn timer_check_proc(_client_data: *mut c_void, flags: c_int) {
let n = state();
if flags & TCL_TIMER_EVENTS == 0 || n.timers.is_empty() {
return;
}
let block = until(&n.timers[0].time, &now());
if block.sec == 0 && block.usec == 0 && !n.timer_pending {
n.timer_pending = true;
let ev = libc::malloc(std::mem::size_of::<TclEvent>()) as *mut TclEvent;
assert!(!ev.is_null(), "out of memory queueing a timer event");
ptr::write(
ev,
TclEvent {
proc_: Some(timer_handler_event_proc),
next_ptr: ptr::null_mut(),
},
);
queue_event(n, ev, TCL_QUEUE_TAIL);
}
}
unsafe extern "C" fn timer_handler_event_proc(_ev: *mut TclEvent, flags: c_int) -> c_int {
if flags & TCL_TIMER_EVENTS == 0 {
return 0;
}
let n = state();
n.timer_pending = false;
let current_id = n.last_timer_id;
let time = now();
loop {
let n = state();
let Some(first) = n.timers.first() else { break };
if time_before(&time, &first.time) {
break;
}
if current_id.wrapping_sub(first.token) < 0 {
break;
}
let handler = n.timers.remove(0);
(handler.proc_)(handler.client_data);
}
timer_setup_proc(ptr::null_mut(), TCL_TIMER_EVENTS);
1
}
unsafe fn service_idle() -> c_int {
let n = state();
if n.idles.is_empty() {
return 0;
}
let old_generation = n.idle_generation;
n.idle_generation += 1;
loop {
let n = state();
let Some(first) = n.idles.first() else { break };
if old_generation.wrapping_sub(first.generation) < 0 {
break;
}
let handler = n.idles.remove(0);
(handler.proc_)(handler.client_data);
}
let n = state();
if !n.idles.is_empty() {
set_max_block_time(&TclTime { sec: 0, usec: 0 });
}
1
}
pub unsafe fn alloc_event(size: usize) -> *mut TclEvent {
assert!(size >= std::mem::size_of::<TclEvent>());
let p = libc::malloc(size) as *mut TclEvent;
assert!(!p.is_null(), "out of memory allocating a Tcl_Event");
(*p).proc_ = None;
(*p).next_ptr = ptr::null_mut();
p
}
macro_rules! entered {
($name:literal) => {
record(
Table::Tcl,
TCL_NAMES
.iter()
.position(|n| *n == $name)
.expect("no such slot"),
)
};
}
pub unsafe extern "C" fn create_event_source(
setup: Option<TclEventSetupProc>,
check: Option<TclEventCheckProc>,
client_data: *mut c_void,
) {
entered!("tcl_CreateEventSource");
state().sources.push(EventSource {
setup,
check,
client_data,
});
}
pub unsafe extern "C" fn delete_event_source(
setup: Option<TclEventSetupProc>,
check: Option<TclEventCheckProc>,
client_data: *mut c_void,
) {
entered!("tcl_DeleteEventSource");
let want = EventSource {
setup,
check,
client_data,
};
let n = state();
if let Some(i) = n.sources.iter().position(|s| s.same(&want)) {
n.sources.remove(i);
}
}
pub unsafe extern "C" fn queue_event_slot(ev: *mut TclEvent, position: c_int) {
entered!("tcl_QueueEvent");
queue_event(state(), ev, position);
}
pub unsafe extern "C" fn thread_queue_event(
thread_id: *mut c_void,
ev: *mut TclEvent,
position: c_int,
) {
entered!("tcl_ThreadQueueEvent");
let n = state();
if n.thread_id != thread_id {
libc::free(ev as *mut c_void);
return;
}
if queue_event(n, ev, position) {
alert_impl(n);
}
}
pub unsafe extern "C" fn thread_alert(thread_id: *mut c_void) {
entered!("tcl_ThreadAlert");
let n = state();
if n.thread_id == thread_id {
alert_impl(n);
}
}
pub unsafe extern "C" fn get_current_thread() -> *mut c_void {
entered!("tcl_GetCurrentThread");
libc::pthread_self() as *mut c_void
}
pub unsafe extern "C" fn delete_events(proc_: TclEventDeleteProc, client_data: *mut c_void) {
entered!("tcl_DeleteEvents");
let n = state();
let mut prev: *mut TclEvent = ptr::null_mut();
let mut ev = n.first_event;
while !ev.is_null() {
let next = (*ev).next_ptr;
if proc_(ev, client_data) == 1 {
let n = state();
if prev.is_null() {
n.first_event = next;
} else {
(*prev).next_ptr = next;
}
if next.is_null() {
n.last_event = prev;
}
if n.marker_event == ev {
n.marker_event = prev;
}
libc::free(ev as *mut c_void);
n.event_count -= 1;
} else {
prev = ev;
}
ev = next;
}
}
pub unsafe extern "C" fn service_event_slot(flags: c_int) -> c_int {
entered!("tcl_ServiceEvent");
service_event(flags)
}
pub unsafe extern "C" fn service_all_slot() -> c_int {
entered!("tcl_ServiceAll");
service_all()
}
pub unsafe extern "C" fn do_one_event_slot(flags: c_int) -> c_int {
entered!("tcl_DoOneEvent");
do_one_event(flags)
}
pub unsafe extern "C" fn get_service_mode() -> c_int {
entered!("tcl_GetServiceMode");
state().service_mode
}
pub unsafe extern "C" fn set_service_mode(mode: c_int) -> c_int {
entered!("tcl_SetServiceMode");
let n = state();
let old = n.service_mode;
n.service_mode = mode;
service_mode_hook_impl(mode);
old
}
pub unsafe extern "C" fn service_mode_hook(mode: c_int) {
entered!("tcl_ServiceModeHook");
service_mode_hook_impl(mode);
}
pub unsafe extern "C" fn alert_notifier(_client_data: *mut c_void) {
entered!("tcl_AlertNotifier");
alert_impl(state());
}
pub unsafe extern "C" fn set_max_block_time_slot(time: *const TclTime) {
entered!("tcl_SetMaxBlockTime");
set_max_block_time(time);
}
pub unsafe extern "C" fn set_timer(time: *const TclTime) {
entered!("tcl_SetTimer");
set_timer_impl(time);
}
pub unsafe extern "C" fn wait_for_event(time: *const TclTime) -> c_int {
entered!("tcl_WaitForEvent");
wait_for_event_impl(time)
}
pub unsafe extern "C" fn sleep_ms(ms: c_int) {
entered!("tcl_Sleep");
if ms <= 0 {
return;
}
let n = state();
if n.run_loop.is_null() {
let mut want = libc::timespec {
tv_sec: (ms / 1000) as libc::time_t,
tv_nsec: ((ms % 1000) * 1_000_000) as std::ffi::c_long,
};
while libc::nanosleep(&want, &mut want) != 0 {}
return;
}
let mut wait_time = ms as f64 / 1000.0;
let now = CFAbsoluteTimeGetCurrent();
let wait_end = now + wait_time;
let mut restore = ptr::null_mut();
let mut next_fire = 0.0;
if !n.run_loop_timer.is_null() {
next_fire = CFRunLoopTimerGetNextFireDate(n.run_loop_timer);
if next_fire < wait_end {
restore = n.run_loop_timer;
CFRunLoopTimerSetNextFireDate(restore, now + CF_TIMEINTERVAL_FOREVER);
}
}
state().sleeping = true;
while wait_time > 0.0 {
match CFRunLoopRunInMode(kCFRunLoopDefaultMode, wait_time, 0) {
K_CF_RUN_LOOP_RUN_FINISHED => panic!("Tcl_Sleep: CFRunLoop finished"),
K_CF_RUN_LOOP_RUN_STOPPED => wait_time = wait_end - CFAbsoluteTimeGetCurrent(),
_ => wait_time = 0.0,
}
}
state().sleeping = false;
if !restore.is_null() {
CFRunLoopTimerSetNextFireDate(restore, next_fire);
}
}
pub unsafe extern "C" fn create_timer_handler(
milliseconds: c_int,
proc_: TclTimerProc,
client_data: *mut c_void,
) -> *mut c_void {
entered!("tcl_CreateTimerHandler");
let mut at = now();
at.sec += (milliseconds / 1000) as i64;
at.usec += ((milliseconds % 1000) * 1000) as std::ffi::c_long;
if at.usec >= 1_000_000 {
at.usec -= 1_000_000;
at.sec += 1;
}
create_timer_at(at, proc_, client_data) as usize as *mut c_void
}
pub unsafe extern "C" fn delete_timer_handler(token: *mut c_void) {
entered!("tcl_DeleteTimerHandler");
if token.is_null() {
return;
}
let want = token as usize as c_int;
let n = init_timer();
if let Some(i) = n.timers.iter().position(|t| t.token == want) {
n.timers.remove(i);
}
}
pub unsafe extern "C" fn do_when_idle(proc_: TclIdleProc, client_data: *mut c_void) {
entered!("tcl_DoWhenIdle");
let n = init_timer();
let generation = n.idle_generation;
n.idles.push(IdleHandler {
proc_,
client_data,
generation,
});
set_max_block_time(&TclTime { sec: 0, usec: 0 });
}
pub unsafe extern "C" fn cancel_idle_call(proc_: TclIdleProc, client_data: *mut c_void) {
entered!("tcl_CancelIdleCall");
let n = init_timer();
n.idles
.retain(|h| !(std::ptr::fn_addr_eq(h.proc_, proc_) && h.client_data == client_data));
}
pub unsafe extern "C" fn create_file_handler(
fd: c_int,
mask: c_int,
proc_: TclFileProc,
client_data: *mut c_void,
) {
entered!("tcl_CreateFileHandler");
let n = state();
if let Some(i) = n.files.iter().position(|h| h.fd == fd) {
n.files[i].mask = mask;
n.files[i].proc_ = proc_;
n.files[i].client_data = client_data;
rearm(&n.files[i]);
return;
}
let mut context = CFContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copy_description: None,
};
let cf_fd = CFFileDescriptorCreate(ptr::null(), fd, 0, file_ready, &mut context);
assert!(!cf_fd.is_null(), "could not watch descriptor {fd}");
let cf_source = CFFileDescriptorCreateRunLoopSource(ptr::null(), cf_fd, 0);
assert!(
!cf_source.is_null(),
"could not create a source for descriptor {fd}"
);
CFRunLoopAddSource(n.run_loop, cf_source, kCFRunLoopCommonModes);
CFRunLoopAddSource(n.run_loop, cf_source, n.events_only_mode);
n.files.push(FileHandler {
fd,
mask,
ready_mask: 0,
event_queued: false,
proc_,
client_data,
cf_fd,
cf_source,
});
let last = n.files.len() - 1;
rearm(&n.files[last]);
}
pub unsafe extern "C" fn delete_file_handler(fd: c_int) {
entered!("tcl_DeleteFileHandler");
let n = state();
let Some(i) = n.files.iter().position(|h| h.fd == fd) else {
return;
};
let h = n.files.remove(i);
CFFileDescriptorDisableCallBacks(h.cf_fd, K_CF_FD_READ | K_CF_FD_WRITE);
CFRunLoopRemoveSource(n.run_loop, h.cf_source, kCFRunLoopCommonModes);
CFRunLoopRemoveSource(n.run_loop, h.cf_source, n.events_only_mode);
CFFileDescriptorInvalidate(h.cf_fd);
CFRelease(h.cf_source);
CFRelease(h.cf_fd);
}
pub unsafe extern "C" fn macosx_notifier_add_run_loop_mode(mode: *const c_void) {
record(
Table::TclPlat,
TCL_PLAT_NAMES
.iter()
.position(|n| *n == "tcl_MacOSXNotifierAddRunLoopMode")
.expect("no such plat slot"),
);
let n = state();
if n.run_loop.is_null() {
return;
}
CFRunLoopAddSource(n.run_loop, n.run_loop_source, mode as CFStringRef);
if !n.run_loop_timer.is_null() {
CFRunLoopAddTimer(n.run_loop, n.run_loop_timer, mode as CFStringRef);
}
}
unsafe fn install(t: &mut TclStubs, name: &str, f: *const ()) -> usize {
let i = TCL_NAMES
.iter()
.position(|n| *n == name)
.unwrap_or_else(|| panic!("no slot named {name} in TclStubs"));
t.slots[i] = std::mem::transmute::<*const (), RawStub>(f);
i
}
pub unsafe fn install_impls(t: &mut TclStubs) -> Vec<usize> {
vec![
install(t, "tcl_CreateFileHandler", create_file_handler as *const ()),
install(t, "tcl_DeleteFileHandler", delete_file_handler as *const ()),
install(t, "tcl_SetTimer", set_timer as *const ()),
install(t, "tcl_Sleep", sleep_ms as *const ()),
install(t, "tcl_WaitForEvent", wait_for_event as *const ()),
install(t, "tcl_CancelIdleCall", cancel_idle_call as *const ()),
install(t, "tcl_CreateEventSource", create_event_source as *const ()),
install(
t,
"tcl_CreateTimerHandler",
create_timer_handler as *const (),
),
install(t, "tcl_DeleteEvents", delete_events as *const ()),
install(t, "tcl_DeleteEventSource", delete_event_source as *const ()),
install(
t,
"tcl_DeleteTimerHandler",
delete_timer_handler as *const (),
),
install(t, "tcl_DoOneEvent", do_one_event_slot as *const ()),
install(t, "tcl_DoWhenIdle", do_when_idle as *const ()),
install(t, "tcl_GetServiceMode", get_service_mode as *const ()),
install(t, "tcl_QueueEvent", queue_event_slot as *const ()),
install(t, "tcl_ServiceAll", service_all_slot as *const ()),
install(t, "tcl_ServiceEvent", service_event_slot as *const ()),
install(
t,
"tcl_SetMaxBlockTime",
set_max_block_time_slot as *const (),
),
install(t, "tcl_SetServiceMode", set_service_mode as *const ()),
install(t, "tcl_GetCurrentThread", get_current_thread as *const ()),
install(t, "tcl_ThreadAlert", thread_alert as *const ()),
install(t, "tcl_ThreadQueueEvent", thread_queue_event as *const ()),
install(t, "tcl_AlertNotifier", alert_notifier as *const ()),
install(t, "tcl_ServiceModeHook", service_mode_hook as *const ()),
install(t, "tcl_SetMainLoop", set_main_loop as *const ()),
]
}
static MAIN_LOOP: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
unsafe extern "C" fn set_main_loop(proc_: *mut c_void) {
entered!("tcl_SetMainLoop");
MAIN_LOOP.store(proc_, Ordering::Relaxed);
}
pub fn main_loop_proc() -> *mut c_void {
MAIN_LOOP.load(Ordering::Relaxed)
}
pub unsafe fn install_plat(t: &mut TclPlatStubs) -> usize {
let i = TCL_PLAT_NAMES
.iter()
.position(|n| *n == "tcl_MacOSXNotifierAddRunLoopMode")
.expect("no tcl_MacOSXNotifierAddRunLoopMode in TclPlatStubs");
t.slots[i] =
std::mem::transmute::<*const (), RawStub>(macosx_notifier_add_run_loop_mode as *const ());
i
}