Skip to main content

libghostty_vt_sys/
lib.rs

1#![allow(non_camel_case_types)]
2#![allow(non_snake_case)]
3#![allow(non_upper_case_globals)]
4#![allow(clippy::all)]
5#![allow(rustdoc::all)]
6
7mod bindings;
8
9use std::ops::Deref;
10
11pub use bindings::*;
12
13/// Initialize a "sized" FFI object.
14#[macro_export]
15macro_rules! sized {
16    ($ty:ty) => {{
17        let mut t = <$ty as ::std::default::Default>::default();
18        t.size = ::std::mem::size_of::<$ty>();
19        t
20    }};
21}
22
23impl<S> From<S> for GhosttyString
24where
25    S: Deref<Target = str>,
26{
27    fn from(value: S) -> Self {
28        Self {
29            ptr: value.as_ptr(),
30            len: value.len(),
31        }
32    }
33}
34
35impl GhosttyString {
36    /// # Safety
37    ///
38    /// The caller must uphold that the associated lifetime is valid
39    /// with the given context behind the FFI string, and that it contains
40    /// valid UTF-8 data.
41    pub unsafe fn to_str<'a>(self) -> &'a str {
42        // SAFETY: To be upheld by caller
43        let slice = unsafe { std::slice::from_raw_parts(self.ptr, self.len) };
44        unsafe { std::str::from_utf8_unchecked(slice) }
45    }
46}
47
48/// Canonical list of exported `libghostty-vt` C functions represented by checked-in bindings.
49pub const EXPORTED_API_SYMBOLS: &[&str] = &[
50    "ghostty_build_info",
51    "ghostty_cell_get",
52    "ghostty_color_rgb_get",
53    "ghostty_focus_encode",
54    "ghostty_formatter_format_alloc",
55    "ghostty_formatter_format_buf",
56    "ghostty_formatter_free",
57    "ghostty_formatter_terminal_new",
58    "ghostty_grid_ref_cell",
59    "ghostty_grid_ref_graphemes",
60    "ghostty_grid_ref_row",
61    "ghostty_grid_ref_style",
62    "ghostty_key_encoder_encode",
63    "ghostty_key_encoder_free",
64    "ghostty_key_encoder_new",
65    "ghostty_key_encoder_setopt",
66    "ghostty_key_encoder_setopt_from_terminal",
67    "ghostty_key_event_free",
68    "ghostty_key_event_get_action",
69    "ghostty_key_event_get_composing",
70    "ghostty_key_event_get_consumed_mods",
71    "ghostty_key_event_get_key",
72    "ghostty_key_event_get_mods",
73    "ghostty_key_event_get_unshifted_codepoint",
74    "ghostty_key_event_get_utf8",
75    "ghostty_key_event_new",
76    "ghostty_key_event_set_action",
77    "ghostty_key_event_set_composing",
78    "ghostty_key_event_set_consumed_mods",
79    "ghostty_key_event_set_key",
80    "ghostty_key_event_set_mods",
81    "ghostty_key_event_set_unshifted_codepoint",
82    "ghostty_key_event_set_utf8",
83    "ghostty_mode_report_encode",
84    "ghostty_mouse_encoder_encode",
85    "ghostty_mouse_encoder_free",
86    "ghostty_mouse_encoder_new",
87    "ghostty_mouse_encoder_reset",
88    "ghostty_mouse_encoder_setopt",
89    "ghostty_mouse_encoder_setopt_from_terminal",
90    "ghostty_mouse_event_clear_button",
91    "ghostty_mouse_event_free",
92    "ghostty_mouse_event_get_action",
93    "ghostty_mouse_event_get_button",
94    "ghostty_mouse_event_get_mods",
95    "ghostty_mouse_event_get_position",
96    "ghostty_mouse_event_new",
97    "ghostty_mouse_event_set_action",
98    "ghostty_mouse_event_set_button",
99    "ghostty_mouse_event_set_mods",
100    "ghostty_mouse_event_set_position",
101    "ghostty_osc_command_data",
102    "ghostty_osc_command_type",
103    "ghostty_osc_end",
104    "ghostty_osc_free",
105    "ghostty_osc_new",
106    "ghostty_osc_next",
107    "ghostty_osc_reset",
108    "ghostty_paste_is_safe",
109    "ghostty_render_state_colors_get",
110    "ghostty_render_state_free",
111    "ghostty_render_state_get",
112    "ghostty_render_state_new",
113    "ghostty_render_state_row_cells_free",
114    "ghostty_render_state_row_cells_get",
115    "ghostty_render_state_row_cells_new",
116    "ghostty_render_state_row_cells_next",
117    "ghostty_render_state_row_cells_select",
118    "ghostty_render_state_row_get",
119    "ghostty_render_state_row_iterator_free",
120    "ghostty_render_state_row_iterator_new",
121    "ghostty_render_state_row_iterator_next",
122    "ghostty_render_state_row_set",
123    "ghostty_render_state_set",
124    "ghostty_render_state_update",
125    "ghostty_row_get",
126    "ghostty_sgr_attribute_tag",
127    "ghostty_sgr_attribute_value",
128    "ghostty_sgr_free",
129    "ghostty_sgr_new",
130    "ghostty_sgr_next",
131    "ghostty_sgr_reset",
132    "ghostty_sgr_set_params",
133    "ghostty_sgr_unknown_full",
134    "ghostty_sgr_unknown_partial",
135    "ghostty_size_report_encode",
136    "ghostty_style_default",
137    "ghostty_style_is_default",
138    "ghostty_terminal_free",
139    "ghostty_terminal_get",
140    "ghostty_terminal_grid_ref",
141    "ghostty_terminal_mode_get",
142    "ghostty_terminal_mode_set",
143    "ghostty_terminal_new",
144    "ghostty_terminal_reset",
145    "ghostty_terminal_resize",
146    "ghostty_terminal_scroll_viewport",
147    "ghostty_terminal_vt_write",
148];
149
150#[cfg(test)]
151mod tests {
152    use std::collections::BTreeSet;
153
154    use super::EXPORTED_API_SYMBOLS;
155
156    fn parse_binding_symbols(input: &str) -> BTreeSet<String> {
157        input
158            .lines()
159            .filter_map(|line| {
160                let line = line.trim();
161                if !line.starts_with("pub fn ghostty_") {
162                    return None;
163                }
164
165                let start = "pub fn ".len();
166                let rest = &line[start..];
167                let end = rest.find('(')?;
168                Some(rest[..end].to_owned())
169            })
170            .collect()
171    }
172
173    fn parse_header_symbols(input: &str) -> BTreeSet<String> {
174        let mut symbols = BTreeSet::new();
175        let mut statement = String::new();
176
177        for line in input.lines() {
178            let trimmed = line.trim();
179
180            if trimmed.starts_with('#') || trimmed.starts_with("//") || trimmed.is_empty() {
181                continue;
182            }
183
184            // Skip static inline functions (they are inlined, not exported symbols)
185            if trimmed.starts_with("static") {
186                continue;
187            }
188
189            if !statement.is_empty() {
190                statement.push(' ');
191            }
192            statement.push_str(trimmed);
193
194            if !trimmed.ends_with(';') && !trimmed.ends_with('{') {
195                continue;
196            }
197
198            if let Some(end) = statement.find('(') {
199                let before_paren = &statement[..end];
200                if let Some(candidate) = before_paren.split_whitespace().last() {
201                    // Strip leading * for pointer-returning functions
202                    let candidate = candidate.trim_start_matches('*');
203                    if candidate.starts_with("ghostty_")
204                        && candidate
205                            .chars()
206                            .all(|char| char.is_ascii_alphanumeric() || char == '_')
207                    {
208                        symbols.insert(candidate.to_owned());
209                    }
210                }
211            }
212
213            statement.clear();
214        }
215
216        symbols
217    }
218
219    #[test]
220    fn exported_manifest_matches_bindings() {
221        let from_bindings = parse_binding_symbols(include_str!("bindings.rs"));
222        let from_manifest: BTreeSet<String> = EXPORTED_API_SYMBOLS
223            .iter()
224            .map(|symbol| (*symbol).to_owned())
225            .collect();
226        assert_eq!(from_manifest, from_bindings);
227    }
228
229    #[test]
230    fn exported_manifest_is_sorted_and_unique() {
231        let mut prev = "";
232        for symbol in EXPORTED_API_SYMBOLS {
233            assert!(
234                *symbol > prev,
235                "EXPORTED_API_SYMBOLS is not sorted or has duplicates: {prev:?} >= {symbol:?}"
236            );
237            prev = symbol;
238        }
239    }
240}