aboutsummaryrefslogtreecommitdiffstats
path: root/src/shader_abi.h
blob: be737c44c95a0b4bad6b3001a27e616382c7efe1 (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
/* The shader ABI: everything a shader needs to see, and nothing else.
 *
 * This header exists so a shader can be compiled *outside* this tree — a
 * plugin includes only this file, with no libghostty-vt, no pty, no layout.
 * That is also the contract: a shader gets a cell and where it sits, and it
 * cannot reach anything else, because nothing else is declared here.
 *
 * Everything in here is ABI. Changing the layout of these structs breaks
 * every plugin built against the old one, so SLOSH_SHADER_ABI is bumped
 * when they change, and a plugin that reports the wrong version (or the wrong
 * sizes, which catches a plugin built against an edited copy of this file) is
 * refused at load rather than trusted into a crash.
 */
#ifndef SLOSH_SHADER_ABI_H
#define SLOSH_SHADER_ABI_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/* 3: shade_ctx_t gained `state_ms`, so an effect can know how long the pane has
 *    been as it is and not only what time it is; struct shader gained
 *    `channels`, which is the pass's business rather than a shader's. */
#define SLOSH_SHADER_ABI 3

/* Which of a cell's two colours a pass is allowed to keep. Enforced by the
 * pass, not by the shader: every shader writes whatever it writes, and the
 * cell's other colour is put back afterwards — so this works for a built-in
 * and for a loaded one, and no shader has to grow a variant of itself.
 *
 * Put back to what it *was*, which for a cell the terminal is drawing in its
 * own default colour means back to unset. That is the difference between
 * recolouring a border glyph and painting a dark rectangle behind it. */
enum {
  SHADE_FG = 1 << 0,
  SHADE_BG = 1 << 1,
  SHADE_BOTH = SHADE_FG | SHADE_BG, /* and 0 means this, so zeroed is normal */
};

enum {
  ATTR_BOLD = 1 << 0,
  ATTR_DIM = 1 << 1,
  ATTR_ITALIC = 1 << 2,
  ATTR_UNDERLINE = 1 << 3,
  ATTR_BLINK = 1 << 4,
  ATTR_INVERSE = 1 << 5,
  ATTR_INVISIBLE = 1 << 6,
  ATTR_STRIKE = 1 << 7,
};

typedef struct {
  bool set; /* false = terminal default */
  uint8_t r, g, b;
} color_t;

/* A composited cell. `len` bytes of UTF-8; a grapheme cluster may be several
 * codepoints, hence the buffer rather than a codepoint. width 0 means this is
 * the tail half of a wide cell and must not be painted.
 *
 * A shader may write fg, bg and attrs. It may not write text, len or width:
 * selection and copy read the terminal rather than the screen, so rewriting
 * text here would desync them, and changing width would break the invariant
 * the compositor relies on. */
typedef struct {
  char text[16];
  uint8_t len;
  uint8_t width;
  uint16_t attrs;
  color_t fg, bg;
} cell_t;

/* Where a cell sits, and what else the effect is allowed to know. */
typedef struct {
  uint16_t x, y;       /* cell position within the content rect, 0-based */
  uint16_t cols, rows; /* content size, so an effect can be positional */
  int64_t now_ms;      /* for anything animated */
  /* How long this pane has been in the state it is in, in milliseconds — the
   * bell that just rang, the pane that died a moment ago, the one you have not
   * been in for a while. `now_ms` says what time it is; a one-shot effect needs
   * to know how long ago something happened, and only the session can say. 0
   * when there is nothing to date the state from. */
  int64_t state_ms;
  bool focused;

  /* Where the cursor is, in the same rect-relative space as x/y. Only ever
   * set for the pane that owns it, so an effect that follows the cursor does
   * not chase another pane's. */
  bool has_cursor;
  uint16_t cursor_x, cursor_y;

  /* What "terminal default" means while shading. A cell whose colour is unset
   * is drawn in whatever the client's terminal calls default, and we never
   * learn that RGB — but most terminal text is default-coloured, so a shader
   * that skipped those cells would visibly do nothing. The pass materialises
   * them to these before running the chain. Configured, because guessing is
   * the one thing that would be worse. */
  color_t default_fg, default_bg;
} shade_ctx_t;

typedef struct shader shader_t;
typedef void (*shade_fn)(const shader_t *sh, const shade_ctx_t *ctx, cell_t *c);

/* Opaque to a shader: a compiled amount expression, owned by the config. When
 * one is attached the pass computes `amount` per cell before calling the
 * shader, so an effect never has to know whether its strength was a number in
 * a config file or a function of where the cell is. */
typedef struct expr_prog expr_prog_t;

struct shader {
  const char *kind; /* registry name, NULL for an empty slot */
  shade_fn fn;
  expr_prog_t *amount_expr; /* NULL when `amount` is just a number */
  color_t color;            /* the target colour, for shaders that have one */
  /* Strength, 0..255; 0 is identity, 255 is fully applied. A cell whose
   * strength works out to 0 is skipped by the pass rather than handed to the
   * shader, so a plugin is never called to do nothing -- and a cell the
   * terminal was drawing in its own default colour stays that way. */
  uint8_t amount;
  /* One number whose meaning is the shader's own, because a second parameter
   * that is a column for one effect and a radius for another is not really
   * two things:
   *   ruler     the column to mark          margin  first column to dim
   *   zebra     rows per band               spotlight  radius in columns
   *   gradient  0 down, 1 up, 2 right, 3 left
   */
  uint16_t param;
  /* SHADE_FG / SHADE_BG / SHADE_BOTH, and 0 for both. Opaque to a shader, like
   * `amount_expr` above: it is the pass that keeps a channel, so a shader
   * cannot get it wrong and does not have to know. */
  uint8_t channels;
};

/* ---- plugins ------------------------------------------------------------
 *
 * A shared library dropped in the shader directory, exporting one symbol:
 *
 *   const shader_plugin_t *slosh_shader_plugin(void);
 *
 * SLOSH_SHADER_PLUGIN() writes that for you. See
 * contrib/shader-plugin/ for a complete one.
 */

typedef struct {
  const char *name; /* what to call it in the config */
  shade_fn fn;
} shader_def_t;

typedef struct {
  uint32_t abi;         /* SLOSH_SHADER_ABI it was built against */
  uint32_t cell_size;   /* the three sizes catch a plugin built against a */
  uint32_t ctx_size;    /* copy of this header that has drifted, which a */
  uint32_t shader_size; /* version number alone would not */
  const char *name;     /* the bundle's name, for the log line */
  size_t count;
  const shader_def_t *shaders;
} shader_plugin_t;

#define SLOSH_SHADER_PLUGIN_SYM "slosh_shader_plugin"

/* Define the entry point. `defs` is a static array of shader_def_t. */
#define SLOSH_SHADER_PLUGIN(bundle_name, defs)                                 \
  const shader_plugin_t *slosh_shader_plugin(void);                            \
  const shader_plugin_t *slosh_shader_plugin(void) {                           \
    static const shader_plugin_t table = {                                     \
        .abi = SLOSH_SHADER_ABI,                                               \
        .cell_size = sizeof(cell_t),                                           \
        .ctx_size = sizeof(shade_ctx_t),                                       \
        .shader_size = sizeof(shader_t),                                       \
        .name = (bundle_name),                                                 \
        .count = sizeof(defs) / sizeof((defs)[0]),                             \
        .shaders = (defs),                                                     \
    };                                                                         \
    return &table;                                                             \
  }

#endif /* SLOSH_SHADER_ABI_H */