use std::{marker::PhantomData, mem::MaybeUninit};
use crate::{
alloc::{Allocator, Object},
error::{Error, Result, from_result},
ffi, key,
screen::GridRef,
style,
};
#[doc(inline)]
pub use ffi::GhosttySizeReportSize as SizeReportSize;
#[derive(Debug)]
pub struct Terminal<'alloc: 'cb, 'cb> {
pub(crate) inner: Object<'alloc, ffi::GhosttyTerminal>,
vtable: VTable<'alloc, 'cb>,
}
#[derive(Clone, Copy, Debug)]
pub struct Options {
pub cols: u16,
pub rows: u16,
pub max_scrollback: usize,
}
impl From<Options> for ffi::GhosttyTerminalOptions {
fn from(value: Options) -> Self {
Self {
cols: value.cols,
rows: value.rows,
max_scrollback: value.max_scrollback,
}
}
}
impl<'alloc: 'cb, 'cb> Terminal<'alloc, 'cb> {
pub fn new(opts: Options) -> Result<Self> {
unsafe { Self::new_inner(std::ptr::null(), opts) }
}
pub fn new_with_alloc<'ctx: 'alloc, Ctx>(
alloc: &'alloc Allocator<'ctx, Ctx>,
opts: Options,
) -> Result<Self> {
unsafe { Self::new_inner(alloc.to_raw(), opts) }
}
unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator, opts: Options) -> Result<Self> {
let mut raw: ffi::GhosttyTerminal_ptr = std::ptr::null_mut();
let result = unsafe { ffi::ghostty_terminal_new(alloc, &raw mut raw, opts.into()) };
from_result(result)?;
Ok(Self {
inner: Object::new(raw)?,
vtable: VTable::default(),
})
}
pub fn vt_write(&mut self, data: &[u8]) {
unsafe { ffi::ghostty_terminal_vt_write(self.inner.as_raw(), data.as_ptr(), data.len()) }
}
pub fn resize(
&mut self,
cols: u16,
rows: u16,
cell_width_px: u32,
cell_height_px: u32,
) -> Result<()> {
let result = unsafe {
ffi::ghostty_terminal_resize(
self.inner.as_raw(),
cols,
rows,
cell_width_px,
cell_height_px,
)
};
from_result(result)
}
pub fn reset(&mut self) {
unsafe { ffi::ghostty_terminal_reset(self.inner.as_raw()) }
}
pub fn scroll_viewport(&mut self, scroll: ScrollViewport) {
unsafe { ffi::ghostty_terminal_scroll_viewport(self.inner.as_raw(), scroll.into()) }
}
pub fn grid_ref(&self, point: Point) -> Result<GridRef<'_>> {
let mut grid_ref = ffi::sized!(ffi::GhosttyGridRef);
let result = unsafe {
ffi::ghostty_terminal_grid_ref(self.inner.as_raw(), point.into(), &raw mut grid_ref)
};
from_result(result)?;
Ok(GridRef {
inner: grid_ref,
_phan: PhantomData,
})
}
pub fn mode(&self, mode: Mode) -> Result<bool> {
let mut value = false;
let result = unsafe {
ffi::ghostty_terminal_mode_get(self.inner.as_raw(), mode.into(), &raw mut value)
};
from_result(result)?;
Ok(value)
}
pub fn set_mode(&mut self, mode: Mode, value: bool) -> Result<()> {
let result =
unsafe { ffi::ghostty_terminal_mode_set(self.inner.as_raw(), mode.into(), value) };
from_result(result)
}
fn get<T>(&self, tag: ffi::GhosttyTerminalData) -> Result<T> {
let mut value = MaybeUninit::<T>::zeroed();
let result = unsafe {
ffi::ghostty_terminal_get(self.inner.as_raw(), tag, value.as_mut_ptr().cast())
};
from_result(result)?;
Ok(unsafe { value.assume_init() })
}
fn set<T>(&self, tag: ffi::GhosttyTerminalOption, v: &T) -> Result<()> {
let result = unsafe {
ffi::ghostty_terminal_set(self.inner.as_raw(), tag, std::ptr::from_ref(v).cast())
};
from_result(result)
}
pub fn cols(&self) -> Result<u16> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_COLS)
}
pub fn rows(&self) -> Result<u16> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_ROWS)
}
pub fn cursor_x(&self) -> Result<u16> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_X)
}
pub fn cursor_y(&self) -> Result<u16> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_Y)
}
pub fn is_cursor_pending_wrap(&self) -> Result<bool> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_PENDING_WRAP)
}
pub fn is_cursor_visible(&self) -> Result<bool> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_VISIBLE)
}
pub fn cursor_style(&self) -> Result<style::Style> {
self.get::<ffi::GhosttyStyle>(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_STYLE)
.and_then(std::convert::TryInto::try_into)
}
pub fn kitty_keyboard_flags(&self) -> Result<key::KittyKeyFlags> {
self.get::<ffi::GhosttyKittyKeyFlags>(
ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_KITTY_KEYBOARD_FLAGS,
)
.map(key::KittyKeyFlags::from_bits_retain)
}
pub fn scrollbar(&self) -> Result<ffi::GhosttyTerminalScrollbar> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_SCROLLBAR)
}
pub fn active_screen(&self) -> Result<ffi::GhosttyTerminalScreen> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_ACTIVE_SCREEN)
}
pub fn is_mouse_tracking(&self) -> Result<bool> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_MOUSE_TRACKING)
}
pub fn title(&self) -> Result<&str> {
let str =
self.get::<ffi::GhosttyString>(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_TITLE)?;
let str = unsafe { std::slice::from_raw_parts(str.ptr, str.len) };
std::str::from_utf8(str).map_err(|_| Error::InvalidValue)
}
pub fn pwd(&self) -> Result<&str> {
let str =
self.get::<ffi::GhosttyString>(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_PWD)?;
let str = unsafe { std::slice::from_raw_parts(str.ptr, str.len) };
std::str::from_utf8(str).map_err(|_| Error::InvalidValue)
}
pub fn total_rows(&self) -> Result<usize> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_TOTAL_ROWS)
}
pub fn scrollback_rows(&self) -> Result<usize> {
self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_SCROLLBACK_ROWS)
}
}
impl Drop for Terminal<'_, '_> {
fn drop(&mut self) {
unsafe { ffi::ghostty_terminal_free(self.inner.as_raw()) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Point {
Active(PointCoordinate),
Viewport(PointCoordinate),
Screen(PointCoordinate),
History(PointCoordinate),
}
impl From<Point> for ffi::GhosttyPoint {
fn from(value: Point) -> Self {
match value {
Point::Active(coord) => Self {
tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_ACTIVE,
value: ffi::GhosttyPointValue {
coordinate: coord.into(),
},
},
Point::Viewport(coord) => Self {
tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_VIEWPORT,
value: ffi::GhosttyPointValue {
coordinate: coord.into(),
},
},
Point::Screen(coord) => Self {
tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_SCREEN,
value: ffi::GhosttyPointValue {
coordinate: coord.into(),
},
},
Point::History(coord) => Self {
tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_HISTORY,
value: ffi::GhosttyPointValue {
coordinate: coord.into(),
},
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PointCoordinate {
x: u16,
y: u32,
}
impl From<PointCoordinate> for ffi::GhosttyPointCoordinate {
fn from(value: PointCoordinate) -> Self {
let PointCoordinate { x, y } = value;
Self { x, y }
}
}
impl From<ffi::GhosttyPointCoordinate> for PointCoordinate {
fn from(value: ffi::GhosttyPointCoordinate) -> Self {
let ffi::GhosttyPointCoordinate { x, y } = value;
Self { x, y }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScrollViewport {
Top,
Bottom,
Delta(isize),
}
impl From<ScrollViewport> for ffi::GhosttyTerminalScrollViewport {
fn from(value: ScrollViewport) -> Self {
match value {
ScrollViewport::Top => Self {
tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_TOP,
value: ffi::GhosttyTerminalScrollViewportValue::default(),
},
ScrollViewport::Bottom => Self {
tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_BOTTOM,
value: ffi::GhosttyTerminalScrollViewportValue::default(),
},
ScrollViewport::Delta(delta) => Self {
tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_DELTA,
value: {
let mut v = ffi::GhosttyTerminalScrollViewportValue::default();
v.delta = delta;
v
},
},
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Mode(pub ffi::GhosttyMode);
impl Mode {
#![expect(missing_docs, reason = "no upstream documentation provided")]
const ANSI_BIT: u16 = 1 << 15;
#[must_use]
pub const fn new(v: u16, kind: ModeKind) -> Self {
match kind {
ModeKind::Ansi => Self(v | Self::ANSI_BIT),
ModeKind::Dec => Self(v),
}
}
#[must_use]
pub fn value(self) -> u16 {
(self.0) & 0x7fff
}
#[must_use]
pub fn kind(self) -> ModeKind {
if (self.0) & Self::ANSI_BIT > 0 {
ModeKind::Ansi
} else {
ModeKind::Dec
}
}
pub const KAM: Self = Self::new(2, ModeKind::Ansi);
pub const INSERT: Self = Self::new(4, ModeKind::Ansi);
pub const SRM: Self = Self::new(12, ModeKind::Ansi);
pub const LINEFEED: Self = Self::new(20, ModeKind::Ansi);
pub const DECCKM: Self = Self::new(1, ModeKind::Dec);
pub const _132_COLUMN: Self = Self::new(3, ModeKind::Dec);
pub const SLOW_SCROLL: Self = Self::new(4, ModeKind::Dec);
pub const REVERSE_COLORS: Self = Self::new(5, ModeKind::Dec);
pub const ORIGIN: Self = Self::new(6, ModeKind::Dec);
pub const WRAPAROUND: Self = Self::new(7, ModeKind::Dec);
pub const AUTOREPEAT: Self = Self::new(8, ModeKind::Dec);
pub const X10_MOUSE: Self = Self::new(9, ModeKind::Dec);
pub const CURSOR_BLINKING: Self = Self::new(12, ModeKind::Dec);
pub const CURSOR_VISIBLE: Self = Self::new(25, ModeKind::Dec);
pub const ENABLE_MODE3: Self = Self::new(40, ModeKind::Dec);
pub const REVERSE_WRAP: Self = Self::new(45, ModeKind::Dec);
pub const ALT_SCREEN_LEGACY: Self = Self::new(47, ModeKind::Dec);
pub const KEYPAD_KEYS: Self = Self::new(66, ModeKind::Dec);
pub const LEFT_RIGHT_MARGIN: Self = Self::new(69, ModeKind::Dec);
pub const NORMAL_MOUSE: Self = Self::new(1000, ModeKind::Dec);
pub const BUTTON_MOUSE: Self = Self::new(1002, ModeKind::Dec);
pub const ANY_MOUSE: Self = Self::new(1003, ModeKind::Dec);
pub const FOCUS_EVENT: Self = Self::new(1004, ModeKind::Dec);
pub const UTF8_MOUSE: Self = Self::new(1005, ModeKind::Dec);
pub const SGR_MOUSE: Self = Self::new(1006, ModeKind::Dec);
pub const ALT_SCROLL: Self = Self::new(1007, ModeKind::Dec);
pub const URXVT_MOUSE: Self = Self::new(1015, ModeKind::Dec);
pub const SGR_PIXELS_MOUSE: Self = Self::new(1016, ModeKind::Dec);
pub const NUMLOCK_KEYPAD: Self = Self::new(1035, ModeKind::Dec);
pub const ALT_ESC_PREFIX: Self = Self::new(1036, ModeKind::Dec);
pub const ALT_SENDS_ESC: Self = Self::new(1039, ModeKind::Dec);
pub const REVERSE_WRAP_EXT: Self = Self::new(1045, ModeKind::Dec);
pub const ALT_SCREEN: Self = Self::new(1047, ModeKind::Dec);
pub const SAVE_CURSOR: Self = Self::new(1048, ModeKind::Dec);
pub const ALT_SCREEN_SAVE: Self = Self::new(1049, ModeKind::Dec);
pub const BRACKETED_PASTE: Self = Self::new(2004, ModeKind::Dec);
pub const SYNC_OUTPUT: Self = Self::new(2026, ModeKind::Dec);
pub const GRAPHEME_CLUSTER: Self = Self::new(2027, ModeKind::Dec);
pub const COLOR_SCHEME_REPORT: Self = Self::new(2031, ModeKind::Dec);
pub const IN_BAND_RESIZE: Self = Self::new(2048, ModeKind::Dec);
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ModeKind {
Dec,
Ansi,
}
impl From<Mode> for ffi::GhosttyMode {
fn from(value: Mode) -> Self {
value.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct DeviceAttributes {
pub primary: PrimaryDeviceAttributes,
pub secondary: SecondaryDeviceAttributes,
pub tertiary: TertiaryDeviceAttributes,
}
impl From<DeviceAttributes> for ffi::GhosttyDeviceAttributes {
fn from(value: DeviceAttributes) -> Self {
Self {
primary: value.primary.into(),
secondary: value.secondary.into(),
tertiary: value.tertiary.into(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PrimaryDeviceAttributes(ffi::GhosttyDeviceAttributesPrimary);
impl PrimaryDeviceAttributes {
#[must_use]
pub fn new<const N: usize>(
conformance_level: ConformanceLevel,
features: [DeviceAttributeFeature; N],
) -> Self {
assert!(N <= 64);
let mut f = [0u16; 64];
f[..N].copy_from_slice(features.map(|f| f.0).as_slice());
Self(ffi::GhosttyDeviceAttributesPrimary {
conformance_level: conformance_level.0,
features: f,
num_features: N,
})
}
}
impl From<PrimaryDeviceAttributes> for ffi::GhosttyDeviceAttributesPrimary {
fn from(value: PrimaryDeviceAttributes) -> Self {
value.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConformanceLevel(pub u16);
impl ConformanceLevel {
#![allow(clippy::cast_possible_truncation, reason = "bindgen ain't perfect")]
#![expect(missing_docs, reason = "self-explanatory")]
pub const VT100: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT100 as u16);
pub const VT101: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT101 as u16);
pub const VT102: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT102 as u16);
pub const VT125: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT125 as u16);
pub const VT131: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT131 as u16);
pub const VT132: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT132 as u16);
pub const VT220: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT220 as u16);
pub const VT240: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT240 as u16);
pub const VT320: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT320 as u16);
pub const VT340: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT340 as u16);
pub const VT420: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT420 as u16);
pub const VT510: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT510 as u16);
pub const VT520: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT520 as u16);
pub const VT525: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT525 as u16);
pub const LEVEL_2: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_2 as u16);
pub const LEVEL_3: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_3 as u16);
pub const LEVEL_4: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_4 as u16);
pub const LEVEL_5: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_5 as u16);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DeviceAttributeFeature(pub u16);
impl DeviceAttributeFeature {
#![expect(clippy::cast_possible_truncation, reason = "bindgen ain't perfect")]
#![expect(missing_docs, reason = "no upstream documentation provided")]
pub const COLUMNS_132: Self = Self(ffi::GHOSTTY_DA_FEATURE_COLUMNS_132 as u16);
pub const PRINTER: Self = Self(ffi::GHOSTTY_DA_FEATURE_PRINTER as u16);
pub const REGIS: Self = Self(ffi::GHOSTTY_DA_FEATURE_REGIS as u16);
pub const SIXEL: Self = Self(ffi::GHOSTTY_DA_FEATURE_SIXEL as u16);
pub const SELECTIVE_ERASE: Self = Self(ffi::GHOSTTY_DA_FEATURE_SELECTIVE_ERASE as u16);
pub const USER_DEFINED_KEYS: Self = Self(ffi::GHOSTTY_DA_FEATURE_USER_DEFINED_KEYS as u16);
pub const NATIONAL_REPLACEMENT: Self =
Self(ffi::GHOSTTY_DA_FEATURE_NATIONAL_REPLACEMENT as u16);
pub const TECHNICAL_CHARACTERS: Self =
Self(ffi::GHOSTTY_DA_FEATURE_TECHNICAL_CHARACTERS as u16);
pub const LOCATOR: Self = Self(ffi::GHOSTTY_DA_FEATURE_LOCATOR as u16);
pub const TERMINAL_STATE: Self = Self(ffi::GHOSTTY_DA_FEATURE_TERMINAL_STATE as u16);
pub const WINDOWING: Self = Self(ffi::GHOSTTY_DA_FEATURE_WINDOWING as u16);
pub const HORIZONTAL_SCROLLING: Self =
Self(ffi::GHOSTTY_DA_FEATURE_HORIZONTAL_SCROLLING as u16);
pub const ANSI_COLOR: Self = Self(ffi::GHOSTTY_DA_FEATURE_ANSI_COLOR as u16);
pub const RECTANGULAR_EDITING: Self = Self(ffi::GHOSTTY_DA_FEATURE_RECTANGULAR_EDITING as u16);
pub const ANSI_TEXT_LOCATOR: Self = Self(ffi::GHOSTTY_DA_FEATURE_ANSI_TEXT_LOCATOR as u16);
pub const CLIPBOARD: Self = Self(ffi::GHOSTTY_DA_FEATURE_CLIPBOARD as u16);
}
#[derive(Debug, Copy, Clone)]
pub struct SecondaryDeviceAttributes {
pub device_type: DeviceType,
pub firmware_version: u16,
pub rom_cartridge: u16,
}
impl From<SecondaryDeviceAttributes> for ffi::GhosttyDeviceAttributesSecondary {
fn from(value: SecondaryDeviceAttributes) -> Self {
Self {
device_type: value.device_type.0,
firmware_version: value.firmware_version,
rom_cartridge: value.rom_cartridge,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DeviceType(pub u16);
impl DeviceType {
#![expect(clippy::cast_possible_truncation, reason = "bindgen ain't perfect")]
#![expect(missing_docs, reason = "self-explanatory")]
pub const VT100: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT100 as u16);
pub const VT220: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT220 as u16);
pub const VT240: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT240 as u16);
pub const VT330: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT330 as u16);
pub const VT340: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT340 as u16);
pub const VT320: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT320 as u16);
pub const VT382: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT382 as u16);
pub const VT420: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT420 as u16);
pub const VT510: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT510 as u16);
pub const VT520: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT520 as u16);
pub const VT525: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT525 as u16);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct TertiaryDeviceAttributes {
pub unit_id: u32,
}
impl From<TertiaryDeviceAttributes> for ffi::GhosttyDeviceAttributesTertiary {
fn from(value: TertiaryDeviceAttributes) -> Self {
Self {
unit_id: value.unit_id,
}
}
}
macro_rules! handlers {
{
$(
$(#[$fmeta:meta])*
$vis:vis fn $name:ident(
&mut self,
tag = $tag:ident,
from = $rawfnty:ident( $($rfname:ident: $rfty:ty),*$(,)? ) $(-> $rawrty:ty)?,
$(#[$tmeta:meta])*
to = $(<$lf:lifetime>)? $fnty:ident( $($fty:ty),*$(,)? ) $(-> $rty:ty)?,
) |$t:ident, $func:ident| $block:block
)*
} => {
impl<'alloc, 'cb> $crate::terminal::Terminal<'alloc, 'cb> {$(
$(#[$fmeta])*
$vis fn $name(&mut self, f: impl $fnty<'alloc, 'cb>) -> $crate::error::Result<&mut Self> {
unsafe extern "C" fn callback(
t: *mut $crate::ffi::GhosttyTerminal,
ud: *mut std::ffi::c_void,
$($rfname: $rfty),*
) $(-> $rawrty)? {
let vtable = unsafe { &mut *ud.cast::<VTable<'_, '_>>() };
let obj = $crate::alloc::Object::new(t).expect("received null terminal ptr in callback - this is a bug!");
let $t = $crate::terminal::Terminal::<'_, '_> {
inner: obj,
vtable: ::core::default::Default::default(),
};
let $func = vtable.$name.as_deref_mut()
.expect("no handler set but callback is still called - this is a bug!");
let ret = $block;
::core::mem::forget($t);
ret
}
self.vtable.$name = Some(::std::boxed::Box::new(f));
self.set(
$crate::ffi::GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_USERDATA,
&self.vtable
)?;
let callback_ptr: unsafe extern "C" fn(
*mut $crate::ffi::GhosttyTerminal,
*mut ::std::ffi::c_void,
$($rfty),*
) $(-> $rawrty)? = callback;
let result = unsafe {
$crate::ffi::ghostty_terminal_set(
self.inner.as_raw(),
$crate::ffi::$tag,
callback_ptr as *const ::std::ffi::c_void
)
};
$crate::error::from_result(result)?;
Ok(self)
}
)*}
$(
#[doc = concat!(
"Callback type for [`Terminal::",
stringify!($name),
"`](Terminal::",
stringify!($name),
").\n"
)]
$(#[$tmeta])*
pub trait $fnty<'alloc, 'cb>:
$(for<$lf>)? FnMut(
&$($lf)? $crate::terminal::Terminal<'alloc, 'cb>,
$($fty),*
) $(-> $rty)? + 'cb {}
impl<'alloc, 'cb, F> $fnty<'alloc, 'cb> for F
where
F: $(for<$lf>)? FnMut(
&$($lf)? $crate::terminal::Terminal<'alloc, 'cb>,
$($fty),*
) $(-> $rty)? + 'cb
{}
)*
struct VTable<'alloc, 'cb> {
$($name: Option<::std::boxed::Box<dyn $fnty<'alloc, 'cb>>>),*
}
impl ::core::fmt::Debug for VTable<'_, '_> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
f.write_str("VTable {..}")
}
}
impl ::core::default::Default for VTable<'_, '_> {
fn default() -> Self {
Self {
$($name: None),*
}
}
}
};
}
handlers! {
pub fn on_pty_write(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_WRITE_PTY,
from = GhosttyTerminalWritePtyFn(ptr: *const u8, len: usize),
to = <'t>PtyWriteFn(&'t [u8]),
) |term, func| {
let data = unsafe { std::slice::from_raw_parts(ptr, len) };
func(&term, data);
}
pub fn on_bell(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_BELL,
from = GhosttyTerminalBellFn(),
to = BellFn(),
) |term, func| {
func(&term);
}
pub fn on_enquiry(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_ENQUIRY,
from = GhosttyTerminalEnquiryFn() -> ffi::GhosttyString,
to = <'t>EnquiryFn() -> Option<&'t str>,
) |term, func| {
func(&term).unwrap_or("").into()
}
pub fn on_xtversion(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_XTVERSION,
from = GhosttyTerminalXtversionFn() -> ffi::GhosttyString,
to = <'t>XtversionFn() -> Option<&'t str>,
) |term, func| {
func(&term).unwrap_or("").into()
}
pub fn on_title_changed(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_TITLE_CHANGED,
from = GhosttyTerminalTitleChangedFn(),
to = TitleChanged(),
) |term, func| {
func(&term);
}
pub fn on_size(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_SIZE,
from = GhosttyTerminalSizeFn(out: *mut ffi::GhosttySizeReportSize) -> bool,
to = SizeFn() -> Option<ffi::GhosttySizeReportSize>,
) |term, func| {
if let Some(size) = func(&term) {
unsafe { *out = size };
true
} else {
false
}
}
pub fn on_color_scheme(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_COLOR_SCHEME,
from = GhosttyTerminalColorSchemeFn(out: *mut ffi::GhosttyColorScheme) -> bool,
to = ColorSchemeFn() -> Option<ffi::GhosttyColorScheme>,
) |term, func| {
if let Some(size) = func(&term) {
unsafe { *out = size };
true
} else {
false
}
}
pub fn on_device_attributes(
&mut self,
tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES,
from = GhosttyTerminalDeviceAttributesFn(out: *mut ffi::GhosttyDeviceAttributes) -> bool,
to = DeviceAttributesFn() -> Option<DeviceAttributes>,
) |term, func| {
if let Some(size) = func(&term) {
unsafe { *out = size.into() };
true
} else {
false
}
}
}