1use std::{marker::PhantomData, mem::MaybeUninit};
4
5use crate::{
6 alloc::{Allocator, Object},
7 error::{Error, Result, from_result},
8 ffi, key,
9 screen::GridRef,
10 style,
11};
12
13#[doc(inline)]
14pub use ffi::GhosttySizeReportSize as SizeReportSize;
15
16#[derive(Debug)]
109pub struct Terminal<'alloc: 'cb, 'cb> {
110 pub(crate) inner: Object<'alloc, ffi::GhosttyTerminal>,
111 vtable: VTable<'alloc, 'cb>,
112}
113
114#[derive(Clone, Copy, Debug)]
116pub struct Options {
117 pub cols: u16,
119 pub rows: u16,
121 pub max_scrollback: usize,
123}
124
125impl From<Options> for ffi::GhosttyTerminalOptions {
126 fn from(value: Options) -> Self {
127 Self {
128 cols: value.cols,
129 rows: value.rows,
130 max_scrollback: value.max_scrollback,
131 }
132 }
133}
134
135impl<'alloc: 'cb, 'cb> Terminal<'alloc, 'cb> {
136 pub fn new(opts: Options) -> Result<Self> {
138 unsafe { Self::new_inner(std::ptr::null(), opts) }
140 }
141
142 pub fn new_with_alloc<'ctx: 'alloc, Ctx>(
147 alloc: &'alloc Allocator<'ctx, Ctx>,
148 opts: Options,
149 ) -> Result<Self> {
150 unsafe { Self::new_inner(alloc.to_raw(), opts) }
152 }
153
154 unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator, opts: Options) -> Result<Self> {
155 let mut raw: ffi::GhosttyTerminal_ptr = std::ptr::null_mut();
156 let result = unsafe { ffi::ghostty_terminal_new(alloc, &raw mut raw, opts.into()) };
157 from_result(result)?;
158 Ok(Self {
159 inner: Object::new(raw)?,
160 vtable: VTable::default(),
161 })
162 }
163
164 pub fn vt_write(&mut self, data: &[u8]) {
178 unsafe { ffi::ghostty_terminal_vt_write(self.inner.as_raw(), data.as_ptr(), data.len()) }
179 }
180
181 pub fn resize(
192 &mut self,
193 cols: u16,
194 rows: u16,
195 cell_width_px: u32,
196 cell_height_px: u32,
197 ) -> Result<()> {
198 let result = unsafe {
199 ffi::ghostty_terminal_resize(
200 self.inner.as_raw(),
201 cols,
202 rows,
203 cell_width_px,
204 cell_height_px,
205 )
206 };
207 from_result(result)
208 }
209
210 pub fn reset(&mut self) {
216 unsafe { ffi::ghostty_terminal_reset(self.inner.as_raw()) }
217 }
218
219 pub fn scroll_viewport(&mut self, scroll: ScrollViewport) {
221 unsafe { ffi::ghostty_terminal_scroll_viewport(self.inner.as_raw(), scroll.into()) }
222 }
223
224 pub fn grid_ref(&self, point: Point) -> Result<GridRef<'_>> {
241 let mut grid_ref = ffi::sized!(ffi::GhosttyGridRef);
242 let result = unsafe {
243 ffi::ghostty_terminal_grid_ref(self.inner.as_raw(), point.into(), &raw mut grid_ref)
244 };
245 from_result(result)?;
246 Ok(GridRef {
247 inner: grid_ref,
248 _phan: PhantomData,
249 })
250 }
251
252 pub fn mode(&self, mode: Mode) -> Result<bool> {
254 let mut value = false;
255 let result = unsafe {
256 ffi::ghostty_terminal_mode_get(self.inner.as_raw(), mode.into(), &raw mut value)
257 };
258 from_result(result)?;
259 Ok(value)
260 }
261
262 pub fn set_mode(&mut self, mode: Mode, value: bool) -> Result<()> {
264 let result =
265 unsafe { ffi::ghostty_terminal_mode_set(self.inner.as_raw(), mode.into(), value) };
266 from_result(result)
267 }
268
269 fn get<T>(&self, tag: ffi::GhosttyTerminalData) -> Result<T> {
270 let mut value = MaybeUninit::<T>::zeroed();
271 let result = unsafe {
272 ffi::ghostty_terminal_get(self.inner.as_raw(), tag, value.as_mut_ptr().cast())
273 };
274 from_result(result)?;
275 Ok(unsafe { value.assume_init() })
277 }
278
279 fn set<T>(&self, tag: ffi::GhosttyTerminalOption, v: &T) -> Result<()> {
280 let result = unsafe {
281 ffi::ghostty_terminal_set(self.inner.as_raw(), tag, std::ptr::from_ref(v).cast())
282 };
283 from_result(result)
284 }
285
286 pub fn cols(&self) -> Result<u16> {
288 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_COLS)
289 }
290 pub fn rows(&self) -> Result<u16> {
292 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_ROWS)
293 }
294 pub fn cursor_x(&self) -> Result<u16> {
296 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_X)
297 }
298 pub fn cursor_y(&self) -> Result<u16> {
300 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_Y)
301 }
302 pub fn is_cursor_pending_wrap(&self) -> Result<bool> {
304 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_PENDING_WRAP)
305 }
306 pub fn is_cursor_visible(&self) -> Result<bool> {
308 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_VISIBLE)
309 }
310 pub fn cursor_style(&self) -> Result<style::Style> {
314 self.get::<ffi::GhosttyStyle>(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_CURSOR_STYLE)
315 .and_then(std::convert::TryInto::try_into)
316 }
317 pub fn kitty_keyboard_flags(&self) -> Result<key::KittyKeyFlags> {
319 self.get::<ffi::GhosttyKittyKeyFlags>(
320 ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_KITTY_KEYBOARD_FLAGS,
321 )
322 .map(key::KittyKeyFlags::from_bits_retain)
323 }
324
325 pub fn scrollbar(&self) -> Result<ffi::GhosttyTerminalScrollbar> {
331 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_SCROLLBAR)
332 }
333 pub fn active_screen(&self) -> Result<ffi::GhosttyTerminalScreen> {
335 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_ACTIVE_SCREEN)
336 }
337 pub fn is_mouse_tracking(&self) -> Result<bool> {
342 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_MOUSE_TRACKING)
343 }
344 pub fn title(&self) -> Result<&str> {
350 let str =
351 self.get::<ffi::GhosttyString>(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_TITLE)?;
352 let str = unsafe { std::slice::from_raw_parts(str.ptr, str.len) };
355 std::str::from_utf8(str).map_err(|_| Error::InvalidValue)
356 }
357
358 pub fn pwd(&self) -> Result<&str> {
364 let str =
365 self.get::<ffi::GhosttyString>(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_PWD)?;
366 let str = unsafe { std::slice::from_raw_parts(str.ptr, str.len) };
369 std::str::from_utf8(str).map_err(|_| Error::InvalidValue)
370 }
371 pub fn total_rows(&self) -> Result<usize> {
373 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_TOTAL_ROWS)
374 }
375 pub fn scrollback_rows(&self) -> Result<usize> {
377 self.get(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_SCROLLBACK_ROWS)
378 }
379}
380
381impl Drop for Terminal<'_, '_> {
382 fn drop(&mut self) {
383 unsafe { ffi::ghostty_terminal_free(self.inner.as_raw()) }
384 }
385}
386
387#[derive(Clone, Copy, Debug, PartialEq, Eq)]
389pub enum Point {
390 Active(PointCoordinate),
392 Viewport(PointCoordinate),
394 Screen(PointCoordinate),
396 History(PointCoordinate),
398}
399
400impl From<Point> for ffi::GhosttyPoint {
401 fn from(value: Point) -> Self {
402 match value {
403 Point::Active(coord) => Self {
404 tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_ACTIVE,
405 value: ffi::GhosttyPointValue {
406 coordinate: coord.into(),
407 },
408 },
409 Point::Viewport(coord) => Self {
410 tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_VIEWPORT,
411 value: ffi::GhosttyPointValue {
412 coordinate: coord.into(),
413 },
414 },
415 Point::Screen(coord) => Self {
416 tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_SCREEN,
417 value: ffi::GhosttyPointValue {
418 coordinate: coord.into(),
419 },
420 },
421 Point::History(coord) => Self {
422 tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_HISTORY,
423 value: ffi::GhosttyPointValue {
424 coordinate: coord.into(),
425 },
426 },
427 }
428 }
429}
430
431#[derive(Clone, Copy, Debug, PartialEq, Eq)]
433pub struct PointCoordinate {
434 x: u16,
436 y: u32,
438}
439impl From<PointCoordinate> for ffi::GhosttyPointCoordinate {
440 fn from(value: PointCoordinate) -> Self {
441 let PointCoordinate { x, y } = value;
442 Self { x, y }
443 }
444}
445impl From<ffi::GhosttyPointCoordinate> for PointCoordinate {
446 fn from(value: ffi::GhosttyPointCoordinate) -> Self {
447 let ffi::GhosttyPointCoordinate { x, y } = value;
448 Self { x, y }
449 }
450}
451
452#[derive(Clone, Copy, Debug, PartialEq, Eq)]
454pub enum ScrollViewport {
455 Top,
457 Bottom,
459 Delta(isize),
461}
462impl From<ScrollViewport> for ffi::GhosttyTerminalScrollViewport {
463 fn from(value: ScrollViewport) -> Self {
464 match value {
465 ScrollViewport::Top => Self {
466 tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_TOP,
467 value: ffi::GhosttyTerminalScrollViewportValue::default(),
468 },
469 ScrollViewport::Bottom => Self {
470 tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_BOTTOM,
471 value: ffi::GhosttyTerminalScrollViewportValue::default(),
472 },
473 ScrollViewport::Delta(delta) => Self {
474 tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_DELTA,
475 value: {
476 let mut v = ffi::GhosttyTerminalScrollViewportValue::default();
477 v.delta = delta;
478 v
479 },
480 },
481 }
482 }
483}
484
485#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
487pub struct Mode(pub ffi::GhosttyMode);
488
489impl Mode {
490 #![expect(missing_docs, reason = "no upstream documentation provided")]
491 const ANSI_BIT: u16 = 1 << 15;
492
493 #[must_use]
495 pub const fn new(v: u16, kind: ModeKind) -> Self {
496 match kind {
497 ModeKind::Ansi => Self(v | Self::ANSI_BIT),
498 ModeKind::Dec => Self(v),
499 }
500 }
501
502 #[must_use]
504 pub fn value(self) -> u16 {
505 (self.0) & 0x7fff
506 }
507
508 #[must_use]
510 pub fn kind(self) -> ModeKind {
511 if (self.0) & Self::ANSI_BIT > 0 {
512 ModeKind::Ansi
513 } else {
514 ModeKind::Dec
515 }
516 }
517
518 pub const KAM: Self = Self::new(2, ModeKind::Ansi);
519 pub const INSERT: Self = Self::new(4, ModeKind::Ansi);
520 pub const SRM: Self = Self::new(12, ModeKind::Ansi);
521 pub const LINEFEED: Self = Self::new(20, ModeKind::Ansi);
522
523 pub const DECCKM: Self = Self::new(1, ModeKind::Dec);
524 pub const _132_COLUMN: Self = Self::new(3, ModeKind::Dec);
525 pub const SLOW_SCROLL: Self = Self::new(4, ModeKind::Dec);
526 pub const REVERSE_COLORS: Self = Self::new(5, ModeKind::Dec);
527 pub const ORIGIN: Self = Self::new(6, ModeKind::Dec);
528 pub const WRAPAROUND: Self = Self::new(7, ModeKind::Dec);
529 pub const AUTOREPEAT: Self = Self::new(8, ModeKind::Dec);
530 pub const X10_MOUSE: Self = Self::new(9, ModeKind::Dec);
531 pub const CURSOR_BLINKING: Self = Self::new(12, ModeKind::Dec);
532 pub const CURSOR_VISIBLE: Self = Self::new(25, ModeKind::Dec);
533 pub const ENABLE_MODE3: Self = Self::new(40, ModeKind::Dec);
534 pub const REVERSE_WRAP: Self = Self::new(45, ModeKind::Dec);
535 pub const ALT_SCREEN_LEGACY: Self = Self::new(47, ModeKind::Dec);
536 pub const KEYPAD_KEYS: Self = Self::new(66, ModeKind::Dec);
537 pub const LEFT_RIGHT_MARGIN: Self = Self::new(69, ModeKind::Dec);
538 pub const NORMAL_MOUSE: Self = Self::new(1000, ModeKind::Dec);
539 pub const BUTTON_MOUSE: Self = Self::new(1002, ModeKind::Dec);
540 pub const ANY_MOUSE: Self = Self::new(1003, ModeKind::Dec);
541 pub const FOCUS_EVENT: Self = Self::new(1004, ModeKind::Dec);
542 pub const UTF8_MOUSE: Self = Self::new(1005, ModeKind::Dec);
543 pub const SGR_MOUSE: Self = Self::new(1006, ModeKind::Dec);
544 pub const ALT_SCROLL: Self = Self::new(1007, ModeKind::Dec);
545 pub const URXVT_MOUSE: Self = Self::new(1015, ModeKind::Dec);
546 pub const SGR_PIXELS_MOUSE: Self = Self::new(1016, ModeKind::Dec);
547 pub const NUMLOCK_KEYPAD: Self = Self::new(1035, ModeKind::Dec);
548 pub const ALT_ESC_PREFIX: Self = Self::new(1036, ModeKind::Dec);
549 pub const ALT_SENDS_ESC: Self = Self::new(1039, ModeKind::Dec);
550 pub const REVERSE_WRAP_EXT: Self = Self::new(1045, ModeKind::Dec);
551 pub const ALT_SCREEN: Self = Self::new(1047, ModeKind::Dec);
552 pub const SAVE_CURSOR: Self = Self::new(1048, ModeKind::Dec);
553 pub const ALT_SCREEN_SAVE: Self = Self::new(1049, ModeKind::Dec);
554 pub const BRACKETED_PASTE: Self = Self::new(2004, ModeKind::Dec);
555 pub const SYNC_OUTPUT: Self = Self::new(2026, ModeKind::Dec);
556 pub const GRAPHEME_CLUSTER: Self = Self::new(2027, ModeKind::Dec);
557 pub const COLOR_SCHEME_REPORT: Self = Self::new(2031, ModeKind::Dec);
558 pub const IN_BAND_RESIZE: Self = Self::new(2048, ModeKind::Dec);
559}
560
561#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
563pub enum ModeKind {
564 Dec,
566 Ansi,
568}
569
570impl From<Mode> for ffi::GhosttyMode {
571 fn from(value: Mode) -> Self {
572 value.0
573 }
574}
575
576#[derive(Debug, Clone, Copy)]
581pub struct DeviceAttributes {
582 pub primary: PrimaryDeviceAttributes,
584 pub secondary: SecondaryDeviceAttributes,
586 pub tertiary: TertiaryDeviceAttributes,
588}
589
590impl From<DeviceAttributes> for ffi::GhosttyDeviceAttributes {
591 fn from(value: DeviceAttributes) -> Self {
592 Self {
593 primary: value.primary.into(),
594 secondary: value.secondary.into(),
595 tertiary: value.tertiary.into(),
596 }
597 }
598}
599
600#[derive(Debug, Clone, Copy)]
604pub struct PrimaryDeviceAttributes(ffi::GhosttyDeviceAttributesPrimary);
605
606impl PrimaryDeviceAttributes {
607 #[must_use]
614 pub fn new<const N: usize>(
615 conformance_level: ConformanceLevel,
616 features: [DeviceAttributeFeature; N],
617 ) -> Self {
618 assert!(N <= 64);
619
620 let mut f = [0u16; 64];
621 f[..N].copy_from_slice(features.map(|f| f.0).as_slice());
622
623 Self(ffi::GhosttyDeviceAttributesPrimary {
624 conformance_level: conformance_level.0,
625 features: f,
626 num_features: N,
627 })
628 }
629}
630
631impl From<PrimaryDeviceAttributes> for ffi::GhosttyDeviceAttributesPrimary {
632 fn from(value: PrimaryDeviceAttributes) -> Self {
633 value.0
634 }
635}
636
637#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
640pub struct ConformanceLevel(pub u16);
641
642impl ConformanceLevel {
643 #![allow(clippy::cast_possible_truncation, reason = "bindgen ain't perfect")]
644 #![expect(missing_docs, reason = "self-explanatory")]
645 pub const VT100: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT100 as u16);
646 pub const VT101: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT101 as u16);
647 pub const VT102: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT102 as u16);
648 pub const VT125: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT125 as u16);
649 pub const VT131: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT131 as u16);
650 pub const VT132: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT132 as u16);
651 pub const VT220: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT220 as u16);
652 pub const VT240: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT240 as u16);
653 pub const VT320: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT320 as u16);
654 pub const VT340: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT340 as u16);
655 pub const VT420: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT420 as u16);
656 pub const VT510: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT510 as u16);
657 pub const VT520: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT520 as u16);
658 pub const VT525: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_VT525 as u16);
659 pub const LEVEL_2: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_2 as u16);
661 pub const LEVEL_3: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_3 as u16);
663 pub const LEVEL_4: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_4 as u16);
665 pub const LEVEL_5: Self = Self(ffi::GHOSTTY_DA_CONFORMANCE_LEVEL_5 as u16);
667}
668
669#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
671pub struct DeviceAttributeFeature(pub u16);
672
673impl DeviceAttributeFeature {
674 #![expect(clippy::cast_possible_truncation, reason = "bindgen ain't perfect")]
675 #![expect(missing_docs, reason = "no upstream documentation provided")]
676 pub const COLUMNS_132: Self = Self(ffi::GHOSTTY_DA_FEATURE_COLUMNS_132 as u16);
677 pub const PRINTER: Self = Self(ffi::GHOSTTY_DA_FEATURE_PRINTER as u16);
678 pub const REGIS: Self = Self(ffi::GHOSTTY_DA_FEATURE_REGIS as u16);
679 pub const SIXEL: Self = Self(ffi::GHOSTTY_DA_FEATURE_SIXEL as u16);
680 pub const SELECTIVE_ERASE: Self = Self(ffi::GHOSTTY_DA_FEATURE_SELECTIVE_ERASE as u16);
681 pub const USER_DEFINED_KEYS: Self = Self(ffi::GHOSTTY_DA_FEATURE_USER_DEFINED_KEYS as u16);
682 pub const NATIONAL_REPLACEMENT: Self =
683 Self(ffi::GHOSTTY_DA_FEATURE_NATIONAL_REPLACEMENT as u16);
684 pub const TECHNICAL_CHARACTERS: Self =
685 Self(ffi::GHOSTTY_DA_FEATURE_TECHNICAL_CHARACTERS as u16);
686 pub const LOCATOR: Self = Self(ffi::GHOSTTY_DA_FEATURE_LOCATOR as u16);
687 pub const TERMINAL_STATE: Self = Self(ffi::GHOSTTY_DA_FEATURE_TERMINAL_STATE as u16);
688 pub const WINDOWING: Self = Self(ffi::GHOSTTY_DA_FEATURE_WINDOWING as u16);
689 pub const HORIZONTAL_SCROLLING: Self =
690 Self(ffi::GHOSTTY_DA_FEATURE_HORIZONTAL_SCROLLING as u16);
691 pub const ANSI_COLOR: Self = Self(ffi::GHOSTTY_DA_FEATURE_ANSI_COLOR as u16);
692 pub const RECTANGULAR_EDITING: Self = Self(ffi::GHOSTTY_DA_FEATURE_RECTANGULAR_EDITING as u16);
693 pub const ANSI_TEXT_LOCATOR: Self = Self(ffi::GHOSTTY_DA_FEATURE_ANSI_TEXT_LOCATOR as u16);
694 pub const CLIPBOARD: Self = Self(ffi::GHOSTTY_DA_FEATURE_CLIPBOARD as u16);
695}
696
697#[derive(Debug, Copy, Clone)]
702pub struct SecondaryDeviceAttributes {
703 pub device_type: DeviceType,
705 pub firmware_version: u16,
707 pub rom_cartridge: u16,
709}
710
711impl From<SecondaryDeviceAttributes> for ffi::GhosttyDeviceAttributesSecondary {
712 fn from(value: SecondaryDeviceAttributes) -> Self {
713 Self {
714 device_type: value.device_type.0,
715 firmware_version: value.firmware_version,
716 rom_cartridge: value.rom_cartridge,
717 }
718 }
719}
720
721#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
723pub struct DeviceType(pub u16);
724
725impl DeviceType {
726 #![expect(clippy::cast_possible_truncation, reason = "bindgen ain't perfect")]
727 #![expect(missing_docs, reason = "self-explanatory")]
728 pub const VT100: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT100 as u16);
729 pub const VT220: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT220 as u16);
730 pub const VT240: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT240 as u16);
731 pub const VT330: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT330 as u16);
732 pub const VT340: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT340 as u16);
733 pub const VT320: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT320 as u16);
734 pub const VT382: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT382 as u16);
735 pub const VT420: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT420 as u16);
736 pub const VT510: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT510 as u16);
737 pub const VT520: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT520 as u16);
738 pub const VT525: Self = Self(ffi::GHOSTTY_DA_DEVICE_TYPE_VT525 as u16);
739}
740
741#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
746pub struct TertiaryDeviceAttributes {
747 pub unit_id: u32,
749}
750
751impl From<TertiaryDeviceAttributes> for ffi::GhosttyDeviceAttributesTertiary {
752 fn from(value: TertiaryDeviceAttributes) -> Self {
753 Self {
754 unit_id: value.unit_id,
755 }
756 }
757}
758
759macro_rules! handlers {
800 {
801 $(
802 $(#[$fmeta:meta])*
803 $vis:vis fn $name:ident(
804 &mut self,
805 tag = $tag:ident,
806 from = $rawfnty:ident( $($rfname:ident: $rfty:ty),*$(,)? ) $(-> $rawrty:ty)?,
807 $(#[$tmeta:meta])*
808 to = $(<$lf:lifetime>)? $fnty:ident( $($fty:ty),*$(,)? ) $(-> $rty:ty)?,
809 ) |$t:ident, $func:ident| $block:block
810 )*
811 } => {
812 impl<'alloc, 'cb> $crate::terminal::Terminal<'alloc, 'cb> {$(
813 $(#[$fmeta])*
814 $vis fn $name(&mut self, f: impl $fnty<'alloc, 'cb>) -> $crate::error::Result<&mut Self> {
815 unsafe extern "C" fn callback(
816 t: *mut $crate::ffi::GhosttyTerminal,
817 ud: *mut std::ffi::c_void,
818 $($rfname: $rfty),*
819 ) $(-> $rawrty)? {
820 let vtable = unsafe { &mut *ud.cast::<VTable<'_, '_>>() };
822
823 let obj = $crate::alloc::Object::new(t).expect("received null terminal ptr in callback - this is a bug!");
824 let $t = $crate::terminal::Terminal::<'_, '_> {
825 inner: obj,
826 vtable: ::core::default::Default::default(),
827 };
828 let $func = vtable.$name.as_deref_mut()
829 .expect("no handler set but callback is still called - this is a bug!");
830 let ret = $block;
831
832 ::core::mem::forget($t);
834 ret
835 }
836
837 self.vtable.$name = Some(::std::boxed::Box::new(f));
838
839 self.set(
840 $crate::ffi::GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_USERDATA,
841 &self.vtable
842 )?;
843
844 let callback_ptr: unsafe extern "C" fn(
848 *mut $crate::ffi::GhosttyTerminal,
849 *mut ::std::ffi::c_void,
850 $($rfty),*
851 ) $(-> $rawrty)? = callback;
852
853 let result = unsafe {
854 $crate::ffi::ghostty_terminal_set(
855 self.inner.as_raw(),
856 $crate::ffi::$tag,
857 callback_ptr as *const ::std::ffi::c_void
858 )
859 };
860 $crate::error::from_result(result)?;
861 Ok(self)
862 }
863 )*}
864 $(
865 #[doc = concat!(
866 "Callback type for [`Terminal::",
867 stringify!($name),
868 "`](Terminal::",
869 stringify!($name),
870 ").\n"
871 )]
872 $(#[$tmeta])*
873 pub trait $fnty<'alloc, 'cb>:
874 $(for<$lf>)? FnMut(
875 &$($lf)? $crate::terminal::Terminal<'alloc, 'cb>,
876 $($fty),*
877 ) $(-> $rty)? + 'cb {}
878
879 impl<'alloc, 'cb, F> $fnty<'alloc, 'cb> for F
880 where
881 F: $(for<$lf>)? FnMut(
882 &$($lf)? $crate::terminal::Terminal<'alloc, 'cb>,
883 $($fty),*
884 ) $(-> $rty)? + 'cb
885 {}
886 )*
887
888 struct VTable<'alloc, 'cb> {
889 $($name: Option<::std::boxed::Box<dyn $fnty<'alloc, 'cb>>>),*
890 }
891
892 impl ::core::fmt::Debug for VTable<'_, '_> {
893 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
894 f.write_str("VTable {..}")
895 }
896 }
897
898 impl ::core::default::Default for VTable<'_, '_> {
899 fn default() -> Self {
900 Self {
901 $($name: None),*
902 }
903 }
904 }
905 };
906}
907
908handlers! {
909 pub fn on_pty_write(
912 &mut self,
913 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_WRITE_PTY,
914 from = GhosttyTerminalWritePtyFn(ptr: *const u8, len: usize),
915 to = <'t>PtyWriteFn(&'t [u8]),
916 ) |term, func| {
917 let data = unsafe { std::slice::from_raw_parts(ptr, len) };
921 func(&term, data);
922 }
923
924 pub fn on_bell(
927 &mut self,
928 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_BELL,
929 from = GhosttyTerminalBellFn(),
930 to = BellFn(),
931 ) |term, func| {
932 func(&term);
933 }
934
935 pub fn on_enquiry(
938 &mut self,
939 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_ENQUIRY,
940 from = GhosttyTerminalEnquiryFn() -> ffi::GhosttyString,
941 to = <'t>EnquiryFn() -> Option<&'t str>,
942 ) |term, func| {
943 func(&term).unwrap_or("").into()
944 }
945
946 pub fn on_xtversion(
950 &mut self,
951 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_XTVERSION,
952 from = GhosttyTerminalXtversionFn() -> ffi::GhosttyString,
953 to = <'t>XtversionFn() -> Option<&'t str>,
954 ) |term, func| {
955 func(&term).unwrap_or("").into()
956 }
957
958 pub fn on_title_changed(
964 &mut self,
965 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_TITLE_CHANGED,
966 from = GhosttyTerminalTitleChangedFn(),
967 to = TitleChanged(),
968 ) |term, func| {
969 func(&term);
970 }
971
972 pub fn on_size(
975 &mut self,
976 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_SIZE,
977 from = GhosttyTerminalSizeFn(out: *mut ffi::GhosttySizeReportSize) -> bool,
978 to = SizeFn() -> Option<ffi::GhosttySizeReportSize>,
979 ) |term, func| {
980 if let Some(size) = func(&term) {
981 unsafe { *out = size };
983 true
984 } else {
985 false
986 }
987 }
988
989 pub fn on_color_scheme(
995 &mut self,
996 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_COLOR_SCHEME,
997 from = GhosttyTerminalColorSchemeFn(out: *mut ffi::GhosttyColorScheme) -> bool,
998 to = ColorSchemeFn() -> Option<ffi::GhosttyColorScheme>,
999 ) |term, func| {
1000 if let Some(size) = func(&term) {
1001 unsafe { *out = size };
1003 true
1004 } else {
1005 false
1006 }
1007 }
1008
1009 pub fn on_device_attributes(
1015 &mut self,
1016 tag = GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES,
1017 from = GhosttyTerminalDeviceAttributesFn(out: *mut ffi::GhosttyDeviceAttributes) -> bool,
1018 to = DeviceAttributesFn() -> Option<DeviceAttributes>,
1019 ) |term, func| {
1020 if let Some(size) = func(&term) {
1021 unsafe { *out = size.into() };
1023 true
1024 } else {
1025 false
1026 }
1027 }
1028}