1#![allow(clippy::cast_possible_truncation, reason = "bindgen ain't perfect")]
16use std::mem::MaybeUninit;
17
18use crate::{
19 Error,
20 alloc::{Allocator, Object},
21 error::{Result, from_result, from_result_with_len},
22 ffi,
23 terminal::Terminal,
24};
25
26#[derive(Debug)]
28pub struct Encoder<'alloc>(Object<'alloc, ffi::GhosttyKeyEncoder>);
29
30impl<'alloc> Encoder<'alloc> {
31 pub fn new() -> Result<Self> {
33 unsafe { Self::new_inner(std::ptr::null()) }
35 }
36
37 pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
42 unsafe { Self::new_inner(alloc.to_raw()) }
44 }
45
46 unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
47 let mut raw: ffi::GhosttyKeyEncoder_ptr = std::ptr::null_mut();
48 let result = unsafe { ffi::ghostty_key_encoder_new(alloc, &raw mut raw) };
49 from_result(result)?;
50 Ok(Self(Object::new(raw)?))
51 }
52
53 unsafe fn setopt(
54 &mut self,
55 option: ffi::GhosttyKeyEncoderOption,
56 value: *const std::ffi::c_void,
57 ) {
58 unsafe { ffi::ghostty_key_encoder_setopt(self.0.as_raw(), option, value) }
59 }
60
61 pub fn encode_to_vec(&mut self, event: &Event, vec: &mut Vec<u8>) -> Result<()> {
70 let remaining = vec.capacity() - vec.len();
71
72 let written = match self.encode_to_uninit_buf(event, vec.spare_capacity_mut()) {
73 Ok(v) => Ok(v),
74 Err(Error::OutOfSpace { required }) => {
75 vec.reserve(required - remaining);
77 self.encode_to_uninit_buf(event, vec.spare_capacity_mut())
78 }
79 Err(e) => Err(e),
80 };
81
82 unsafe { vec.set_len(vec.len() + written?) };
85 Ok(())
86 }
87
88 pub fn encode(&mut self, event: &Event, buf: &mut [u8]) -> Result<usize> {
103 self.encode_to_uninit_buf(event, unsafe {
105 std::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len())
106 })
107 }
108
109 fn encode_to_uninit_buf(
110 &mut self,
111 event: &Event,
112 buf: &mut [MaybeUninit<u8>],
113 ) -> Result<usize> {
114 let mut written: usize = 0;
115 let result = unsafe {
116 ffi::ghostty_key_encoder_encode(
117 self.0.as_raw(),
118 event.inner.as_raw(),
119 buf.as_mut_ptr().cast(),
120 buf.len(),
121 &raw mut written,
122 )
123 };
124 from_result_with_len(result, written)
125 }
126
127 pub fn set_options_from_terminal(&mut self, terminal: &Terminal<'_, '_>) -> &mut Self {
138 unsafe {
139 ffi::ghostty_key_encoder_setopt_from_terminal(self.0.as_raw(), terminal.inner.as_raw());
140 }
141 self
142 }
143
144 pub fn set_cursor_key_application(&mut self, value: bool) -> &mut Self {
146 unsafe {
147 self.setopt(
148 ffi::GhosttyKeyEncoderOption_GHOSTTY_KEY_ENCODER_OPT_CURSOR_KEY_APPLICATION,
149 std::ptr::from_ref(&value).cast(),
150 );
151 }
152 self
153 }
154 pub fn set_keypad_key_application(&mut self, value: bool) -> &mut Self {
156 unsafe {
157 self.setopt(
158 ffi::GhosttyKeyEncoderOption_GHOSTTY_KEY_ENCODER_OPT_KEYPAD_KEY_APPLICATION,
159 std::ptr::from_ref(&value).cast(),
160 );
161 }
162 self
163 }
164 pub fn set_ignore_keypad_with_numlock(&mut self, value: bool) -> &mut Self {
166 unsafe {
167 self.setopt(
168 ffi::GhosttyKeyEncoderOption_GHOSTTY_KEY_ENCODER_OPT_IGNORE_KEYPAD_WITH_NUMLOCK,
169 std::ptr::from_ref(&value).cast(),
170 );
171 }
172 self
173 }
174 pub fn set_alt_esc_prefix(&mut self, value: bool) -> &mut Self {
176 unsafe {
177 self.setopt(
178 ffi::GhosttyKeyEncoderOption_GHOSTTY_KEY_ENCODER_OPT_ALT_ESC_PREFIX,
179 std::ptr::from_ref(&value).cast(),
180 );
181 }
182 self
183 }
184 pub fn set_modify_other_keys_state_2(&mut self, value: bool) -> &mut Self {
186 unsafe {
187 self.setopt(
188 ffi::GhosttyKeyEncoderOption_GHOSTTY_KEY_ENCODER_OPT_MODIFY_OTHER_KEYS_STATE_2,
189 std::ptr::from_ref(&value).cast(),
190 );
191 }
192 self
193 }
194 pub fn set_kitty_flags(&mut self, value: KittyKeyFlags) -> &mut Self {
196 let value = value.bits();
197 unsafe {
198 self.setopt(
199 ffi::GhosttyKeyEncoderOption_GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS,
200 std::ptr::from_ref(&value).cast(),
201 );
202 }
203 self
204 }
205 pub fn set_macos_option_as_alt(&mut self, value: OptionAsAlt) -> &mut Self {
207 unsafe {
208 self.setopt(
209 ffi::GhosttyKeyEncoderOption_GHOSTTY_KEY_ENCODER_OPT_MACOS_OPTION_AS_ALT,
210 std::ptr::from_ref(&value).cast(),
211 );
212 }
213 self
214 }
215}
216
217impl Drop for Encoder<'_> {
218 fn drop(&mut self) {
219 unsafe { ffi::ghostty_key_encoder_free(self.0.as_raw()) }
220 }
221}
222
223#[derive(Debug)]
226pub struct Event<'alloc> {
227 inner: Object<'alloc, ffi::GhosttyKeyEvent>,
228 text: Option<String>,
229}
230
231impl<'alloc> Event<'alloc> {
232 pub fn new() -> Result<Self> {
234 unsafe { Self::new_inner(std::ptr::null()) }
236 }
237
238 pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
243 unsafe { Self::new_inner(alloc.to_raw()) }
245 }
246
247 unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
248 let mut raw: ffi::GhosttyKeyEvent_ptr = std::ptr::null_mut();
249 let result = unsafe { ffi::ghostty_key_event_new(alloc, &raw mut raw) };
250 from_result(result)?;
251 Ok(Self {
252 inner: Object::new(raw)?,
253 text: None,
254 })
255 }
256
257 pub fn set_action(&mut self, action: Action) -> &mut Self {
259 unsafe { ffi::ghostty_key_event_set_action(self.inner.as_raw(), action.into()) }
260 self
261 }
262
263 #[must_use]
265 pub fn action(&self) -> Action {
266 Action::try_from(unsafe { ffi::ghostty_key_event_get_action(self.inner.as_raw()) })
267 .unwrap_or(Action::Press)
268 }
269
270 pub fn set_key(&mut self, key: Key) -> &mut Self {
272 unsafe { ffi::ghostty_key_event_set_key(self.inner.as_raw(), key.into()) }
273 self
274 }
275
276 #[must_use]
278 pub fn key(&self) -> Key {
279 Key::try_from(unsafe { ffi::ghostty_key_event_get_key(self.inner.as_raw()) })
280 .unwrap_or(Key::Unidentified)
281 }
282
283 pub fn set_mods(&mut self, mods: Mods) -> &mut Self {
285 unsafe { ffi::ghostty_key_event_set_mods(self.inner.as_raw(), mods.bits()) }
286 self
287 }
288
289 #[must_use]
291 pub fn mods(&self) -> Mods {
292 Mods::from_bits_retain(unsafe { ffi::ghostty_key_event_get_mods(self.inner.as_raw()) })
293 }
294
295 pub fn set_consumed_mods(&mut self, mods: Mods) -> &mut Self {
297 unsafe { ffi::ghostty_key_event_set_consumed_mods(self.inner.as_raw(), mods.bits()) }
298 self
299 }
300
301 #[must_use]
303 pub fn consumed_mods(&self) -> Mods {
304 Mods::from_bits_retain(unsafe {
305 ffi::ghostty_key_event_get_consumed_mods(self.inner.as_raw())
306 })
307 }
308
309 pub fn set_composing(&mut self, composing: bool) -> &mut Self {
311 unsafe { ffi::ghostty_key_event_set_composing(self.inner.as_raw(), composing) }
312 self
313 }
314
315 #[must_use]
317 pub fn is_composing(&self) -> bool {
318 unsafe { ffi::ghostty_key_event_get_composing(self.inner.as_raw()) }
319 }
320
321 pub fn set_utf8<S: Into<String>>(&mut self, text: Option<S>) -> &mut Self {
326 self.text = text.map(Into::into);
327
328 match &self.text {
329 Some(text) => unsafe {
330 ffi::ghostty_key_event_set_utf8(
331 self.inner.as_raw(),
332 text.as_ptr().cast(),
333 text.len(),
334 );
335 },
336 None => unsafe {
337 ffi::ghostty_key_event_set_utf8(self.inner.as_raw(), std::ptr::null(), 0);
338 },
339 }
340 self
341 }
342
343 pub fn utf8(&mut self) -> Option<&str> {
345 self.text.as_deref()
348 }
349
350 pub fn set_unshifted_codepoint(&mut self, codepoint: char) -> &mut Self {
352 unsafe {
353 ffi::ghostty_key_event_set_unshifted_codepoint(self.inner.as_raw(), codepoint.into())
354 }
355 self
356 }
357
358 #[must_use]
360 pub fn unshifted_codepoint(&self) -> char {
361 unsafe {
362 char::from_u32_unchecked(ffi::ghostty_key_event_get_unshifted_codepoint(
363 self.inner.as_raw(),
364 ))
365 }
366 }
367}
368
369impl Drop for Event<'_> {
370 fn drop(&mut self) {
371 unsafe { ffi::ghostty_key_event_free(self.inner.as_raw()) }
372 }
373}
374
375#[repr(u32)]
376#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
377#[non_exhaustive]
378#[expect(missing_docs, reason = "self-explanatory")]
379pub enum Key {
380 Unidentified = 0,
381 Backquote = 1,
382 Backslash = 2,
383 BracketLeft = 3,
384 BracketRight = 4,
385 Comma = 5,
386 Digit0 = 6,
387 Digit1 = 7,
388 Digit2 = 8,
389 Digit3 = 9,
390 Digit4 = 10,
391 Digit5 = 11,
392 Digit6 = 12,
393 Digit7 = 13,
394 Digit8 = 14,
395 Digit9 = 15,
396 Equal = 16,
397 IntlBackslash = 17,
398 IntlRo = 18,
399 IntlYen = 19,
400 A = 20,
401 B = 21,
402 C = 22,
403 D = 23,
404 E = 24,
405 F = 25,
406 G = 26,
407 H = 27,
408 I = 28,
409 J = 29,
410 K = 30,
411 L = 31,
412 M = 32,
413 N = 33,
414 O = 34,
415 P = 35,
416 Q = 36,
417 R = 37,
418 S = 38,
419 T = 39,
420 U = 40,
421 V = 41,
422 W = 42,
423 X = 43,
424 Y = 44,
425 Z = 45,
426 Minus = 46,
427 Period = 47,
428 Quote = 48,
429 Semicolon = 49,
430 Slash = 50,
431 AltLeft = 51,
432 AltRight = 52,
433 Backspace = 53,
434 CapsLock = 54,
435 ContextMenu = 55,
436 ControlLeft = 56,
437 ControlRight = 57,
438 Enter = 58,
439 MetaLeft = 59,
440 MetaRight = 60,
441 ShiftLeft = 61,
442 ShiftRight = 62,
443 Space = 63,
444 Tab = 64,
445 Convert = 65,
446 KanaMode = 66,
447 NonConvert = 67,
448 Delete = 68,
449 End = 69,
450 Help = 70,
451 Home = 71,
452 Insert = 72,
453 PageDown = 73,
454 PageUp = 74,
455 ArrowDown = 75,
456 ArrowLeft = 76,
457 ArrowRight = 77,
458 ArrowUp = 78,
459 NumLock = 79,
460 Numpad0 = 80,
461 Numpad1 = 81,
462 Numpad2 = 82,
463 Numpad3 = 83,
464 Numpad4 = 84,
465 Numpad5 = 85,
466 Numpad6 = 86,
467 Numpad7 = 87,
468 Numpad8 = 88,
469 Numpad9 = 89,
470 NumpadAdd = 90,
471 NumpadBackspace = 91,
472 NumpadClear = 92,
473 NumpadClearEntry = 93,
474 NumpadComma = 94,
475 NumpadDecimal = 95,
476 NumpadDivide = 96,
477 NumpadEnter = 97,
478 NumpadEqual = 98,
479 NumpadMemoryAdd = 99,
480 NumpadMemoryClear = 100,
481 NumpadMemoryRecall = 101,
482 NumpadMemoryStore = 102,
483 NumpadMemorySubtract = 103,
484 NumpadMultiply = 104,
485 NumpadParenLeft = 105,
486 NumpadParenRight = 106,
487 NumpadSubtract = 107,
488 NumpadSeparator = 108,
489 NumpadUp = 109,
490 NumpadDown = 110,
491 NumpadRight = 111,
492 NumpadLeft = 112,
493 NumpadBegin = 113,
494 NumpadHome = 114,
495 NumpadEnd = 115,
496 NumpadInsert = 116,
497 NumpadDelete = 117,
498 NumpadPageUp = 118,
499 NumpadPageDown = 119,
500 Escape = 120,
501 F1 = 121,
502 F2 = 122,
503 F3 = 123,
504 F4 = 124,
505 F5 = 125,
506 F6 = 126,
507 F7 = 127,
508 F8 = 128,
509 F9 = 129,
510 F10 = 130,
511 F11 = 131,
512 F12 = 132,
513 F13 = 133,
514 F14 = 134,
515 F15 = 135,
516 F16 = 136,
517 F17 = 137,
518 F18 = 138,
519 F19 = 139,
520 F20 = 140,
521 F21 = 141,
522 F22 = 142,
523 F23 = 143,
524 F24 = 144,
525 F25 = 145,
526 Fn = 146,
527 FnLock = 147,
528 PrintScreen = 148,
529 ScrollLock = 149,
530 Pause = 150,
531 BrowserBack = 151,
532 BrowserFavorites = 152,
533 BrowserForward = 153,
534 BrowserHome = 154,
535 BrowserRefresh = 155,
536 BrowserSearch = 156,
537 BrowserStop = 157,
538 Eject = 158,
539 LaunchApp1 = 159,
540 LaunchApp2 = 160,
541 LaunchMail = 161,
542 MediaPlayPause = 162,
543 MediaSelect = 163,
544 MediaStop = 164,
545 MediaTrackNext = 165,
546 MediaTrackPrevious = 166,
547 Power = 167,
548 Sleep = 168,
549 AudioVolumeDown = 169,
550 AudioVolumeMute = 170,
551 AudioVolumeUp = 171,
552 WakeUp = 172,
553 Copy = 173,
554 Cut = 174,
555 Paste = 175,
556}
557
558#[repr(u32)]
560#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
561#[non_exhaustive]
562pub enum Action {
563 Press = ffi::GhosttyKeyAction_GHOSTTY_KEY_ACTION_PRESS,
565 Release = ffi::GhosttyKeyAction_GHOSTTY_KEY_ACTION_RELEASE,
567 Repeat = ffi::GhosttyKeyAction_GHOSTTY_KEY_ACTION_REPEAT,
569}
570
571#[repr(u32)]
576#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
577pub enum OptionAsAlt {
578 False = ffi::GhosttyOptionAsAlt_GHOSTTY_OPTION_AS_ALT_FALSE,
580 True = ffi::GhosttyOptionAsAlt_GHOSTTY_OPTION_AS_ALT_TRUE,
582 Left = ffi::GhosttyOptionAsAlt_GHOSTTY_OPTION_AS_ALT_LEFT,
584 Right = ffi::GhosttyOptionAsAlt_GHOSTTY_OPTION_AS_ALT_RIGHT,
586}
587
588bitflags::bitflags! {
589 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
600 pub struct Mods: u16 {
601 const SHIFT = ffi::GHOSTTY_MODS_SHIFT as u16;
603 const ALT = ffi::GHOSTTY_MODS_ALT as u16;
605 const CTRL = ffi::GHOSTTY_MODS_CTRL as u16;
607 const SUPER = ffi::GHOSTTY_MODS_SUPER as u16;
609 const CAPS_LOCK = ffi::GHOSTTY_MODS_CAPS_LOCK as u16;
611 const NUM_LOCK = ffi::GHOSTTY_MODS_NUM_LOCK as u16;
613 const SHIFT_SIDE = ffi::GHOSTTY_MODS_SHIFT_SIDE as u16;
617 const ALT_SIDE = ffi::GHOSTTY_MODS_ALT_SIDE as u16;
621 const CTRL_SIDE = ffi::GHOSTTY_MODS_CTRL_SIDE as u16;
625 const SUPER_SIDE = ffi::GHOSTTY_MODS_SUPER_SIDE as u16;
629 }
630
631 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
635 pub struct KittyKeyFlags: u8 {
636 const DISABLED = ffi::GHOSTTY_KITTY_KEY_DISABLED as u8;
638 const DISAMBIGUATE = ffi::GHOSTTY_KITTY_KEY_DISAMBIGUATE as u8;
640 const REPORT_EVENTS = ffi::GHOSTTY_KITTY_KEY_REPORT_EVENTS as u8;
642 const REPORT_ALTERNATES = ffi::GHOSTTY_KITTY_KEY_REPORT_ALTERNATES as u8;
644 const REPORT_ALL = ffi::GHOSTTY_KITTY_KEY_REPORT_ALL as u8;
646 const REPORT_ASSOCIATED = ffi::GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED as u8;
648 const ALL = ffi::GHOSTTY_KITTY_KEY_ALL as u8;
650 }
651}