1use std::{
2 ffi::{CStr, CString},
3 ptr,
4 time::Duration,
5};
6
7use cbf::data::{edit::EditAction, window_open::WindowOpenResponse};
8use cbf_chrome_sys::ffi::*;
9use tracing::warn;
10
11use super::map::{
12 ime_range_to_ffi, key_event_type_to_ffi, mouse_button_to_ffi, mouse_event_type_to_ffi,
13 parse_event, parse_extension_list, pointer_type_to_ffi, scroll_granularity_to_ffi,
14 to_ffi_ime_text_spans,
15};
16use super::utils::{c_string_to_string, to_optional_cstring};
17use super::{Error, IpcEvent};
18use crate::data::{
19 browsing_context_open::ChromeBrowsingContextOpenResponse,
20 download::ChromeDownloadId,
21 drag::{ChromeDragDrop, ChromeDragUpdate},
22 extension::ChromeExtensionInfo,
23 ids::{PopupId, TabId},
24 ime::{
25 ChromeConfirmCompositionBehavior, ChromeImeCommitText, ChromeImeComposition,
26 ChromeTransientImeCommitText, ChromeTransientImeComposition,
27 },
28 input::{ChromeKeyEvent, ChromeMouseWheelEvent},
29 mouse::ChromeMouseEvent,
30 profile::ChromeProfileInfo,
31 prompt_ui::{PromptUiId, PromptUiResponse},
32};
33
34pub struct IpcClient {
36 inner: *mut CbfBridgeClientHandle,
37}
38
39#[derive(Debug, Clone, Copy)]
40pub(crate) struct IpcEventWaitHandle {
41 inner: *mut CbfBridgeClientHandle,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum EventWaitResult {
46 EventAvailable,
47 TimedOut,
48 Disconnected,
49 Closed,
50}
51
52unsafe impl Send for IpcClient {}
57unsafe impl Send for IpcEventWaitHandle {}
61unsafe impl Sync for IpcEventWaitHandle {}
64
65impl std::fmt::Debug for IpcClient {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.debug_struct("IpcClient")
68 .field("inner", &format!("{:p}", self.inner))
69 .finish()
70 }
71}
72
73impl IpcClient {
74 pub fn prepare_channel() -> Result<(i32, String), Error> {
82 let mut buf = [0u8; 512];
83 let fd = unsafe {
84 cbf_bridge_init();
85 cbf_bridge_prepare_channel(
86 buf.as_mut_ptr() as *mut std::os::raw::c_char,
87 buf.len() as i32,
88 )
89 };
90 let switch_arg = CStr::from_bytes_until_nul(&buf)
91 .map_err(|_| Error::ConnectionFailed)?
92 .to_str()
93 .map_err(|_| Error::ConnectionFailed)?
94 .to_owned();
95 Ok((fd, switch_arg))
96 }
97
98 pub fn pass_child_pid(pid: u32) {
104 unsafe { cbf_bridge_pass_child_pid(pid as i64) }
105 }
106
107 pub unsafe fn connect_inherited(inner: *mut CbfBridgeClientHandle) -> Result<Self, Error> {
119 if inner.is_null() {
120 return Err(Error::ConnectionFailed);
121 }
122 let connected = unsafe { cbf_bridge_client_connect_inherited(inner) };
123 if !connected {
124 warn!(
125 result = "err",
126 error = "ipc_connect_inherited_failed",
127 "IPC inherited connect failed"
128 );
129 unsafe { cbf_bridge_client_destroy(inner) };
130 return Err(Error::ConnectionFailed);
131 }
132 Ok(Self { inner })
133 }
134
135 pub fn authenticate(&self, token: &str) -> Result<(), Error> {
139 if self.inner.is_null() {
140 return Err(Error::ConnectionFailed);
141 }
142 let token = CString::new(token).map_err(|_| Error::InvalidInput)?;
143 if unsafe { cbf_bridge_client_authenticate(self.inner, token.as_ptr()) } {
144 Ok(())
145 } else {
146 Err(Error::ConnectionFailed)
147 }
148 }
149
150 pub fn wait_for_event(&self, timeout: Option<Duration>) -> Result<EventWaitResult, Error> {
152 wait_for_event_inner(self.inner, timeout)
153 }
154
155 pub(crate) fn event_wait_handle(&self) -> IpcEventWaitHandle {
156 IpcEventWaitHandle { inner: self.inner }
157 }
158
159 pub fn poll_event(&mut self) -> Option<Result<IpcEvent, Error>> {
161 if self.inner.is_null() {
162 return None;
163 }
164
165 let mut event = CbfBridgeEvent::default();
166 if !unsafe { cbf_bridge_client_poll_event(self.inner, &mut event) } {
167 return None;
168 }
169
170 let parsed = parse_event(event);
171 unsafe { cbf_bridge_event_free(&mut event) };
172
173 if let Err(err) = &parsed {
174 warn!(
175 result = "err",
176 error = "ipc_event_parse_failed",
177 err = ?err,
178 "IPC event parse failed"
179 );
180 }
181
182 Some(parsed)
183 }
184
185 pub fn list_profiles(&mut self) -> Result<Vec<ChromeProfileInfo>, Error> {
187 if self.inner.is_null() {
188 return Err(Error::ConnectionFailed);
189 }
190
191 let mut list = CbfProfileList::default();
192 if !unsafe { cbf_bridge_client_get_profiles(self.inner, &mut list) } {
193 return Err(Error::ConnectionFailed);
194 }
195
196 let profiles = if list.len == 0 || list.profiles.is_null() {
197 &[]
198 } else {
199 unsafe { std::slice::from_raw_parts(list.profiles, list.len as usize) }
200 };
201 let mut result = Vec::with_capacity(profiles.len());
202
203 for profile in profiles {
204 result.push(ChromeProfileInfo {
205 profile_id: c_string_to_string(profile.profile_id),
206 profile_path: c_string_to_string(profile.profile_path),
207 display_name: c_string_to_string(profile.display_name),
208 is_default: profile.is_default,
209 });
210 }
211
212 unsafe { cbf_bridge_profile_list_free(&mut list) };
213
214 Ok(result)
215 }
216
217 pub fn list_extensions(&mut self, profile_id: &str) -> Result<Vec<ChromeExtensionInfo>, Error> {
219 if self.inner.is_null() {
220 return Err(Error::ConnectionFailed);
221 }
222
223 let profile = CString::new(profile_id).map_err(|_| Error::InvalidInput)?;
224
225 let mut list = CbfExtensionInfoList::default();
226 if !unsafe { cbf_bridge_client_list_extensions(self.inner, profile.as_ptr(), &mut list) } {
227 return Err(Error::ConnectionFailed);
228 }
229
230 let result = parse_extension_list(list);
231
232 unsafe { cbf_bridge_extension_list_free(&mut list) };
233 Ok(result)
234 }
235
236 pub fn activate_extension_action(
237 &mut self,
238 browsing_context_id: TabId,
239 extension_id: &str,
240 ) -> Result<(), Error> {
241 if self.inner.is_null() {
242 return Err(Error::ConnectionFailed);
243 }
244
245 let extension_id = CString::new(extension_id).map_err(|_| Error::InvalidInput)?;
246 if unsafe {
247 cbf_bridge_client_activate_extension_action(
248 self.inner,
249 browsing_context_id.get(),
250 extension_id.as_ptr(),
251 )
252 } {
253 Ok(())
254 } else {
255 Err(Error::ConnectionFailed)
256 }
257 }
258
259 pub fn create_tab(
261 &mut self,
262 request_id: u64,
263 initial_url: &str,
264 profile_id: &str,
265 ) -> Result<(), Error> {
266 if self.inner.is_null() {
267 return Err(Error::ConnectionFailed);
268 }
269
270 let url = CString::new(initial_url).map_err(|_| Error::InvalidInput)?;
271 let profile = CString::new(profile_id).map_err(|_| Error::InvalidInput)?;
272
273 if unsafe {
274 cbf_bridge_client_create_tab(self.inner, request_id, url.as_ptr(), profile.as_ptr())
275 } {
276 Ok(())
277 } else {
278 Err(Error::ConnectionFailed)
279 }
280 }
281
282 pub fn request_close_tab(&mut self, browsing_context_id: TabId) -> Result<(), Error> {
284 if self.inner.is_null() {
285 return Err(Error::ConnectionFailed);
286 }
287
288 if unsafe { cbf_bridge_client_request_close_tab(self.inner, browsing_context_id.get()) } {
289 Ok(())
290 } else {
291 Err(Error::ConnectionFailed)
292 }
293 }
294
295 pub fn set_tab_size(
297 &mut self,
298 browsing_context_id: TabId,
299 width: u32,
300 height: u32,
301 ) -> Result<(), Error> {
302 if self.inner.is_null() {
303 return Err(Error::ConnectionFailed);
304 }
305
306 if unsafe {
307 cbf_bridge_client_set_tab_size(self.inner, browsing_context_id.get(), width, height)
308 } {
309 Ok(())
310 } else {
311 Err(Error::ConnectionFailed)
312 }
313 }
314
315 pub fn set_tab_focus(
317 &mut self,
318 browsing_context_id: TabId,
319 focused: bool,
320 ) -> Result<(), Error> {
321 if self.inner.is_null() {
322 return Err(Error::ConnectionFailed);
323 }
324
325 if unsafe {
326 cbf_bridge_client_set_tab_focus(self.inner, browsing_context_id.get(), focused)
327 } {
328 Ok(())
329 } else {
330 Err(Error::ConnectionFailed)
331 }
332 }
333
334 pub fn confirm_beforeunload(
336 &mut self,
337 browsing_context_id: TabId,
338 request_id: u64,
339 proceed: bool,
340 ) -> Result<(), Error> {
341 if self.inner.is_null() {
342 return Err(Error::ConnectionFailed);
343 }
344
345 if unsafe {
346 cbf_bridge_client_confirm_beforeunload(
347 self.inner,
348 browsing_context_id.get(),
349 request_id,
350 proceed,
351 )
352 } {
353 Ok(())
354 } else {
355 Err(Error::ConnectionFailed)
356 }
357 }
358
359 pub fn respond_javascript_dialog(
361 &mut self,
362 browsing_context_id: TabId,
363 request_id: u64,
364 accept: bool,
365 prompt_text: Option<&str>,
366 ) -> Result<(), Error> {
367 if self.inner.is_null() {
368 return Err(Error::ConnectionFailed);
369 }
370
371 let prompt_text = to_optional_cstring(&prompt_text.map(ToOwned::to_owned))
372 .map_err(|_| Error::InvalidInput)?;
373
374 if unsafe {
375 cbf_bridge_client_respond_javascript_dialog(
376 self.inner,
377 browsing_context_id.get(),
378 request_id,
379 accept,
380 prompt_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
381 )
382 } {
383 Ok(())
384 } else {
385 Err(Error::ConnectionFailed)
386 }
387 }
388
389 pub fn respond_extension_popup_javascript_dialog(
391 &mut self,
392 popup_id: PopupId,
393 request_id: u64,
394 accept: bool,
395 prompt_text: Option<&str>,
396 ) -> Result<(), Error> {
397 if self.inner.is_null() {
398 return Err(Error::ConnectionFailed);
399 }
400
401 let prompt_text = to_optional_cstring(&prompt_text.map(ToOwned::to_owned))
402 .map_err(|_| Error::InvalidInput)?;
403
404 if unsafe {
405 cbf_bridge_client_respond_extension_popup_javascript_dialog(
406 self.inner,
407 popup_id.get(),
408 request_id,
409 accept,
410 prompt_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
411 )
412 } {
413 Ok(())
414 } else {
415 Err(Error::ConnectionFailed)
416 }
417 }
418
419 pub fn navigate(&mut self, browsing_context_id: TabId, url: &str) -> Result<(), Error> {
421 if self.inner.is_null() {
422 return Err(Error::ConnectionFailed);
423 }
424
425 let url = CString::new(url).map_err(|_| Error::InvalidInput)?;
426
427 if unsafe {
428 cbf_bridge_client_navigate(self.inner, browsing_context_id.get(), url.as_ptr())
429 } {
430 Ok(())
431 } else {
432 Err(Error::ConnectionFailed)
433 }
434 }
435
436 pub fn go_back(&mut self, browsing_context_id: TabId) -> Result<(), Error> {
438 if self.inner.is_null() {
439 return Err(Error::ConnectionFailed);
440 }
441
442 if unsafe { cbf_bridge_client_go_back(self.inner, browsing_context_id.get()) } {
443 Ok(())
444 } else {
445 Err(Error::ConnectionFailed)
446 }
447 }
448
449 pub fn go_forward(&mut self, browsing_context_id: TabId) -> Result<(), Error> {
451 if self.inner.is_null() {
452 return Err(Error::ConnectionFailed);
453 }
454
455 if unsafe { cbf_bridge_client_go_forward(self.inner, browsing_context_id.get()) } {
456 Ok(())
457 } else {
458 Err(Error::ConnectionFailed)
459 }
460 }
461
462 pub fn reload(&mut self, browsing_context_id: TabId, ignore_cache: bool) -> Result<(), Error> {
464 if self.inner.is_null() {
465 return Err(Error::ConnectionFailed);
466 }
467
468 if unsafe { cbf_bridge_client_reload(self.inner, browsing_context_id.get(), ignore_cache) }
469 {
470 Ok(())
471 } else {
472 Err(Error::ConnectionFailed)
473 }
474 }
475
476 pub fn print_preview(&mut self, browsing_context_id: TabId) -> Result<(), Error> {
478 if self.inner.is_null() {
479 return Err(Error::ConnectionFailed);
480 }
481
482 if unsafe { cbf_bridge_client_print_preview(self.inner, browsing_context_id.get()) } {
483 Ok(())
484 } else {
485 Err(Error::ConnectionFailed)
486 }
487 }
488
489 pub fn open_dev_tools(&mut self, browsing_context_id: TabId) -> Result<(), Error> {
491 if self.inner.is_null() {
492 return Err(Error::ConnectionFailed);
493 }
494
495 if unsafe { cbf_bridge_client_open_dev_tools(self.inner, browsing_context_id.get()) } {
496 Ok(())
497 } else {
498 Err(Error::ConnectionFailed)
499 }
500 }
501
502 pub fn inspect_element(
504 &mut self,
505 browsing_context_id: TabId,
506 x: i32,
507 y: i32,
508 ) -> Result<(), Error> {
509 if self.inner.is_null() {
510 return Err(Error::ConnectionFailed);
511 }
512
513 if unsafe { cbf_bridge_client_inspect_element(self.inner, browsing_context_id.get(), x, y) }
514 {
515 Ok(())
516 } else {
517 Err(Error::ConnectionFailed)
518 }
519 }
520
521 pub fn get_tab_dom_html(
523 &mut self,
524 browsing_context_id: TabId,
525 request_id: u64,
526 ) -> Result<(), Error> {
527 if self.inner.is_null() {
528 return Err(Error::ConnectionFailed);
529 }
530
531 if unsafe {
532 cbf_bridge_client_get_tab_dom_html(self.inner, browsing_context_id.get(), request_id)
533 } {
534 Ok(())
535 } else {
536 Err(Error::ConnectionFailed)
537 }
538 }
539
540 pub fn open_default_prompt_ui(
542 &mut self,
543 profile_id: &str,
544 request_id: u64,
545 ) -> Result<(), Error> {
546 if self.inner.is_null() {
547 return Err(Error::ConnectionFailed);
548 }
549 let profile_id = CString::new(profile_id).map_err(|_| Error::InvalidInput)?;
550 if unsafe {
551 cbf_bridge_client_open_default_prompt_ui(self.inner, profile_id.as_ptr(), request_id)
552 } {
553 Ok(())
554 } else {
555 Err(Error::ConnectionFailed)
556 }
557 }
558
559 pub fn respond_prompt_ui(
561 &mut self,
562 profile_id: &str,
563 request_id: u64,
564 response: &PromptUiResponse,
565 ) -> Result<(), Error> {
566 if self.inner.is_null() {
567 return Err(Error::ConnectionFailed);
568 }
569 let profile_id = CString::new(profile_id).map_err(|_| Error::InvalidInput)?;
570 let (prompt_ui_kind, proceed, destination_path, report_abuse) = match response {
571 PromptUiResponse::PermissionPrompt { allow } => {
572 (CBF_PROMPT_UI_KIND_PERMISSION_PROMPT, *allow, None, false)
573 }
574 PromptUiResponse::DownloadPrompt {
575 allow,
576 destination_path,
577 } => (
578 CBF_PROMPT_UI_KIND_DOWNLOAD_PROMPT,
579 *allow,
580 to_optional_cstring(destination_path)?,
581 false,
582 ),
583 PromptUiResponse::ExtensionInstallPrompt { proceed } => (
584 CBF_PROMPT_UI_KIND_EXTENSION_INSTALL_PROMPT,
585 *proceed,
586 None,
587 false,
588 ),
589 PromptUiResponse::ExtensionUninstallPrompt {
590 proceed,
591 report_abuse,
592 } => (
593 CBF_PROMPT_UI_KIND_EXTENSION_UNINSTALL_PROMPT,
594 *proceed,
595 None,
596 *report_abuse,
597 ),
598 PromptUiResponse::PrintPreviewDialog { proceed } => (
599 CBF_PROMPT_UI_KIND_PRINT_PREVIEW_DIALOG,
600 *proceed,
601 None,
602 false,
603 ),
604 PromptUiResponse::Unknown => (CBF_PROMPT_UI_KIND_UNKNOWN, false, None, false),
605 };
606 if unsafe {
607 cbf_bridge_client_respond_prompt_ui(
608 self.inner,
609 profile_id.as_ptr(),
610 request_id,
611 prompt_ui_kind,
612 proceed,
613 report_abuse,
614 destination_path
615 .as_ref()
616 .map_or(ptr::null(), |path| path.as_ptr()),
617 )
618 } {
619 Ok(())
620 } else {
621 Err(Error::ConnectionFailed)
622 }
623 }
624
625 pub fn respond_prompt_ui_for_tab(
627 &mut self,
628 browsing_context_id: TabId,
629 request_id: u64,
630 response: &PromptUiResponse,
631 ) -> Result<(), Error> {
632 if self.inner.is_null() {
633 return Err(Error::ConnectionFailed);
634 }
635 let (prompt_ui_kind, proceed, destination_path, report_abuse) = match response {
636 PromptUiResponse::PermissionPrompt { allow } => {
637 (CBF_PROMPT_UI_KIND_PERMISSION_PROMPT, *allow, None, false)
638 }
639 PromptUiResponse::DownloadPrompt {
640 allow,
641 destination_path,
642 } => (
643 CBF_PROMPT_UI_KIND_DOWNLOAD_PROMPT,
644 *allow,
645 to_optional_cstring(destination_path)?,
646 false,
647 ),
648 PromptUiResponse::ExtensionInstallPrompt { proceed } => (
649 CBF_PROMPT_UI_KIND_EXTENSION_INSTALL_PROMPT,
650 *proceed,
651 None,
652 false,
653 ),
654 PromptUiResponse::ExtensionUninstallPrompt {
655 proceed,
656 report_abuse,
657 } => (
658 CBF_PROMPT_UI_KIND_EXTENSION_UNINSTALL_PROMPT,
659 *proceed,
660 None,
661 *report_abuse,
662 ),
663 PromptUiResponse::PrintPreviewDialog { proceed } => (
664 CBF_PROMPT_UI_KIND_PRINT_PREVIEW_DIALOG,
665 *proceed,
666 None,
667 false,
668 ),
669 PromptUiResponse::Unknown => (CBF_PROMPT_UI_KIND_UNKNOWN, false, None, false),
670 };
671 if unsafe {
672 cbf_bridge_client_respond_prompt_ui_for_tab(
673 self.inner,
674 browsing_context_id.get(),
675 request_id,
676 prompt_ui_kind,
677 proceed,
678 report_abuse,
679 destination_path
680 .as_ref()
681 .map_or(ptr::null(), |path| path.as_ptr()),
682 )
683 } {
684 Ok(())
685 } else {
686 Err(Error::ConnectionFailed)
687 }
688 }
689
690 pub fn close_prompt_ui(
692 &mut self,
693 profile_id: &str,
694 prompt_ui_id: PromptUiId,
695 ) -> Result<(), Error> {
696 if self.inner.is_null() {
697 return Err(Error::ConnectionFailed);
698 }
699 let profile_id = CString::new(profile_id).map_err(|_| Error::InvalidInput)?;
700 if unsafe {
701 cbf_bridge_client_close_prompt_ui(self.inner, profile_id.as_ptr(), prompt_ui_id.get())
702 } {
703 Ok(())
704 } else {
705 Err(Error::ConnectionFailed)
706 }
707 }
708
709 pub fn pause_download(&mut self, download_id: ChromeDownloadId) -> Result<(), Error> {
711 if self.inner.is_null() {
712 return Err(Error::ConnectionFailed);
713 }
714 if unsafe { cbf_bridge_client_pause_download(self.inner, download_id.get()) } {
715 Ok(())
716 } else {
717 Err(Error::ConnectionFailed)
718 }
719 }
720
721 pub fn resume_download(&mut self, download_id: ChromeDownloadId) -> Result<(), Error> {
723 if self.inner.is_null() {
724 return Err(Error::ConnectionFailed);
725 }
726 if unsafe { cbf_bridge_client_resume_download(self.inner, download_id.get()) } {
727 Ok(())
728 } else {
729 Err(Error::ConnectionFailed)
730 }
731 }
732
733 pub fn cancel_download(&mut self, download_id: ChromeDownloadId) -> Result<(), Error> {
735 if self.inner.is_null() {
736 return Err(Error::ConnectionFailed);
737 }
738 if unsafe { cbf_bridge_client_cancel_download(self.inner, download_id.get()) } {
739 Ok(())
740 } else {
741 Err(Error::ConnectionFailed)
742 }
743 }
744
745 pub fn respond_tab_open(
747 &mut self,
748 request_id: u64,
749 response: &ChromeBrowsingContextOpenResponse,
750 ) -> Result<(), Error> {
751 if self.inner.is_null() {
752 return Err(Error::ConnectionFailed);
753 }
754 let (response_kind, target_tab_id, activate) = match response {
755 ChromeBrowsingContextOpenResponse::AllowNewContext { activate } => {
756 (CBF_TAB_OPEN_RESPONSE_ALLOW_NEW_CONTEXT, 0, *activate)
757 }
758 ChromeBrowsingContextOpenResponse::AllowExistingContext { tab_id, activate } => (
759 CBF_TAB_OPEN_RESPONSE_ALLOW_EXISTING_CONTEXT,
760 tab_id.get(),
761 *activate,
762 ),
763 ChromeBrowsingContextOpenResponse::Deny => (CBF_TAB_OPEN_RESPONSE_DENY, 0, false),
764 };
765 if unsafe {
766 cbf_bridge_client_respond_tab_open(
767 self.inner,
768 request_id,
769 response_kind,
770 target_tab_id,
771 activate,
772 )
773 } {
774 Ok(())
775 } else {
776 Err(Error::ConnectionFailed)
777 }
778 }
779
780 pub fn respond_window_open(
784 &mut self,
785 request_id: u64,
786 response: &WindowOpenResponse,
787 ) -> Result<(), Error> {
788 let tab_open_response = match response {
789 WindowOpenResponse::AllowExistingWindow { .. }
790 | WindowOpenResponse::AllowNewWindow { .. } => {
791 ChromeBrowsingContextOpenResponse::AllowNewContext { activate: true }
792 }
793 WindowOpenResponse::Deny => ChromeBrowsingContextOpenResponse::Deny,
794 };
795 self.respond_tab_open(request_id, &tab_open_response)
796 }
797
798 pub fn send_key_event_raw(
800 &mut self,
801 browsing_context_id: TabId,
802 event: &ChromeKeyEvent,
803 commands: &[String],
804 ) -> Result<(), Error> {
805 if self.inner.is_null() {
806 return Err(Error::ConnectionFailed);
807 }
808
809 let dom_code = to_optional_cstring(&event.dom_code)?;
810 let dom_key = to_optional_cstring(&event.dom_key)?;
811 let text = to_optional_cstring(&event.text)?;
812 let unmodified_text = to_optional_cstring(&event.unmodified_text)?;
813
814 let command_cstrings = commands
815 .iter()
816 .map(|command| CString::new(command.as_str()).map_err(|_| Error::InvalidInput))
817 .collect::<Result<Vec<_>, _>>()?;
818 let command_ptrs: Vec<*const std::os::raw::c_char> =
819 command_cstrings.iter().map(|cstr| cstr.as_ptr()).collect();
820
821 let ffi_event = CbfKeyEvent {
822 tab_id: browsing_context_id.get(),
823 type_: key_event_type_to_ffi(event.type_),
824 modifiers: event.modifiers,
825 windows_key_code: event.windows_key_code,
826 native_key_code: event.native_key_code,
827 dom_code: dom_code.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
828 dom_key: dom_key.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
829 text: text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
830 unmodified_text: unmodified_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
831 auto_repeat: event.auto_repeat,
832 is_keypad: event.is_keypad,
833 is_system_key: event.is_system_key,
834 location: event.location,
835 };
836
837 let ffi_commands = CbfCommandList {
838 items: if command_ptrs.is_empty() {
839 ptr::null()
840 } else {
841 command_ptrs.as_ptr()
842 },
843 len: command_ptrs.len() as u32,
844 };
845
846 if unsafe { cbf_bridge_client_send_key_event(self.inner, &ffi_event, &ffi_commands) } {
847 Ok(())
848 } else {
849 Err(Error::ConnectionFailed)
850 }
851 }
852
853 pub fn send_extension_popup_key_event_raw(
855 &mut self,
856 popup_id: PopupId,
857 event: &ChromeKeyEvent,
858 commands: &[String],
859 ) -> Result<(), Error> {
860 if self.inner.is_null() {
861 return Err(Error::ConnectionFailed);
862 }
863
864 let dom_code = to_optional_cstring(&event.dom_code)?;
865 let dom_key = to_optional_cstring(&event.dom_key)?;
866 let text = to_optional_cstring(&event.text)?;
867 let unmodified_text = to_optional_cstring(&event.unmodified_text)?;
868
869 let command_cstrings = commands
870 .iter()
871 .map(|command| CString::new(command.as_str()).map_err(|_| Error::InvalidInput))
872 .collect::<Result<Vec<_>, _>>()?;
873 let command_ptrs: Vec<*const std::os::raw::c_char> =
874 command_cstrings.iter().map(|cstr| cstr.as_ptr()).collect();
875
876 let ffi_event = CbfKeyEvent {
877 tab_id: 0,
878 type_: key_event_type_to_ffi(event.type_),
879 modifiers: event.modifiers,
880 windows_key_code: event.windows_key_code,
881 native_key_code: event.native_key_code,
882 dom_code: dom_code.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
883 dom_key: dom_key.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
884 text: text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
885 unmodified_text: unmodified_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
886 auto_repeat: event.auto_repeat,
887 is_keypad: event.is_keypad,
888 is_system_key: event.is_system_key,
889 location: event.location,
890 };
891
892 let ffi_commands = CbfCommandList {
893 items: if command_ptrs.is_empty() {
894 ptr::null()
895 } else {
896 command_ptrs.as_ptr()
897 },
898 len: command_ptrs.len() as u32,
899 };
900
901 if unsafe {
902 cbf_bridge_client_send_extension_popup_key_event(
903 self.inner,
904 popup_id.get(),
905 &ffi_event,
906 &ffi_commands,
907 )
908 } {
909 Ok(())
910 } else {
911 Err(Error::ConnectionFailed)
912 }
913 }
914
915 pub fn send_mouse_event(
917 &mut self,
918 browsing_context_id: TabId,
919 event: &ChromeMouseEvent,
920 ) -> Result<(), Error> {
921 if self.inner.is_null() {
922 return Err(Error::ConnectionFailed);
923 }
924
925 let ffi_event = CbfMouseEvent {
926 tab_id: browsing_context_id.get(),
927 type_: mouse_event_type_to_ffi(event.type_),
928 modifiers: event.modifiers,
929 button: mouse_button_to_ffi(event.button),
930 click_count: event.click_count,
931 position_in_widget_x: event.position_in_widget_x,
932 position_in_widget_y: event.position_in_widget_y,
933 position_in_screen_x: event.position_in_screen_x,
934 position_in_screen_y: event.position_in_screen_y,
935 movement_x: event.movement_x,
936 movement_y: event.movement_y,
937 is_raw_movement_event: event.is_raw_movement_event,
938 pointer_type: pointer_type_to_ffi(event.pointer_type),
939 };
940
941 if unsafe { cbf_bridge_client_send_mouse_event(self.inner, &ffi_event) } {
942 Ok(())
943 } else {
944 Err(Error::ConnectionFailed)
945 }
946 }
947
948 pub fn send_extension_popup_mouse_event(
950 &mut self,
951 popup_id: PopupId,
952 event: &ChromeMouseEvent,
953 ) -> Result<(), Error> {
954 if self.inner.is_null() {
955 return Err(Error::ConnectionFailed);
956 }
957
958 let ffi_event = CbfMouseEvent {
959 tab_id: 0,
960 type_: mouse_event_type_to_ffi(event.type_),
961 modifiers: event.modifiers,
962 button: mouse_button_to_ffi(event.button),
963 click_count: event.click_count,
964 position_in_widget_x: event.position_in_widget_x,
965 position_in_widget_y: event.position_in_widget_y,
966 position_in_screen_x: event.position_in_screen_x,
967 position_in_screen_y: event.position_in_screen_y,
968 movement_x: event.movement_x,
969 movement_y: event.movement_y,
970 is_raw_movement_event: event.is_raw_movement_event,
971 pointer_type: pointer_type_to_ffi(event.pointer_type),
972 };
973
974 if unsafe {
975 cbf_bridge_client_send_extension_popup_mouse_event(
976 self.inner,
977 popup_id.get(),
978 &ffi_event,
979 )
980 } {
981 Ok(())
982 } else {
983 Err(Error::ConnectionFailed)
984 }
985 }
986
987 pub fn send_mouse_wheel_event_raw(
989 &mut self,
990 browsing_context_id: TabId,
991 event: &ChromeMouseWheelEvent,
992 ) -> Result<(), Error> {
993 if self.inner.is_null() {
994 return Err(Error::ConnectionFailed);
995 }
996
997 let ffi_event = CbfMouseWheelEvent {
998 tab_id: browsing_context_id.get(),
999 modifiers: event.modifiers,
1000 position_in_widget_x: event.position_in_widget_x,
1001 position_in_widget_y: event.position_in_widget_y,
1002 position_in_screen_x: event.position_in_screen_x,
1003 position_in_screen_y: event.position_in_screen_y,
1004 movement_x: event.movement_x,
1005 movement_y: event.movement_y,
1006 is_raw_movement_event: event.is_raw_movement_event,
1007 delta_x: event.delta_x,
1008 delta_y: event.delta_y,
1009 wheel_ticks_x: event.wheel_ticks_x,
1010 wheel_ticks_y: event.wheel_ticks_y,
1011 phase: event.phase,
1012 momentum_phase: event.momentum_phase,
1013 delta_units: scroll_granularity_to_ffi(event.delta_units),
1014 };
1015
1016 if unsafe { cbf_bridge_client_send_mouse_wheel_event(self.inner, &ffi_event) } {
1017 Ok(())
1018 } else {
1019 Err(Error::ConnectionFailed)
1020 }
1021 }
1022
1023 pub fn send_extension_popup_mouse_wheel_event_raw(
1025 &mut self,
1026 popup_id: PopupId,
1027 event: &ChromeMouseWheelEvent,
1028 ) -> Result<(), Error> {
1029 if self.inner.is_null() {
1030 return Err(Error::ConnectionFailed);
1031 }
1032
1033 let ffi_event = CbfMouseWheelEvent {
1034 tab_id: 0,
1035 modifiers: event.modifiers,
1036 position_in_widget_x: event.position_in_widget_x,
1037 position_in_widget_y: event.position_in_widget_y,
1038 position_in_screen_x: event.position_in_screen_x,
1039 position_in_screen_y: event.position_in_screen_y,
1040 movement_x: event.movement_x,
1041 movement_y: event.movement_y,
1042 is_raw_movement_event: event.is_raw_movement_event,
1043 delta_x: event.delta_x,
1044 delta_y: event.delta_y,
1045 wheel_ticks_x: event.wheel_ticks_x,
1046 wheel_ticks_y: event.wheel_ticks_y,
1047 phase: event.phase,
1048 momentum_phase: event.momentum_phase,
1049 delta_units: scroll_granularity_to_ffi(event.delta_units),
1050 };
1051
1052 if unsafe {
1053 cbf_bridge_client_send_extension_popup_mouse_wheel_event(
1054 self.inner,
1055 popup_id.get(),
1056 &ffi_event,
1057 )
1058 } {
1059 Ok(())
1060 } else {
1061 Err(Error::ConnectionFailed)
1062 }
1063 }
1064
1065 pub fn send_drag_update(&mut self, update: &ChromeDragUpdate) -> Result<(), Error> {
1067 if self.inner.is_null() {
1068 return Err(Error::ConnectionFailed);
1069 }
1070
1071 let ffi_update = CbfDragUpdate {
1072 session_id: update.session_id,
1073 tab_id: update.browsing_context_id.get(),
1074 allowed_operations: update.allowed_operations.bits(),
1075 modifiers: update.modifiers,
1076 position_in_widget_x: update.position_in_widget_x,
1077 position_in_widget_y: update.position_in_widget_y,
1078 position_in_screen_x: update.position_in_screen_x,
1079 position_in_screen_y: update.position_in_screen_y,
1080 };
1081
1082 if unsafe { cbf_bridge_client_send_drag_update(self.inner, &ffi_update) } {
1083 Ok(())
1084 } else {
1085 Err(Error::ConnectionFailed)
1086 }
1087 }
1088
1089 pub fn send_drag_drop(&mut self, drop: &ChromeDragDrop) -> Result<(), Error> {
1091 if self.inner.is_null() {
1092 return Err(Error::ConnectionFailed);
1093 }
1094
1095 let ffi_drop = CbfDragDrop {
1096 session_id: drop.session_id,
1097 tab_id: drop.browsing_context_id.get(),
1098 modifiers: drop.modifiers,
1099 position_in_widget_x: drop.position_in_widget_x,
1100 position_in_widget_y: drop.position_in_widget_y,
1101 position_in_screen_x: drop.position_in_screen_x,
1102 position_in_screen_y: drop.position_in_screen_y,
1103 };
1104
1105 if unsafe { cbf_bridge_client_send_drag_drop(self.inner, &ffi_drop) } {
1106 Ok(())
1107 } else {
1108 Err(Error::ConnectionFailed)
1109 }
1110 }
1111
1112 pub fn send_drag_cancel(
1114 &mut self,
1115 session_id: u64,
1116 browsing_context_id: TabId,
1117 ) -> Result<(), Error> {
1118 if self.inner.is_null() {
1119 return Err(Error::ConnectionFailed);
1120 }
1121
1122 if unsafe {
1123 cbf_bridge_client_send_drag_cancel(self.inner, session_id, browsing_context_id.get())
1124 } {
1125 Ok(())
1126 } else {
1127 Err(Error::ConnectionFailed)
1128 }
1129 }
1130
1131 pub fn set_composition(&mut self, composition: &ChromeImeComposition) -> Result<(), Error> {
1133 if self.inner.is_null() {
1134 return Err(Error::ConnectionFailed);
1135 }
1136
1137 let text = CString::new(composition.text.as_str()).map_err(|_| Error::InvalidInput)?;
1138 let spans = to_ffi_ime_text_spans(&composition.spans);
1139 let span_list = CbfImeTextSpanList {
1140 items: if spans.is_empty() {
1141 ptr::null()
1142 } else {
1143 spans.as_ptr()
1144 },
1145 len: spans.len() as u32,
1146 };
1147 let (replacement_start, replacement_end) = ime_range_to_ffi(&composition.replacement_range);
1148
1149 let ffi_composition = CbfImeComposition {
1150 tab_id: composition.browsing_context_id.get(),
1151 text: text.as_ptr(),
1152 selection_start: composition.selection_start,
1153 selection_end: composition.selection_end,
1154 replacement_range_start: replacement_start,
1155 replacement_range_end: replacement_end,
1156 spans: span_list,
1157 };
1158
1159 if unsafe { cbf_bridge_client_set_composition(self.inner, &ffi_composition) } {
1160 Ok(())
1161 } else {
1162 Err(Error::ConnectionFailed)
1163 }
1164 }
1165
1166 pub fn set_extension_popup_composition(
1168 &mut self,
1169 composition: &ChromeTransientImeComposition,
1170 ) -> Result<(), Error> {
1171 if self.inner.is_null() {
1172 return Err(Error::ConnectionFailed);
1173 }
1174
1175 let text = CString::new(composition.text.as_str()).map_err(|_| Error::InvalidInput)?;
1176 let spans = to_ffi_ime_text_spans(&composition.spans);
1177 let span_list = CbfImeTextSpanList {
1178 items: if spans.is_empty() {
1179 ptr::null()
1180 } else {
1181 spans.as_ptr()
1182 },
1183 len: spans.len() as u32,
1184 };
1185 let (replacement_start, replacement_end) = ime_range_to_ffi(&composition.replacement_range);
1186
1187 let ffi_composition = CbfImeComposition {
1188 tab_id: 0,
1189 text: text.as_ptr(),
1190 selection_start: composition.selection_start,
1191 selection_end: composition.selection_end,
1192 replacement_range_start: replacement_start,
1193 replacement_range_end: replacement_end,
1194 spans: span_list,
1195 };
1196
1197 if unsafe {
1198 cbf_bridge_client_set_extension_popup_composition(
1199 self.inner,
1200 composition.popup_id.get(),
1201 &ffi_composition,
1202 )
1203 } {
1204 Ok(())
1205 } else {
1206 Err(Error::ConnectionFailed)
1207 }
1208 }
1209
1210 pub fn commit_text(&mut self, commit: &ChromeImeCommitText) -> Result<(), Error> {
1212 if self.inner.is_null() {
1213 return Err(Error::ConnectionFailed);
1214 }
1215
1216 let text = CString::new(commit.text.as_str()).map_err(|_| Error::InvalidInput)?;
1217 let spans = to_ffi_ime_text_spans(&commit.spans);
1218 let span_list = CbfImeTextSpanList {
1219 items: if spans.is_empty() {
1220 ptr::null()
1221 } else {
1222 spans.as_ptr()
1223 },
1224 len: spans.len() as u32,
1225 };
1226 let (replacement_start, replacement_end) = ime_range_to_ffi(&commit.replacement_range);
1227
1228 let ffi_commit = CbfImeCommitText {
1229 tab_id: commit.browsing_context_id.get(),
1230 text: text.as_ptr(),
1231 relative_caret_position: commit.relative_caret_position,
1232 replacement_range_start: replacement_start,
1233 replacement_range_end: replacement_end,
1234 spans: span_list,
1235 };
1236
1237 if unsafe { cbf_bridge_client_commit_text(self.inner, &ffi_commit) } {
1238 Ok(())
1239 } else {
1240 Err(Error::ConnectionFailed)
1241 }
1242 }
1243
1244 pub fn commit_extension_popup_text(
1246 &mut self,
1247 commit: &ChromeTransientImeCommitText,
1248 ) -> Result<(), Error> {
1249 if self.inner.is_null() {
1250 return Err(Error::ConnectionFailed);
1251 }
1252
1253 let text = CString::new(commit.text.as_str()).map_err(|_| Error::InvalidInput)?;
1254 let spans = to_ffi_ime_text_spans(&commit.spans);
1255 let span_list = CbfImeTextSpanList {
1256 items: if spans.is_empty() {
1257 ptr::null()
1258 } else {
1259 spans.as_ptr()
1260 },
1261 len: spans.len() as u32,
1262 };
1263 let (replacement_start, replacement_end) = ime_range_to_ffi(&commit.replacement_range);
1264
1265 let ffi_commit = CbfImeCommitText {
1266 tab_id: 0,
1267 text: text.as_ptr(),
1268 relative_caret_position: commit.relative_caret_position,
1269 replacement_range_start: replacement_start,
1270 replacement_range_end: replacement_end,
1271 spans: span_list,
1272 };
1273
1274 if unsafe {
1275 cbf_bridge_client_commit_extension_popup_text(
1276 self.inner,
1277 commit.popup_id.get(),
1278 &ffi_commit,
1279 )
1280 } {
1281 Ok(())
1282 } else {
1283 Err(Error::ConnectionFailed)
1284 }
1285 }
1286
1287 pub fn finish_composing_text(
1289 &mut self,
1290 browsing_context_id: TabId,
1291 behavior: ChromeConfirmCompositionBehavior,
1292 ) -> Result<(), Error> {
1293 if self.inner.is_null() {
1294 return Err(Error::ConnectionFailed);
1295 }
1296
1297 let behavior = match behavior {
1298 ChromeConfirmCompositionBehavior::DoNotKeepSelection => {
1299 CBF_IME_CONFIRM_DO_NOT_KEEP_SELECTION
1300 }
1301 ChromeConfirmCompositionBehavior::KeepSelection => CBF_IME_CONFIRM_KEEP_SELECTION,
1302 };
1303
1304 if unsafe {
1305 cbf_bridge_client_finish_composing_text(self.inner, browsing_context_id.get(), behavior)
1306 } {
1307 Ok(())
1308 } else {
1309 Err(Error::ConnectionFailed)
1310 }
1311 }
1312
1313 pub fn finish_extension_popup_composing_text(
1315 &mut self,
1316 popup_id: PopupId,
1317 behavior: ChromeConfirmCompositionBehavior,
1318 ) -> Result<(), Error> {
1319 if self.inner.is_null() {
1320 return Err(Error::ConnectionFailed);
1321 }
1322
1323 let behavior = match behavior {
1324 ChromeConfirmCompositionBehavior::DoNotKeepSelection => {
1325 CBF_IME_CONFIRM_DO_NOT_KEEP_SELECTION
1326 }
1327 ChromeConfirmCompositionBehavior::KeepSelection => CBF_IME_CONFIRM_KEEP_SELECTION,
1328 };
1329
1330 if unsafe {
1331 cbf_bridge_client_finish_extension_popup_composing_text(
1332 self.inner,
1333 popup_id.get(),
1334 behavior,
1335 )
1336 } {
1337 Ok(())
1338 } else {
1339 Err(Error::ConnectionFailed)
1340 }
1341 }
1342
1343 pub fn set_extension_popup_focus(
1344 &mut self,
1345 popup_id: PopupId,
1346 focused: bool,
1347 ) -> Result<(), Error> {
1348 if self.inner.is_null() {
1349 return Err(Error::ConnectionFailed);
1350 }
1351
1352 if unsafe {
1353 cbf_bridge_client_set_extension_popup_focus(self.inner, popup_id.get(), focused)
1354 } {
1355 Ok(())
1356 } else {
1357 Err(Error::ConnectionFailed)
1358 }
1359 }
1360
1361 pub fn set_extension_popup_size(
1362 &mut self,
1363 popup_id: PopupId,
1364 width: u32,
1365 height: u32,
1366 ) -> Result<(), Error> {
1367 if self.inner.is_null() {
1368 return Err(Error::ConnectionFailed);
1369 }
1370
1371 if unsafe {
1372 cbf_bridge_client_set_extension_popup_size(self.inner, popup_id.get(), width, height)
1373 } {
1374 Ok(())
1375 } else {
1376 Err(Error::ConnectionFailed)
1377 }
1378 }
1379
1380 pub fn close_extension_popup(&mut self, popup_id: PopupId) -> Result<(), Error> {
1381 if self.inner.is_null() {
1382 return Err(Error::ConnectionFailed);
1383 }
1384
1385 if unsafe { cbf_bridge_client_close_extension_popup(self.inner, popup_id.get()) } {
1386 Ok(())
1387 } else {
1388 Err(Error::ConnectionFailed)
1389 }
1390 }
1391
1392 pub fn execute_edit_action(
1394 &mut self,
1395 browsing_context_id: TabId,
1396 action: EditAction,
1397 ) -> Result<(), Error> {
1398 if self.inner.is_null() {
1399 return Err(Error::ConnectionFailed);
1400 }
1401
1402 if unsafe {
1403 cbf_bridge_client_execute_edit_action(
1404 self.inner,
1405 browsing_context_id.get(),
1406 edit_action_to_ffi(action),
1407 )
1408 } {
1409 Ok(())
1410 } else {
1411 Err(Error::ConnectionFailed)
1412 }
1413 }
1414
1415 pub fn execute_extension_popup_edit_action(
1417 &mut self,
1418 popup_id: PopupId,
1419 action: EditAction,
1420 ) -> Result<(), Error> {
1421 if self.inner.is_null() {
1422 return Err(Error::ConnectionFailed);
1423 }
1424
1425 if unsafe {
1426 cbf_bridge_client_execute_extension_popup_edit_action(
1427 self.inner,
1428 popup_id.get(),
1429 edit_action_to_ffi(action),
1430 )
1431 } {
1432 Ok(())
1433 } else {
1434 Err(Error::ConnectionFailed)
1435 }
1436 }
1437
1438 pub fn execute_context_menu_command(
1440 &mut self,
1441 menu_id: u64,
1442 command_id: i32,
1443 event_flags: i32,
1444 ) -> Result<(), Error> {
1445 if self.inner.is_null() {
1446 return Err(Error::ConnectionFailed);
1447 }
1448
1449 if unsafe {
1450 cbf_bridge_client_execute_context_menu_command(
1451 self.inner,
1452 menu_id,
1453 command_id,
1454 event_flags,
1455 )
1456 } {
1457 Ok(())
1458 } else {
1459 Err(Error::ConnectionFailed)
1460 }
1461 }
1462
1463 pub fn accept_choice_menu_selection(
1465 &mut self,
1466 request_id: u64,
1467 indices: &[i32],
1468 ) -> Result<(), Error> {
1469 if self.inner.is_null() {
1470 return Err(Error::ConnectionFailed);
1471 }
1472
1473 let ffi_indices = CbfChoiceMenuSelectedIndices {
1474 items: indices.as_ptr(),
1475 len: indices.len() as u32,
1476 };
1477 if unsafe {
1478 cbf_bridge_client_accept_choice_menu_selection(self.inner, request_id, &ffi_indices)
1479 } {
1480 Ok(())
1481 } else {
1482 Err(Error::ConnectionFailed)
1483 }
1484 }
1485
1486 pub fn dismiss_choice_menu(&mut self, request_id: u64) -> Result<(), Error> {
1488 if self.inner.is_null() {
1489 return Err(Error::ConnectionFailed);
1490 }
1491
1492 if unsafe { cbf_bridge_client_dismiss_choice_menu(self.inner, request_id) } {
1493 Ok(())
1494 } else {
1495 Err(Error::ConnectionFailed)
1496 }
1497 }
1498
1499 pub fn dismiss_context_menu(&mut self, menu_id: u64) -> Result<(), Error> {
1501 if self.inner.is_null() {
1502 return Err(Error::ConnectionFailed);
1503 }
1504
1505 if unsafe { cbf_bridge_client_dismiss_context_menu(self.inner, menu_id) } {
1506 Ok(())
1507 } else {
1508 Err(Error::ConnectionFailed)
1509 }
1510 }
1511
1512 pub fn request_shutdown(&mut self, request_id: u64) -> Result<(), Error> {
1514 if self.inner.is_null() {
1515 return Err(Error::ConnectionFailed);
1516 }
1517
1518 if unsafe { cbf_bridge_client_request_shutdown(self.inner, request_id) } {
1519 Ok(())
1520 } else {
1521 Err(Error::ConnectionFailed)
1522 }
1523 }
1524
1525 pub fn confirm_shutdown(&mut self, request_id: u64, proceed: bool) -> Result<(), Error> {
1527 if self.inner.is_null() {
1528 return Err(Error::ConnectionFailed);
1529 }
1530
1531 if unsafe { cbf_bridge_client_confirm_shutdown(self.inner, request_id, proceed) } {
1532 Ok(())
1533 } else {
1534 Err(Error::ConnectionFailed)
1535 }
1536 }
1537
1538 pub fn force_shutdown(&mut self) -> Result<(), Error> {
1540 if self.inner.is_null() {
1541 return Err(Error::ConnectionFailed);
1542 }
1543
1544 if unsafe { cbf_bridge_client_force_shutdown(self.inner) } {
1545 Ok(())
1546 } else {
1547 Err(Error::ConnectionFailed)
1548 }
1549 }
1550
1551 pub fn shutdown(&mut self) {
1553 if !self.inner.is_null() {
1554 unsafe { cbf_bridge_client_shutdown(self.inner) };
1555 }
1556 }
1557}
1558
1559impl IpcEventWaitHandle {
1560 pub(crate) fn wait_for_event(
1561 &self,
1562 timeout: Option<Duration>,
1563 ) -> Result<EventWaitResult, Error> {
1564 wait_for_event_inner(self.inner, timeout)
1565 }
1566}
1567
1568fn wait_for_event_inner(
1569 inner: *mut CbfBridgeClientHandle,
1570 timeout: Option<Duration>,
1571) -> Result<EventWaitResult, Error> {
1572 if inner.is_null() {
1573 return Ok(EventWaitResult::Closed);
1574 }
1575
1576 let timeout_ms = timeout
1577 .map(|value| value.as_millis().min(i64::MAX as u128) as i64)
1578 .unwrap_or(-1);
1579 let status = unsafe { cbf_bridge_client_wait_for_event(inner, timeout_ms) };
1580
1581 match status {
1582 CBF_BRIDGE_EVENT_WAIT_STATUS_EVENT_AVAILABLE => Ok(EventWaitResult::EventAvailable),
1583 CBF_BRIDGE_EVENT_WAIT_STATUS_TIMED_OUT => Ok(EventWaitResult::TimedOut),
1584 CBF_BRIDGE_EVENT_WAIT_STATUS_DISCONNECTED => Ok(EventWaitResult::Disconnected),
1585 CBF_BRIDGE_EVENT_WAIT_STATUS_CLOSED => Ok(EventWaitResult::Closed),
1586 _ => Err(Error::InvalidEvent),
1587 }
1588}
1589
1590fn edit_action_to_ffi(action: EditAction) -> u8 {
1591 match action {
1592 EditAction::Undo => CBF_EDIT_ACTION_UNDO,
1593 EditAction::Redo => CBF_EDIT_ACTION_REDO,
1594 EditAction::Cut => CBF_EDIT_ACTION_CUT,
1595 EditAction::Copy => CBF_EDIT_ACTION_COPY,
1596 EditAction::Paste => CBF_EDIT_ACTION_PASTE,
1597 EditAction::SelectAll => CBF_EDIT_ACTION_SELECT_ALL,
1598 }
1599}
1600
1601impl Drop for IpcClient {
1602 fn drop(&mut self) {
1603 if !self.inner.is_null() {
1604 unsafe { cbf_bridge_client_destroy(self.inner) };
1605 self.inner = ptr::null_mut();
1606 }
1607 }
1608}