core_foundation/
runloop.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10#![allow(non_upper_case_globals)]
11
12use core_foundation_sys::base::CFIndex;
13use core_foundation_sys::base::{kCFAllocatorDefault, CFOptionFlags};
14pub use core_foundation_sys::runloop::*;
15use core_foundation_sys::string::CFStringRef;
16
17use crate::base::TCFType;
18use crate::date::{CFAbsoluteTime, CFTimeInterval};
19use crate::filedescriptor::CFFileDescriptor;
20use crate::string::CFString;
21
22pub type CFRunLoopMode = CFStringRef;
23
24declare_TCFType!(CFRunLoop, CFRunLoopRef);
25impl_TCFType!(CFRunLoop, CFRunLoopRef, CFRunLoopGetTypeID);
26impl_CFTypeDescription!(CFRunLoop);
27
28// https://github.com/servo/core-foundation-rs/issues/550
29unsafe impl Send for CFRunLoop {}
30unsafe impl Sync for CFRunLoop {}
31
32#[derive(Copy, Clone, Debug, PartialEq)]
33pub enum CFRunLoopRunResult {
34    Finished = 1,
35    Stopped = 2,
36    TimedOut = 3,
37    HandledSource = 4,
38}
39
40impl CFRunLoop {
41    pub fn get_current() -> CFRunLoop {
42        unsafe {
43            let run_loop_ref = CFRunLoopGetCurrent();
44            TCFType::wrap_under_get_rule(run_loop_ref)
45        }
46    }
47
48    pub fn get_main() -> CFRunLoop {
49        unsafe {
50            let run_loop_ref = CFRunLoopGetMain();
51            TCFType::wrap_under_get_rule(run_loop_ref)
52        }
53    }
54
55    pub fn run_current() {
56        unsafe {
57            CFRunLoopRun();
58        }
59    }
60
61    pub fn run_in_mode(
62        mode: CFStringRef,
63        duration: std::time::Duration,
64        return_after_source_handled: bool,
65    ) -> CFRunLoopRunResult {
66        let seconds = duration.as_secs_f64();
67        let return_after_source_handled = if return_after_source_handled { 1 } else { 0 };
68
69        unsafe {
70            match CFRunLoopRunInMode(mode, seconds, return_after_source_handled) {
71                2 => CFRunLoopRunResult::Stopped,
72                3 => CFRunLoopRunResult::TimedOut,
73                4 => CFRunLoopRunResult::HandledSource,
74                _ => CFRunLoopRunResult::Finished,
75            }
76        }
77    }
78
79    pub fn stop(&self) {
80        unsafe {
81            CFRunLoopStop(self.0);
82        }
83    }
84
85    pub fn current_mode(&self) -> Option<String> {
86        unsafe {
87            let string_ref = CFRunLoopCopyCurrentMode(self.0);
88            if string_ref.is_null() {
89                return None;
90            }
91
92            let cf_string: CFString = TCFType::wrap_under_create_rule(string_ref);
93            Some(cf_string.to_string())
94        }
95    }
96
97    pub fn contains_timer(&self, timer: &CFRunLoopTimer, mode: CFRunLoopMode) -> bool {
98        unsafe { CFRunLoopContainsTimer(self.0, timer.0, mode) != 0 }
99    }
100
101    pub fn add_timer(&self, timer: &CFRunLoopTimer, mode: CFRunLoopMode) {
102        unsafe {
103            CFRunLoopAddTimer(self.0, timer.0, mode);
104        }
105    }
106
107    pub fn remove_timer(&self, timer: &CFRunLoopTimer, mode: CFRunLoopMode) {
108        unsafe {
109            CFRunLoopRemoveTimer(self.0, timer.0, mode);
110        }
111    }
112
113    pub fn contains_source(&self, source: &CFRunLoopSource, mode: CFRunLoopMode) -> bool {
114        unsafe { CFRunLoopContainsSource(self.0, source.0, mode) != 0 }
115    }
116
117    pub fn add_source(&self, source: &CFRunLoopSource, mode: CFRunLoopMode) {
118        unsafe {
119            CFRunLoopAddSource(self.0, source.0, mode);
120        }
121    }
122
123    pub fn remove_source(&self, source: &CFRunLoopSource, mode: CFRunLoopMode) {
124        unsafe {
125            CFRunLoopRemoveSource(self.0, source.0, mode);
126        }
127    }
128
129    pub fn contains_observer(&self, observer: &CFRunLoopObserver, mode: CFRunLoopMode) -> bool {
130        unsafe { CFRunLoopContainsObserver(self.0, observer.0, mode) != 0 }
131    }
132
133    pub fn add_observer(&self, observer: &CFRunLoopObserver, mode: CFRunLoopMode) {
134        unsafe {
135            CFRunLoopAddObserver(self.0, observer.0, mode);
136        }
137    }
138
139    pub fn remove_observer(&self, observer: &CFRunLoopObserver, mode: CFRunLoopMode) {
140        unsafe {
141            CFRunLoopRemoveObserver(self.0, observer.0, mode);
142        }
143    }
144}
145
146declare_TCFType!(CFRunLoopTimer, CFRunLoopTimerRef);
147impl_TCFType!(CFRunLoopTimer, CFRunLoopTimerRef, CFRunLoopTimerGetTypeID);
148
149impl CFRunLoopTimer {
150    pub fn new(
151        fireDate: CFAbsoluteTime,
152        interval: CFTimeInterval,
153        flags: CFOptionFlags,
154        order: CFIndex,
155        callout: CFRunLoopTimerCallBack,
156        context: *mut CFRunLoopTimerContext,
157    ) -> CFRunLoopTimer {
158        unsafe {
159            let timer_ref = CFRunLoopTimerCreate(
160                kCFAllocatorDefault,
161                fireDate,
162                interval,
163                flags,
164                order,
165                callout,
166                context,
167            );
168            TCFType::wrap_under_create_rule(timer_ref)
169        }
170    }
171}
172
173declare_TCFType!(CFRunLoopSource, CFRunLoopSourceRef);
174impl_TCFType!(
175    CFRunLoopSource,
176    CFRunLoopSourceRef,
177    CFRunLoopSourceGetTypeID
178);
179
180impl CFRunLoopSource {
181    pub fn from_file_descriptor(fd: &CFFileDescriptor, order: CFIndex) -> Option<CFRunLoopSource> {
182        fd.to_run_loop_source(order)
183    }
184}
185
186declare_TCFType!(CFRunLoopObserver, CFRunLoopObserverRef);
187impl_TCFType!(
188    CFRunLoopObserver,
189    CFRunLoopObserverRef,
190    CFRunLoopObserverGetTypeID
191);
192
193#[cfg(test)]
194mod test {
195    use super::*;
196    use crate::base::Boolean;
197    use crate::date::{CFAbsoluteTime, CFDate};
198    use std::mem;
199    use std::os::raw::c_void;
200    use std::ptr::null_mut;
201    use std::sync::mpsc;
202    use std::thread::spawn;
203    use std::time::Duration;
204
205    #[test]
206    fn wait_200_milliseconds() {
207        let run_loop = CFRunLoop::get_current();
208
209        let now = CFDate::now().abs_time();
210        let (elapsed_tx, elapsed_rx) = mpsc::channel();
211        let mut info = Info {
212            start_time: now,
213            elapsed_tx,
214        };
215        let mut context = CFRunLoopTimerContext {
216            version: 0,
217            info: &mut info as *mut _ as *mut c_void,
218            retain: None,
219            release: None,
220            copyDescription: None,
221        };
222
223        let run_loop_timer =
224            CFRunLoopTimer::new(now + 0.20f64, 0f64, 0, 0, timer_popped, &mut context);
225        unsafe {
226            run_loop.add_timer(&run_loop_timer, kCFRunLoopDefaultMode);
227        }
228        CFRunLoop::run_current();
229        let elapsed = elapsed_rx.try_recv().unwrap();
230        println!("wait_200_milliseconds, elapsed: {}", elapsed);
231        assert!(elapsed > 0.19 && elapsed < 0.35);
232    }
233
234    struct Info {
235        start_time: CFAbsoluteTime,
236        elapsed_tx: mpsc::Sender<f64>,
237    }
238
239    extern "C" fn timer_popped(_timer: CFRunLoopTimerRef, raw_info: *mut c_void) {
240        let info: *mut Info = unsafe { mem::transmute(raw_info) };
241        let now = CFDate::now().abs_time();
242        let elapsed = now - unsafe { (*info).start_time };
243        let _ = unsafe { (*info).elapsed_tx.send(elapsed) };
244        CFRunLoop::get_current().stop();
245    }
246
247    extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
248        let tx: &mpsc::Sender<CFRunLoop> = unsafe { &*(context as *const _) };
249        let _ = tx.send(CFRunLoop::get_current());
250    }
251
252    extern "C" fn observe_timer_popped(_: CFRunLoopTimerRef, _: *mut c_void) {
253        panic!("timer popped unexpectedly");
254    }
255
256    #[test]
257    fn observe_runloop() {
258        let (tx, rx) = mpsc::channel();
259        spawn(move || {
260            let mut context = CFRunLoopObserverContext {
261                version: 0,
262                info: &tx as *const _ as *mut c_void,
263                retain: None,
264                release: None,
265                copyDescription: None,
266            };
267
268            let observer = unsafe {
269                CFRunLoopObserver::wrap_under_create_rule(CFRunLoopObserverCreate(
270                    kCFAllocatorDefault,
271                    kCFRunLoopEntry,
272                    false as Boolean,
273                    0,
274                    observe,
275                    &mut context,
276                ))
277            };
278
279            let runloop = CFRunLoop::get_current();
280            runloop.add_observer(&observer, unsafe { kCFRunLoopDefaultMode });
281
282            let timer = CFRunLoopTimer::new(
283                CFDate::now().abs_time() + 1f64,
284                0f64,
285                0,
286                0,
287                observe_timer_popped,
288                null_mut(),
289            );
290            runloop.add_timer(&timer, unsafe { kCFRunLoopDefaultMode });
291
292            let result = unsafe {
293                CFRunLoop::run_in_mode(kCFRunLoopDefaultMode, Duration::from_secs(10), false)
294            };
295
296            assert_eq!(result, CFRunLoopRunResult::Stopped);
297
298            drop(tx);
299        });
300
301        let runloop: CFRunLoop = rx.recv().unwrap();
302        runloop.stop();
303    }
304}