arcdps_imgui/
clipboard.rs

1use std::ffi::{CStr, CString};
2use std::fmt;
3use std::os::raw::{c_char, c_void};
4use std::panic::catch_unwind;
5use std::process;
6use std::ptr;
7
8// use crate::string::{ImStr, ImString};
9use crate::Ui;
10
11/// Trait for clipboard backends
12pub trait ClipboardBackend: 'static {
13    /// Returns the current clipboard contents as an owned imgui-rs string, or None if the
14    /// clipboard is empty or inaccessible
15    fn get(&mut self) -> Option<String>;
16    /// Sets the clipboard contents to the given imgui-rs string slice.
17    fn set(&mut self, value: &str);
18}
19
20pub(crate) struct ClipboardContext {
21    backend: Box<dyn ClipboardBackend>,
22    // this is needed to keep ownership of the value when the raw C callback is called
23    last_value: CString,
24}
25
26impl ClipboardContext {
27    /// Creates a new [ClipboardContext]. This function previously took a `Box`, but now
28    /// is generic over the T it takes and boxes itself (which should be less strange).
29    pub fn new<T: ClipboardBackend>(backend: T) -> ClipboardContext {
30        ClipboardContext {
31            backend: Box::new(backend) as Box<dyn ClipboardBackend>,
32            last_value: CString::default(),
33        }
34    }
35
36    pub fn dummy() -> ClipboardContext {
37        Self {
38            backend: Box::new(DummyClipboardContext),
39            last_value: CString::default(),
40        }
41    }
42}
43
44pub struct DummyClipboardContext;
45impl ClipboardBackend for DummyClipboardContext {
46    fn get(&mut self) -> Option<String> {
47        None
48    }
49
50    fn set(&mut self, _: &str) {
51        // empty
52    }
53}
54
55impl fmt::Debug for ClipboardContext {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_struct("ClipboardContext")
58            // beautiful code, no?
59            .field("backend", &(&(*self.backend) as *const _))
60            .field("last_value", &self.last_value)
61            .finish()
62    }
63}
64
65pub(crate) unsafe extern "C" fn get_clipboard_text(user_data: *mut c_void) -> *const c_char {
66    let result = catch_unwind(|| {
67        let ctx = &mut *(user_data as *mut ClipboardContext);
68        match ctx.backend.get() {
69            Some(text) => {
70                ctx.last_value = CString::new(text).unwrap();
71                ctx.last_value.as_ptr()
72            }
73            None => ptr::null(),
74        }
75    });
76    result.unwrap_or_else(|_| {
77        eprintln!("Clipboard getter panicked");
78        process::abort();
79    })
80}
81
82pub(crate) unsafe extern "C" fn set_clipboard_text(user_data: *mut c_void, text: *const c_char) {
83    let result = catch_unwind(|| {
84        let ctx = &mut *(user_data as *mut ClipboardContext);
85        let text = CStr::from_ptr(text).to_owned();
86        ctx.backend.set(text.to_str().unwrap());
87    });
88    result.unwrap_or_else(|_| {
89        eprintln!("Clipboard setter panicked");
90        process::abort();
91    });
92}
93
94/// # Clipboard
95#[allow(clippy::fn_address_comparisons)] // This is allowed because although function addresses wont be unique, we just care if its OURS
96impl<'ui> Ui<'ui> {
97    /// Returns the current clipboard contents as text, or None if the clipboard is empty or cannot
98    /// be accessed
99    pub fn clipboard_text(&self) -> Option<String> {
100        let io = self.io();
101        io.get_clipboard_text_fn.and_then(|get_clipboard_text_fn| {
102            // Bypass FFI if we end up calling our own function anyway
103            if get_clipboard_text_fn == get_clipboard_text {
104                let ctx = unsafe { &mut *(io.clipboard_user_data as *mut ClipboardContext) };
105                ctx.backend.get()
106            } else {
107                unsafe {
108                    let text_ptr = get_clipboard_text_fn(io.clipboard_user_data);
109                    if text_ptr.is_null() || *text_ptr == b'\0' as c_char {
110                        None
111                    } else {
112                        Some(
113                            CStr::from_ptr(text_ptr)
114                                .to_owned()
115                                .to_str()
116                                .ok()?
117                                .to_owned(),
118                        )
119                    }
120                }
121            }
122        })
123    }
124
125    /// Sets the clipboard contents.
126    ///
127    /// Does nothing if the clipboard cannot be accessed.
128    pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
129        let io = self.io();
130        if let Some(set_clipboard_text_fn) = io.set_clipboard_text_fn {
131            // Bypass FFI if we end up calling our own function anyway
132            if set_clipboard_text_fn == set_clipboard_text {
133                let ctx = unsafe { &mut *(io.clipboard_user_data as *mut ClipboardContext) };
134                ctx.backend.set(text.as_ref());
135            } else {
136                unsafe {
137                    set_clipboard_text_fn(io.clipboard_user_data, self.scratch_txt(text));
138                }
139            }
140        }
141    }
142}