1#![deny(missing_docs)]
2
3use log::error;
6use std::cell::{Cell, RefCell};
7use std::ffi::{c_void, CStr, CString};
8use std::rc::Rc;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::{panic, process};
11
12thread_local! {
13 static MAIN_THREAD: Cell<bool> = Cell::new(false);
14}
15
16static INITIALIZED: AtomicBool = AtomicBool::new(false);
17
18pub trait Handler: 'static {
23 fn handle(&mut self, window: Window, message: &str) {
25 let _ = (window, message);
26 }
27}
28
29impl<F: FnMut(Window, &str) + 'static> Handler for F {
30 fn handle(&mut self, window: Window, message: &str) {
31 (self)(window, message)
32 }
33}
34
35#[derive(Clone)]
36pub struct Window {
38 data: Rc<RefCell<Option<raw::tether>>>,
39}
40
41type Data = (Window, Box<dyn Handler>);
42
43impl Window {
44 pub fn new(opts: Options) -> Self {
46 assert_main();
47
48 let this = Window {
49 data: Rc::new(RefCell::new(None)),
50 };
51
52 let handler = opts.handler.unwrap_or(Box::new(|_, _: &_| {}));
53
54 let opts = raw::tether_options {
55 initial_width: opts.initial_width,
56 initial_height: opts.initial_height,
57 minimum_width: opts.minimum_width,
58 minimum_height: opts.minimum_height,
59
60 borderless: opts.borderless,
61 debug: opts.debug,
62
63 data: Box::<Data>::into_raw(Box::new((this.clone(), handler))) as _,
64 closed: Some(closed),
65 message: Some(message),
66 };
67
68 let raw = unsafe { raw::tether_new(opts) };
69 this.data.replace(Some(raw));
70
71 unsafe extern fn closed(data: *mut c_void) {
72 abort_on_panic(|| {
73 let _ = Box::<Data>::from_raw(data as _);
74 });
75 }
76
77 unsafe extern fn message(data: *mut c_void, message: *const i8) {
78 abort_on_panic(|| {
79 let data = data as *mut Data;
80
81 match CStr::from_ptr(message).to_str() {
82 Ok(message) => {
83 (*data).1.handle((*data).0.clone(), message);
84 }
85 Err(e) => {
86 error!("{}", e);
87 }
88 }
89 });
90 }
91
92 this
93 }
94
95 pub fn with_handler(handler: impl Handler) -> Self {
97 Self::new(Options {
98 handler: Some(Box::new(handler)),
99 ..Default::default()
100 })
101 }
102
103 pub fn eval<I: Into<String>>(&self, s: I) {
105 if let Some(data) = *self.data.borrow_mut() {
106 let s = string_to_cstring(s);
107 unsafe {
108 raw::tether_eval(data, s.as_ptr());
109 }
110 }
111 }
112
113 pub fn load<I: Into<String>>(&self, s: I) {
115 if let Some(data) = *self.data.borrow_mut() {
116 let s = string_to_cstring(s);
117 unsafe {
118 raw::tether_load(data, s.as_ptr());
119 }
120 }
121 }
122
123 pub fn title<I: Into<String>>(&self, s: I) {
125 if let Some(data) = *self.data.borrow_mut() {
126 let s = string_to_cstring(s);
127 unsafe {
128 raw::tether_title(data, s.as_ptr());
129 }
130 }
131 }
132
133 pub fn focus(&self) {
135 if let Some(data) = *self.data.borrow_mut() {
136 unsafe {
137 raw::tether_focus(data);
138 }
139 }
140 }
141
142 pub fn close(&self) {
144 if let Some(data) = *self.data.borrow_mut() {
145 unsafe {
146 raw::tether_close(data);
147 }
148 }
149 }
150}
151
152impl Default for Window {
153 fn default() -> Self {
154 Self::new(Default::default())
155 }
156}
157
158pub struct Options {
162 pub initial_width: usize,
164 pub initial_height: usize,
166 pub minimum_width: usize,
168 pub minimum_height: usize,
170
171 pub borderless: bool,
173 pub debug: bool,
175
176 pub handler: Option<Box<dyn Handler>>,
178}
179
180impl Default for Options {
181 fn default() -> Self {
182 Self {
183 initial_width: 640,
184 initial_height: 480,
185 minimum_width: 480,
186 minimum_height: 360,
187
188 borderless: false,
189 debug: false,
190
191 handler: None,
192 }
193 }
194}
195
196pub unsafe fn start(cb: fn()) {
202 static mut INIT: Option<fn()> = None;
203 INIT = Some(cb);
204
205 unsafe extern fn init() {
206 abort_on_panic(|| {
207 MAIN_THREAD.with(|initialized| {
208 initialized.set(true);
209 });
210
211 INITIALIZED.store(true, Ordering::Relaxed);
212
213 INIT.unwrap()();
214 });
215 }
216
217 raw::tether_start(Some(init));
218}
219
220pub fn exit() {
222 assert_main();
223
224 unsafe {
225 raw::tether_exit();
226 }
227}
228
229pub fn dispatch<F: FnOnce() + Send>(f: F) {
231 assert_initialized();
232
233 unsafe {
234 raw::tether_dispatch(
235 Box::<F>::into_raw(Box::new(f)) as _,
236 Some(execute::<F>),
237 );
238 }
239
240 unsafe extern fn execute<F: FnOnce() + Send>(data: *mut c_void) {
241 abort_on_panic(|| {
242 Box::<F>::from_raw(data as _)();
243 });
244 }
245}
246
247fn abort_on_panic<F: FnOnce() + panic::UnwindSafe>(f: F) {
248 if panic::catch_unwind(f).is_err() {
249 process::abort();
250 }
251}
252
253fn assert_initialized() {
255 assert!(INITIALIZED.load(Ordering::Relaxed));
256}
257
258fn assert_main() {
260 MAIN_THREAD.with(|initialized| {
261 assert!(initialized.get());
262 });
263}
264
265fn string_to_cstring<I: Into<String>>(s: I) -> CString {
266 CString::new(s.into()).unwrap()
267}
268
269mod raw {
270 #![allow(dead_code, nonstandard_style)]
271 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
272}