summaryrefslogtreecommitdiff
path: root/src/macos/pardes.h
blob: 62ddbae540811660882fe00523ed5f426f628b92 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// 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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#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;

// What the core just did, for a shell that can answer with something the hand
// feels. Taken with pardes_take_haptic once per pump; the two acme verbs are
// distinguished because they deserve distinct taps.
typedef enum {
  PARDES_HAPTIC_NONE = 0,
  PARDES_HAPTIC_EXEC = 1,
  PARDES_HAPTIC_LOOK = 2,
} pardes_haptic_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.
//
// The return value is whether this tick did any IO — bytes arrived from a pty,
// or an effect was performed. It is NOT a repaint signal, and a host that uses
// it as one shows a stale screen: moving the cursor, extending a selection,
// changing mode and scrolling all mutate the grid while queueing nothing and
// performing nothing, so they tick false. Mark the view dirty after any call
// into the core and use this only to decide whether there was work.
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 CELLS, sign following the grid
// (positive scrolls down and right). The core has no fractional scroll — it
// moves a row or a column at a time — so libpardes accumulates here and emits
// whole 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, and
// so the quantizer is unit-tested on a machine with no trackpad. A discrete
// wheel notch should go through pardes_mouse instead.
void pardes_scroll(float delta_rows, float delta_cols, uint16_t col,
                   uint16_t row);

// A two-finger trackpad rotation, in degrees since the last call, positive
// counterclockwise (AppKit's sign, unchanged). The core has no rotation: this
// is spent as the search-step keys, clockwise `n` and counterclockwise `N`, a
// notch at a time with the remainder kept — the same accumulate-and-spend
// shape as pardes_scroll, and in Zig for the same reason. Feed it the raw
// per-event delta; pass 0 at gesture start to drop a stale remainder.
void pardes_rotate(float degrees);

// Run one builtin command line, exactly as executing the same text in a tag
// would. This is the core's own `command` event, which is how a nested pardes
// talks to its host; here it is what a menu item is made of, and what opens
// the file named on argv or dropped on the Dock icon (`Look <path>`).
void pardes_command(const char *text, size_t len);

// `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);

// The Look or Exec the core performed since this was last asked, and clears
// it. Call it once per pump, after pardes_tick — the input functions run the
// dispatch synchronously, so a gesture's pulse is already waiting by the time
// its tick returns. PARDES_HAPTIC_NONE means nothing to feel.
pardes_haptic_e pardes_take_haptic(void);

// The font file the `Font` builtin asked for since this was last called, and
// clears it; NULL when nothing was asked. Ask once per pump, beside the haptic
// above. The string is a NUL-terminated absolute path owned by libpardes and
// valid until the next call.
//
// A path rather than a family name: the core found the file by walking the
// font directories itself, so both shells agree on which faces exist and
// neither has to ask its platform to resolve a name it might resolve
// differently. A `.ttc` collection names its first face, which is the cut the
// file is named after.
//
// The host loads it, re-measures its cell, and reports the new grid through
// pardes_resize. A file the host cannot load is one to ignore: keep wearing
// the face that works, because a terminal that cannot draw has no way back
// out of it.
const char *pardes_font_take(void);

#ifdef __cplusplus
}
#endif

#endif // PARDES_H