1use std::ffi::{CStr, CString};
42use std::os::raw::c_void;
43use std::ptr;
44use wxdragon_sys as ffi;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[repr(i32)]
49pub enum IPCFormat {
50 Text = 1,
52 Bitmap = 2,
54 Metafile = 3,
56 UnicodeText = 13,
58 Utf8Text = 14,
60 Private = 20,
62}
63
64impl From<ffi::wxd_IPCFormat> for IPCFormat {
65 fn from(format: ffi::wxd_IPCFormat) -> Self {
66 match format {
67 ffi::wxd_IPCFormat_WXD_IPC_TEXT => IPCFormat::Text,
68 ffi::wxd_IPCFormat_WXD_IPC_BITMAP => IPCFormat::Bitmap,
69 ffi::wxd_IPCFormat_WXD_IPC_METAFILE => IPCFormat::Metafile,
70 ffi::wxd_IPCFormat_WXD_IPC_UNICODETEXT => IPCFormat::UnicodeText,
71 ffi::wxd_IPCFormat_WXD_IPC_UTF8TEXT => IPCFormat::Utf8Text,
72 ffi::wxd_IPCFormat_WXD_IPC_PRIVATE => IPCFormat::Private,
73 _ => IPCFormat::Text,
74 }
75 }
76}
77
78impl From<IPCFormat> for ffi::wxd_IPCFormat {
79 fn from(format: IPCFormat) -> Self {
80 match format {
81 IPCFormat::Text => ffi::wxd_IPCFormat_WXD_IPC_TEXT,
82 IPCFormat::Bitmap => ffi::wxd_IPCFormat_WXD_IPC_BITMAP,
83 IPCFormat::Metafile => ffi::wxd_IPCFormat_WXD_IPC_METAFILE,
84 IPCFormat::UnicodeText => ffi::wxd_IPCFormat_WXD_IPC_UNICODETEXT,
85 IPCFormat::Utf8Text => ffi::wxd_IPCFormat_WXD_IPC_UTF8TEXT,
86 IPCFormat::Private => ffi::wxd_IPCFormat_WXD_IPC_PRIVATE,
87 }
88 }
89}
90
91type ExecuteCallback = Box<dyn FnMut(&str, &[u8], IPCFormat) -> bool>;
97type RequestCallback = Box<dyn FnMut(&str, &str, IPCFormat) -> Option<Vec<u8>>>;
98type PokeCallback = Box<dyn FnMut(&str, &str, &[u8], IPCFormat) -> bool>;
99type AdviseTopicCallback = Box<dyn FnMut(&str, &str) -> bool>;
100type AdviseDataCallback = Box<dyn FnMut(&str, &str, &[u8], IPCFormat) -> bool>;
101type DisconnectCallback = Box<dyn FnMut() -> bool>;
102type AcceptConnectionCallback = Box<dyn FnMut(&str) -> Option<IPCConnection>>;
103
104struct ConnectionCallbacks {
106 on_execute: Option<ExecuteCallback>,
107 on_request: Option<RequestCallback>,
108 on_poke: Option<PokeCallback>,
109 on_start_advise: Option<AdviseTopicCallback>,
110 on_stop_advise: Option<AdviseTopicCallback>,
111 on_advise: Option<AdviseDataCallback>,
112 on_disconnect: Option<DisconnectCallback>,
113 request_buffer: Vec<u8>,
115}
116
117impl ConnectionCallbacks {
118 fn new() -> Self {
119 Self {
120 on_execute: None,
121 on_request: None,
122 on_poke: None,
123 on_start_advise: None,
124 on_stop_advise: None,
125 on_advise: None,
126 on_disconnect: None,
127 request_buffer: Vec::new(),
128 }
129 }
130}
131
132#[allow(unsafe_op_in_unsafe_fn)]
138unsafe extern "C" fn on_execute_trampoline(
139 user_data: *mut c_void,
140 topic: *const i8,
141 data: *const c_void,
142 size: usize,
143 format: ffi::wxd_IPCFormat,
144) -> bool {
145 if user_data.is_null() {
146 return false;
147 }
148 let callbacks = &mut *(user_data as *mut ConnectionCallbacks);
149 if let Some(ref mut cb) = callbacks.on_execute {
150 let topic_str = if topic.is_null() {
151 ""
152 } else {
153 CStr::from_ptr(topic).to_str().unwrap_or("")
154 };
155 let data_slice = if data.is_null() || size == 0 {
156 &[]
157 } else {
158 std::slice::from_raw_parts(data as *const u8, size)
159 };
160 return cb(topic_str, data_slice, IPCFormat::from(format));
161 }
162 false
163}
164
165#[allow(unsafe_op_in_unsafe_fn)]
166unsafe extern "C" fn on_request_trampoline(
167 user_data: *mut c_void,
168 topic: *const i8,
169 item: *const i8,
170 out_size: *mut usize,
171 format: ffi::wxd_IPCFormat,
172) -> *const c_void {
173 if user_data.is_null() {
174 return ptr::null();
175 }
176 let callbacks = &mut *(user_data as *mut ConnectionCallbacks);
177 if let Some(ref mut cb) = callbacks.on_request {
178 let topic_str = if topic.is_null() {
179 ""
180 } else {
181 CStr::from_ptr(topic).to_str().unwrap_or("")
182 };
183 let item_str = if item.is_null() {
184 ""
185 } else {
186 CStr::from_ptr(item).to_str().unwrap_or("")
187 };
188 if let Some(data) = cb(topic_str, item_str, IPCFormat::from(format)) {
189 callbacks.request_buffer = data;
191 if !out_size.is_null() {
192 *out_size = callbacks.request_buffer.len();
193 }
194 return callbacks.request_buffer.as_ptr() as *const c_void;
195 }
196 }
197 if !out_size.is_null() {
198 *out_size = 0;
199 }
200 ptr::null()
201}
202
203#[allow(unsafe_op_in_unsafe_fn)]
204unsafe extern "C" fn on_poke_trampoline(
205 user_data: *mut c_void,
206 topic: *const i8,
207 item: *const i8,
208 data: *const c_void,
209 size: usize,
210 format: ffi::wxd_IPCFormat,
211) -> bool {
212 if user_data.is_null() {
213 return false;
214 }
215 let callbacks = &mut *(user_data as *mut ConnectionCallbacks);
216 if let Some(ref mut cb) = callbacks.on_poke {
217 let topic_str = if topic.is_null() {
218 ""
219 } else {
220 CStr::from_ptr(topic).to_str().unwrap_or("")
221 };
222 let item_str = if item.is_null() {
223 ""
224 } else {
225 CStr::from_ptr(item).to_str().unwrap_or("")
226 };
227 let data_slice = if data.is_null() || size == 0 {
228 &[]
229 } else {
230 std::slice::from_raw_parts(data as *const u8, size)
231 };
232 return cb(topic_str, item_str, data_slice, IPCFormat::from(format));
233 }
234 false
235}
236
237#[allow(unsafe_op_in_unsafe_fn)]
238unsafe extern "C" fn on_start_advise_trampoline(user_data: *mut c_void, topic: *const i8, item: *const i8) -> bool {
239 if user_data.is_null() {
240 return false;
241 }
242 let callbacks = &mut *(user_data as *mut ConnectionCallbacks);
243 if let Some(ref mut cb) = callbacks.on_start_advise {
244 let topic_str = if topic.is_null() {
245 ""
246 } else {
247 CStr::from_ptr(topic).to_str().unwrap_or("")
248 };
249 let item_str = if item.is_null() {
250 ""
251 } else {
252 CStr::from_ptr(item).to_str().unwrap_or("")
253 };
254 return cb(topic_str, item_str);
255 }
256 false
257}
258
259#[allow(unsafe_op_in_unsafe_fn)]
260unsafe extern "C" fn on_stop_advise_trampoline(user_data: *mut c_void, topic: *const i8, item: *const i8) -> bool {
261 if user_data.is_null() {
262 return false;
263 }
264 let callbacks = &mut *(user_data as *mut ConnectionCallbacks);
265 if let Some(ref mut cb) = callbacks.on_stop_advise {
266 let topic_str = if topic.is_null() {
267 ""
268 } else {
269 CStr::from_ptr(topic).to_str().unwrap_or("")
270 };
271 let item_str = if item.is_null() {
272 ""
273 } else {
274 CStr::from_ptr(item).to_str().unwrap_or("")
275 };
276 return cb(topic_str, item_str);
277 }
278 false
279}
280
281#[allow(unsafe_op_in_unsafe_fn)]
282unsafe extern "C" fn on_advise_trampoline(
283 user_data: *mut c_void,
284 topic: *const i8,
285 item: *const i8,
286 data: *const c_void,
287 size: usize,
288 format: ffi::wxd_IPCFormat,
289) -> bool {
290 if user_data.is_null() {
291 return false;
292 }
293 let callbacks = &mut *(user_data as *mut ConnectionCallbacks);
294 if let Some(ref mut cb) = callbacks.on_advise {
295 let topic_str = if topic.is_null() {
296 ""
297 } else {
298 CStr::from_ptr(topic).to_str().unwrap_or("")
299 };
300 let item_str = if item.is_null() {
301 ""
302 } else {
303 CStr::from_ptr(item).to_str().unwrap_or("")
304 };
305 let data_slice = if data.is_null() || size == 0 {
306 &[]
307 } else {
308 std::slice::from_raw_parts(data as *const u8, size)
309 };
310 return cb(topic_str, item_str, data_slice, IPCFormat::from(format));
311 }
312 false
313}
314
315#[allow(unsafe_op_in_unsafe_fn)]
316unsafe extern "C" fn on_disconnect_trampoline(user_data: *mut c_void) -> bool {
317 if user_data.is_null() {
318 return true;
319 }
320 let callbacks = &mut *(user_data as *mut ConnectionCallbacks);
321 if let Some(ref mut cb) = callbacks.on_disconnect {
322 return cb();
323 }
324 true
325}
326
327#[allow(unsafe_op_in_unsafe_fn)]
328unsafe extern "C" fn free_connection_callbacks(user_data: *mut c_void) {
329 if !user_data.is_null() {
330 let _ = Box::from_raw(user_data as *mut ConnectionCallbacks);
331 }
332}
333
334pub struct IPCConnection {
344 ptr: *mut ffi::wxd_IPCConnection_t,
345 owned: bool,
347}
348
349impl IPCConnection {
350 pub fn builder() -> IPCConnectionBuilder {
352 IPCConnectionBuilder::new()
353 }
354
355 #[allow(dead_code)]
360 pub(crate) unsafe fn from_ptr(ptr: *mut ffi::wxd_IPCConnection_t) -> Option<Self> {
361 if ptr.is_null() {
362 None
363 } else {
364 Some(Self { ptr, owned: false })
365 }
366 }
367
368 #[allow(dead_code)]
370 pub(crate) fn as_ptr(&self) -> *mut ffi::wxd_IPCConnection_t {
371 self.ptr
372 }
373
374 pub fn execute(&self, data: &[u8], format: IPCFormat) -> bool {
379 if self.ptr.is_null() {
380 return false;
381 }
382 unsafe { ffi::wxd_IPCConnection_Execute(self.ptr, data.as_ptr() as *const c_void, data.len(), format.into()) }
383 }
384
385 pub fn execute_string(&self, data: &str) -> bool {
387 if self.ptr.is_null() {
388 return false;
389 }
390 let c_str = match CString::new(data) {
391 Ok(s) => s,
392 Err(_) => return false,
393 };
394 unsafe { ffi::wxd_IPCConnection_ExecuteString(self.ptr, c_str.as_ptr()) }
395 }
396
397 pub fn request(&self, item: &str, format: IPCFormat) -> Option<Vec<u8>> {
401 if self.ptr.is_null() {
402 return None;
403 }
404 let c_item = CString::new(item).ok()?;
405 let mut size: usize = 0;
406 let data_ptr = unsafe { ffi::wxd_IPCConnection_Request(self.ptr, c_item.as_ptr(), &mut size, format.into()) };
407 if data_ptr.is_null() || size == 0 {
408 return None;
409 }
410 let data_slice = unsafe { std::slice::from_raw_parts(data_ptr as *const u8, size) };
411 Some(data_slice.to_vec())
412 }
413
414 pub fn poke(&self, item: &str, data: &[u8], format: IPCFormat) -> bool {
416 if self.ptr.is_null() {
417 return false;
418 }
419 let c_item = match CString::new(item) {
420 Ok(s) => s,
421 Err(_) => return false,
422 };
423 unsafe {
424 ffi::wxd_IPCConnection_Poke(
425 self.ptr,
426 c_item.as_ptr(),
427 data.as_ptr() as *const c_void,
428 data.len(),
429 format.into(),
430 )
431 }
432 }
433
434 pub fn start_advise(&self, item: &str) -> bool {
438 if self.ptr.is_null() {
439 return false;
440 }
441 let c_item = match CString::new(item) {
442 Ok(s) => s,
443 Err(_) => return false,
444 };
445 unsafe { ffi::wxd_IPCConnection_StartAdvise(self.ptr, c_item.as_ptr()) }
446 }
447
448 pub fn stop_advise(&self, item: &str) -> bool {
450 if self.ptr.is_null() {
451 return false;
452 }
453 let c_item = match CString::new(item) {
454 Ok(s) => s,
455 Err(_) => return false,
456 };
457 unsafe { ffi::wxd_IPCConnection_StopAdvise(self.ptr, c_item.as_ptr()) }
458 }
459
460 pub fn advise(&self, item: &str, data: &[u8], format: IPCFormat) -> bool {
462 if self.ptr.is_null() {
463 return false;
464 }
465 let c_item = match CString::new(item) {
466 Ok(s) => s,
467 Err(_) => return false,
468 };
469 unsafe {
470 ffi::wxd_IPCConnection_Advise(
471 self.ptr,
472 c_item.as_ptr(),
473 data.as_ptr() as *const c_void,
474 data.len(),
475 format.into(),
476 )
477 }
478 }
479
480 pub fn disconnect(&self) -> bool {
482 if self.ptr.is_null() {
483 return false;
484 }
485 unsafe { ffi::wxd_IPCConnection_Disconnect(self.ptr) }
486 }
487
488 pub fn is_connected(&self) -> bool {
490 if self.ptr.is_null() {
491 return false;
492 }
493 unsafe { ffi::wxd_IPCConnection_IsConnected(self.ptr) }
494 }
495}
496
497impl Drop for IPCConnection {
498 fn drop(&mut self) {
499 if self.owned && !self.ptr.is_null() {
500 unsafe { ffi::wxd_IPCConnection_Destroy(self.ptr) };
501 }
502 }
503}
504
505pub struct IPCConnectionBuilder {
507 callbacks: ConnectionCallbacks,
508}
509
510impl IPCConnectionBuilder {
511 pub fn new() -> Self {
513 Self {
514 callbacks: ConnectionCallbacks::new(),
515 }
516 }
517
518 pub fn on_execute<F>(mut self, callback: F) -> Self
520 where
521 F: FnMut(&str, &[u8], IPCFormat) -> bool + 'static,
522 {
523 self.callbacks.on_execute = Some(Box::new(callback));
524 self
525 }
526
527 pub fn on_request<F>(mut self, callback: F) -> Self
529 where
530 F: FnMut(&str, &str, IPCFormat) -> Option<Vec<u8>> + 'static,
531 {
532 self.callbacks.on_request = Some(Box::new(callback));
533 self
534 }
535
536 pub fn on_poke<F>(mut self, callback: F) -> Self
538 where
539 F: FnMut(&str, &str, &[u8], IPCFormat) -> bool + 'static,
540 {
541 self.callbacks.on_poke = Some(Box::new(callback));
542 self
543 }
544
545 pub fn on_start_advise<F>(mut self, callback: F) -> Self
547 where
548 F: FnMut(&str, &str) -> bool + 'static,
549 {
550 self.callbacks.on_start_advise = Some(Box::new(callback));
551 self
552 }
553
554 pub fn on_stop_advise<F>(mut self, callback: F) -> Self
556 where
557 F: FnMut(&str, &str) -> bool + 'static,
558 {
559 self.callbacks.on_stop_advise = Some(Box::new(callback));
560 self
561 }
562
563 pub fn on_advise<F>(mut self, callback: F) -> Self
565 where
566 F: FnMut(&str, &str, &[u8], IPCFormat) -> bool + 'static,
567 {
568 self.callbacks.on_advise = Some(Box::new(callback));
569 self
570 }
571
572 pub fn on_disconnect<F>(mut self, callback: F) -> Self
574 where
575 F: FnMut() -> bool + 'static,
576 {
577 self.callbacks.on_disconnect = Some(Box::new(callback));
578 self
579 }
580
581 pub fn build(self) -> IPCConnection {
583 let callbacks_box = Box::new(self.callbacks);
584 let user_data = Box::into_raw(callbacks_box) as *mut c_void;
585
586 let ptr = unsafe {
587 ffi::wxd_IPCConnection_Create(
588 user_data,
589 Some(on_execute_trampoline),
590 Some(on_request_trampoline),
591 Some(on_poke_trampoline),
592 Some(on_start_advise_trampoline),
593 Some(on_stop_advise_trampoline),
594 Some(on_advise_trampoline),
595 Some(on_disconnect_trampoline),
596 Some(free_connection_callbacks),
597 )
598 };
599
600 IPCConnection { ptr, owned: true }
601 }
602}
603
604impl Default for IPCConnectionBuilder {
605 fn default() -> Self {
606 Self::new()
607 }
608}
609
610struct ServerCallbacks {
616 on_accept: AcceptConnectionCallback,
617}
618
619#[allow(unsafe_op_in_unsafe_fn)]
620unsafe extern "C" fn on_accept_connection_trampoline(user_data: *mut c_void, topic: *const i8) -> *mut ffi::wxd_IPCConnection_t {
621 if user_data.is_null() {
622 return ptr::null_mut();
623 }
624 let callbacks = &mut *(user_data as *mut ServerCallbacks);
625 let topic_str = if topic.is_null() {
626 ""
627 } else {
628 CStr::from_ptr(topic).to_str().unwrap_or("")
629 };
630 if let Some(conn) = (callbacks.on_accept)(topic_str) {
631 let ptr = conn.ptr;
633 std::mem::forget(conn);
634 ptr
635 } else {
636 ptr::null_mut()
637 }
638}
639
640#[allow(unsafe_op_in_unsafe_fn)]
641unsafe extern "C" fn free_server_callbacks(user_data: *mut c_void) {
642 if !user_data.is_null() {
643 let _ = Box::from_raw(user_data as *mut ServerCallbacks);
644 }
645}
646
647pub struct IPCServer {
675 ptr: *mut ffi::wxd_IPCServer_t,
676}
677
678impl IPCServer {
679 pub fn new<F>(on_accept_connection: F) -> Self
684 where
685 F: FnMut(&str) -> Option<IPCConnection> + 'static,
686 {
687 let callbacks = ServerCallbacks {
688 on_accept: Box::new(on_accept_connection),
689 };
690 let user_data = Box::into_raw(Box::new(callbacks)) as *mut c_void;
691
692 let ptr =
693 unsafe { ffi::wxd_IPCServer_Create(user_data, Some(on_accept_connection_trampoline), Some(free_server_callbacks)) };
694
695 Self { ptr }
696 }
697
698 pub fn create(&self, service: &str) -> bool {
703 if self.ptr.is_null() {
704 return false;
705 }
706 let c_service = match CString::new(service) {
707 Ok(s) => s,
708 Err(_) => return false,
709 };
710 unsafe { ffi::wxd_IPCServer_Create_Service(self.ptr, c_service.as_ptr()) }
711 }
712}
713
714impl Drop for IPCServer {
715 fn drop(&mut self) {
716 if !self.ptr.is_null() {
717 unsafe { ffi::wxd_IPCServer_Destroy(self.ptr) };
718 }
719 }
720}
721
722pub struct IPCClient {
756 ptr: *mut ffi::wxd_IPCClient_t,
757}
758
759impl IPCClient {
760 pub fn new() -> Self {
762 let ptr = unsafe { ffi::wxd_IPCClient_Create() };
763 Self { ptr }
764 }
765
766 pub fn make_connection(&self, host: &str, service: &str, topic: &str) -> Option<IPCConnection> {
776 self.make_connection_with_callbacks(host, service, topic, IPCConnectionBuilder::new())
777 }
778
779 pub fn make_connection_with_callbacks(
784 &self,
785 host: &str,
786 service: &str,
787 topic: &str,
788 builder: IPCConnectionBuilder,
789 ) -> Option<IPCConnection> {
790 if self.ptr.is_null() {
791 return None;
792 }
793
794 let c_host = CString::new(host).ok()?;
795 let c_service = CString::new(service).ok()?;
796 let c_topic = CString::new(topic).ok()?;
797
798 let callbacks_box = Box::new(builder.callbacks);
799 let user_data = Box::into_raw(callbacks_box) as *mut c_void;
800
801 let conn_ptr = unsafe {
802 ffi::wxd_IPCClient_MakeConnection(
803 self.ptr,
804 c_host.as_ptr(),
805 c_service.as_ptr(),
806 c_topic.as_ptr(),
807 user_data,
808 Some(on_execute_trampoline),
809 Some(on_request_trampoline),
810 Some(on_poke_trampoline),
811 Some(on_start_advise_trampoline),
812 Some(on_stop_advise_trampoline),
813 Some(on_advise_trampoline),
814 Some(on_disconnect_trampoline),
815 Some(free_connection_callbacks),
816 )
817 };
818
819 if conn_ptr.is_null() {
820 None
821 } else {
822 Some(IPCConnection {
823 ptr: conn_ptr,
824 owned: false, })
826 }
827 }
828}
829
830impl Default for IPCClient {
831 fn default() -> Self {
832 Self::new()
833 }
834}
835
836impl Drop for IPCClient {
837 fn drop(&mut self) {
838 if !self.ptr.is_null() {
839 unsafe { ffi::wxd_IPCClient_Destroy(self.ptr) };
840 }
841 }
842}