// libpardes — the C ABI the macOS app links against. // // Hand-written, and kept honest by a test: build.zig translate-C's this file // into the unit-test build, and src/macos.zig asserts every constant and // struct layout below against the Zig side (ghostty's trick — see docs/macos.md). // Change nothing here without running `zig build unit-test -Dplatform=macos`. // // Division of labour, which is the whole design in two lines: // Zig owns the core, the ptys, every effect, and the worker threads. // Swift owns NSApplication, the window, input translation, and drawing. // // There is exactly one core per process, so there are no handles: the state is // a file-scoped singleton, same as src/web.zig. Every function below must be // called from the main thread. The one exception is the `wakeup` callback, // which fires from a pty reader thread. #ifndef PARDES_H #define PARDES_H #include #include #include #ifdef __cplusplus extern "C" { #endif // ---------------------------------------------------------------- frame // One rendered cell. Mirrors pardes.Cell (src/pardes.zig) flattened for the // wire; the identical encoding is spelled a second time for the browser in // src/web.zig as WebCell. typedef struct { // UTF-8 grapheme, `len` bytes. The core caps graphemes at seven bytes; the // eighth is always zero so a debugger prints something sensible. uint8_t text[8]; // 0x01000000 terminal default // 0x02000000 | index 256-color palette entry // 0x00RRGGBB direct color uint32_t fg; uint32_t bg; // bit 0 bold, 1 dim, 2 italic, 3 blink, 4 reverse, 5 invisible, // 6 strikethrough; bits 8.. hold PARDES_UL_*. uint16_t attrs; uint8_t len; // bit 0: the core never painted this cell — draw it as the default cell // (background only), which is what makes a partial redraw cheap. uint8_t flags; } pardes_cell_s; #define PARDES_COLOR_DEFAULT 0x01000000u #define PARDES_COLOR_INDEXED 0x02000000u #define PARDES_COLOR_TAG_MASK 0xff000000u #define PARDES_COLOR_RGB_MASK 0x00ffffffu #define PARDES_ATTR_BOLD 0x0001u #define PARDES_ATTR_DIM 0x0002u #define PARDES_ATTR_ITALIC 0x0004u #define PARDES_ATTR_BLINK 0x0008u #define PARDES_ATTR_REVERSE 0x0010u #define PARDES_ATTR_INVISIBLE 0x0020u #define PARDES_ATTR_STRIKETHROUGH 0x0040u #define PARDES_ATTR_UL_SHIFT 8 // Literals rather than (1u << n): Swift's macro importer is dependable on a // plain integer and less so on an expression, and nothing here can compile the // Swift side to find out. The ABI guard asserts each value against the encoder. #define PARDES_UL_OFF 0 #define PARDES_UL_SINGLE 1 #define PARDES_UL_DOUBLE 2 #define PARDES_UL_CURLY 3 #define PARDES_UL_DOTTED 4 #define PARDES_UL_DASHED 5 #define PARDES_CELL_DEFAULT 0x01u // ---------------------------------------------------------------- input // Modifier bitmask shared by key and mouse events. #define PARDES_MOD_CTRL 0x0001u #define PARDES_MOD_ALT 0x0002u #define PARDES_MOD_SHIFT 0x0004u // Keys that carry no text. The four editing keys are their ASCII controls; // everything else lives in a private-use plane so it can never collide with a // real codepoint arriving as text. #define PARDES_KEY_ENTER 0x0Du #define PARDES_KEY_ESCAPE 0x1Bu #define PARDES_KEY_TAB 0x09u #define PARDES_KEY_BACKSPACE 0x7Fu #define PARDES_KEY_UP 0xF0001u #define PARDES_KEY_DOWN 0xF0002u #define PARDES_KEY_LEFT 0xF0003u #define PARDES_KEY_RIGHT 0xF0004u #define PARDES_KEY_HOME 0xF0005u #define PARDES_KEY_END 0xF0006u #define PARDES_KEY_PAGE_UP 0xF0007u #define PARDES_KEY_PAGE_DOWN 0xF0008u #define PARDES_KEY_DELETE 0xF0009u // acme's three buttons carry the whole vocabulary: 1 selects, 2 executes, // 3 looks. Wheel buttons are ordinary buttons, not a separate axis. typedef enum { PARDES_MOUSE_LEFT = 0, PARDES_MOUSE_MIDDLE = 1, PARDES_MOUSE_RIGHT = 2, PARDES_MOUSE_WHEEL_UP = 3, PARDES_MOUSE_WHEEL_DOWN = 4, PARDES_MOUSE_WHEEL_LEFT = 5, PARDES_MOUSE_WHEEL_RIGHT = 6, PARDES_MOUSE_NONE = 7, } pardes_mouse_button_e; typedef enum { PARDES_MOUSE_PRESS = 0, PARDES_MOUSE_RELEASE = 1, PARDES_MOUSE_MOTION = 2, PARDES_MOUSE_DRAG = 3, } pardes_mouse_kind_e; // ---------------------------------------------------------------- runtime // What the host lends the core. Two callbacks, because everything else the // core wants done it already does itself: it owns the ptys, and it opens URLs // through /usr/bin/open. Copied by value during pardes_init, so the struct // need only outlive that call. typedef struct { // Passed back to every callback below. Conventionally the AppDelegate. void *userdata; // "Your state moved; please pump me." Called from a pty reader thread, so // the host must hop to the main thread before calling pardes_tick — see // AppDelegate.wakeup, which does exactly one DispatchQueue.main.async. void (*wakeup)(void *userdata); // The yank register changed. `text` is borrowed for the duration of the // call only. Main thread, inside pardes_tick. void (*set_clipboard)(void *userdata, const char *text, size_t len); } pardes_runtime_s; // ---------------------------------------------------------------- lifecycle // Returns 0 on success, or a nonzero opaque error code. Pass the real window // grid, not a placeholder: init delivers it as the first resize EVENT (the // core defers each shell's greeting until it has seen one), and the first // forkpty takes its winsize straight off the core — boot at 80x24 and the // shell draws its first prompt to a width the window never had. int pardes_init(const pardes_runtime_s *runtime, uint16_t cols, uint16_t rows); void pardes_deinit(void); // Drain pty output into the core and perform the effects it queued. Call after // every input function and on every wakeup. Returns true if anything changed // and the host should mark its view dirty. bool pardes_tick(void); // The core asked to exit (the Exit builtin, or the last pane closing). bool pardes_should_quit(void); // A theme transition is mid-flight and wants ~60 Hz ticks until it settles. bool pardes_animating(void); // ---------------------------------------------------------------- events in // `cp` is a codepoint or one of PARDES_KEY_*; `text`/`len` are the host's // already-composed characters for printable keys and empty for functional // ones. `text` is borrowed for the call. void pardes_key(uint32_t cp, const char *text, size_t len, uint32_t mods); void pardes_paste(const char *text, size_t len); void pardes_mouse(pardes_mouse_button_e button, pardes_mouse_kind_e kind, uint16_t col, uint16_t row, uint32_t mods); // Trackpad/precision wheel distance in rows, sign following the grid (positive // scrolls down). The core has no fractional scroll — it moves a row at a time — // so libpardes accumulates here and emits whole-row wheel presses, keeping the // remainder. Both other shells do this same accumulation host-side (stepScroll // in gui.zig, the drain loop in web/app.mjs); it lives in Zig here so the Swift // side stays a translator. A discrete wheel notch should go through // pardes_mouse instead. void pardes_scroll(float delta_rows, uint16_t col, uint16_t row); // `cell_w`/`cell_h` are one cell in physical pixels, which only the native PDF // placement path reads. Pass the backing-store size, not points. void pardes_resize(uint16_t cols, uint16_t rows, uint16_t cell_w, uint16_t cell_h); // ---------------------------------------------------------------- frame out // Render one frame. Returns the cell count (cols * rows), or 0 on failure. // The buffer returned by pardes_frame_cells is valid until the next call. uint32_t pardes_frame(void); const pardes_cell_s *pardes_frame_cells(void); uint16_t pardes_frame_cols(void); uint16_t pardes_frame_rows(void); // -1 when the cursor is hidden. `bar` asks for a thin insert-mode caret. int32_t pardes_cursor_x(void); int32_t pardes_cursor_y(void); bool pardes_cursor_bar(void); #ifdef __cplusplus } #endif #endif // PARDES_H