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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
|
/* app.c internals, shared by the app_*.c compilation units and nobody
* else: the tree, the tabs and the session struct itself, plus the
* handful of helpers every unit leans on. Everything public is in
* app.h; this header exists so one 7k-line file could become five. */
#ifndef SLOSH_APP_INTERNAL_H
#define SLOSH_APP_INTERNAL_H
#include "app.h"
#include "config.h"
#include "expr.h"
#include "shader.h"
/* A chain attached to one pane, with the programs its expressions compiled to
* kept beside it. Together because they are one lifetime: freeing the chain
* without the programs leaks, and freeing the programs without the chain leaves
* shaders pointing at freed code. */
typedef struct {
shader_t sh[SHADE_MAX];
size_t n;
expr_prog_t *exprs[SHADE_MAX];
size_t nexprs;
} inband_chain_t;
struct node {
enum { NODE_LEAF, NODE_SPLIT } kind;
node_t *parent;
rect_t rect; /* recomputed every layout pass; never trusted between them */
/* Share of the parent split, in arbitrary units. Even splits are simply
* equal weights, so resizing is not a special case of anything. */
int weight;
/* Put away: out of the layout, into a strip along the bottom, still running.
* Intent, like the weight beside it and unlike the rect and the collapsed
* flag below — layout_node must not clear this, or it would un-minimise
* every pane on every frame. */
bool minimized;
/* Floating: out of the layout, drawn on top of the tiled panes and below
* the modals. Same pattern as `minimized` — the leaf keeps its seat in the
* tree, layout skips it, the siblings absorb its share, and un-floating
* returns it home: same id, same place, nothing that referred to it has to
* be told anything.
*
* `float_rect` is where it *wants* to be, in cells. Intent: what is drawn
* each frame is this clamped to the tab's area, and the clamp never writes
* back — shrink the terminal and the float is squeezed in, grow it again
* and it is exactly where you put it. Zero-width means "never placed",
* which the placement answers with a centred default.
*
* `raised` orders overlapping floats: highest paints last, so it is on
* top. Stamped when a float is made and when a focused float is not the
* top one — in layout(), once, rather than at each place focus can move. */
bool floating;
rect_t float_rect;
uint32_t raised;
/* leaf */
pane_t *pane;
uint32_t id;
rect_t content; /* where the pane's cells go */
/* Colour passes over this pane, applied in order: one chain for its contents
* and one for its frame, because those are two rects and not one list with a
* sort key. Sent in-band by the program inside the pane, which `shaders.md`
* calls prototyping and D13 called a hazard -- both are true, so it takes
* `in_band_shaders true` to be possible at all.
*
* Each chain owns the programs its expressions compiled to. The config owns
* the ones a file produced and frees them on reload; these live exactly as
* long as the chain that was sent, which is until the next one replaces it or
* the pane goes away, and that is nobody else's schedule. */
inband_chain_t content_chain, chrome_chain;
char purpose[64];
bool purpose_locked; /* declared by a layout: in-band cannot override */
/* When this pane last became what it is, for `since` (D20). A *transition*
* cannot be derived from the frame in front of you — the frame only says
* what is true now — so this is remembered, and it is remembered in the one
* place that decides the state, which is what keeps the timestamp and the
* state it belongs to from disagreeing. `last_state` is the state it was in
* when `state_since` was stamped; PSTATE_COUNT is "in none of them", which
* is a state to have been in for a while like any other. */
pane_state_t last_state;
int64_t state_since;
/* split */
split_dir_t dir;
node_t **kids;
size_t nkids;
/* recomputed every layout pass, like the rect: this subtree did not fit and
* is drawn as a one-row header (D6). Never remembered between passes. */
bool collapsed;
bool hidden; /* a leaf inside a collapsed subtree: running, not drawn */
};
/* A tab is a layout tree and its focus. Panes in every tab keep running; only
* the current tab is composed. */
/* Where a row boundary and a column boundary meet, and the boundaries that
* meet there. Recomputed every frame with the layout it describes. */
typedef struct {
rect_t r;
uint32_t h_id[2]; /* row boundaries meeting here: these move vertically */
size_t h_edge[2];
size_t nh;
uint32_t v_id[2]; /* column boundaries: these move horizontally */
size_t v_edge[2];
size_t nv;
} corner_t;
typedef struct {
node_t *root;
node_t *focus;
/* Where the minimised panes are listed, or zero-sized when none are. Set by
* the layout and consumed by the drawing, rather than each working the
* geometry out for itself. */
rect_t min_bar;
/* The pane filling this tab on its own, or 0. Intent rather than derived
* state, like focus: nothing about the tree says a pane is zoomed, you said
* so. Kept per tab so zooming one does not disturb another. */
uint32_t zoom;
uint32_t id;
char name[64];
char purpose[64];
bool purpose_locked;
} tab_t;
struct app {
tab_t *tabs;
size_t ntabs, tabcap, cur;
uint32_t next_tab_id;
uint32_t next_id;
/* The last raise stamp handed out. A float whose `raised` equals this is
* the top one, which is what lets the focus rule stamp without spinning
* the counter every frame. */
uint32_t raise_seq;
uint16_t cols, rows;
/* The client's cell size in pixels, as reported when it attached. Panes are
* told so that a program can size an image, and so lib-vt can work out how
* many cells an image covers when the program did not say. Defaulted rather
* than zeroed: 0 means "no image can be placed", which is not a good thing
* for a headless session or a terminal that will not say. */
uint16_t cell_w, cell_h;
bool prefix;
bool quit;
bool detach;
/* One drag machine, two verbs: a title drags a pane onto another to swap
* them, a gap between panes drags the boundary. Both are started by a press
* on something the hit list says is draggable, so neither can disagree with
* what is on screen. */
struct {
enum {
DRAG_NONE,
DRAG_TITLE,
DRAG_EDGE,
DRAG_SELECT,
DRAG_BORDER,
DRAG_TAB,
/* The float verbs: the title row moves it, any other border cell
* resizes from the edges under the grab. Their own kinds rather than
* flags on DRAG_TITLE/DRAG_BORDER, because everything those two mean
* on release — swap, split — is exactly what a float must not do. */
DRAG_FLOAT_MOVE,
DRAG_FLOAT_RESIZE,
} kind;
uint32_t src; /* pane being dragged, or the split being resized */
uint32_t target; /* pane under the pointer, for the drop highlight */
/* ...or the tab under it, when a pane is dragged over the strip: the same
* gesture with a different kind of destination. Never both -- the pointer is
* over one thing, and two highlights would be two promises. */
uint32_t tab_target;
bool new_tab_target; /* the strip's `+`: a tab of its own */
size_t edge; /* which boundary of that split */
uint16_t x, y; /* where the pointer was at the last event */
bool moved; /* a press that never moves is a click */
char side; /* border press: 'l' 'r' 't' 'b' */
/* Which of the target's drop zones the pointer is in, or 0 for the
* centre. The centre is the swap; a side means "insert me beside the
* target, on this side" — the drop grammar that turns dragging into
* re-layout. */
char drop_side;
/* Which edges a float resize is holding (FEDGE_*): derived from where
* the press landed on the frame, so a bottom corner is two edges and
* follows the pointer on both axes. */
uint8_t fedges;
/* A corner drag carries the boundaries it is moving, not an index into
* the corner list: that list is rebuilt from the layout every frame, and
* the layout is the thing being changed. Halfway through a drag the
* crossing you grabbed may be a different entry, or gone — an index would
* quietly start moving somebody else's panes. */
uint32_t c_h[2];
size_t c_hedge[2];
size_t c_nh;
uint32_t c_v[2];
size_t c_vedge[2];
size_t c_nv;
} drag;
graphics_t *gfx; /* kitty images, and what the client has been told */
/* Where the pointer is. Which border that *means* is derived during the
* paint, from the rects painted in that same pass — remembering the answer
* instead left the guide describing the layout from before a split, until
* the mouse moved and corrected it. */
uint16_t ptr_x, ptr_y;
bool ptr_valid;
int64_t ptr_still_since; /* when the pointer last stopped moving */
/* Transient announcements: "copied 13 chars", a notification from a pane,
* a config reload. They stack upward from the bottom right and expire on
* their own, so nothing has to be dismissed. */
struct {
char text[128];
int64_t until;
} toasts[3];
size_t ntoasts;
/* What a selection put on the clipboard, kept for middle-click paste (the
* X11 primary-selection habit) and handed to the client as OSC 52. */
char *clipboard;
char *clipboard_pending;
/* The cheatsheet. An overlay like the finder, but a *modal*: it reads and
* is dismissed, it never navigates, so anything you press puts it away. */
bool help;
/* The modal picker: a query, a filtered list, one selection. Two things
* wear it -- the pane finder (tabs stop being navigation past about six)
* and the command palette -- and they are one machine with a subject
* rather than two that would drift apart, for the same reason the rename
* editor is. Only one can be open, which is what makes sharing the query
* and the selection honest rather than a saving. */
enum { PICK_NONE = 0, PICK_FINDER, PICK_PALETTE, PICK_WORKSPACES } picker;
/* What the project picker is listing: scanned once when it opens, because
* draw_picker asks for its rows every frame and a readdir per repaint is a
* filesystem walk at 120Hz. Once per opening is when the answer has to be
* right, which is why nothing is kept for longer. */
project_t *projects;
size_t nprojects;
char query[64];
size_t sel;
/* Renaming in place: the title cell becomes the editor, so the name is typed
* where it will live rather than in a dialog somewhere else. A pane's title
* and a tab's label are the same gesture on two different things, so this is
* one editor with a subject rather than two machines that would drift. */
enum { RENAME_NONE = 0, RENAME_PANE, RENAME_TAB, RENAME_PURPOSE } renaming;
uint32_t rename_id;
/* As wide as a pane title, so seeding the editor never truncates one (and so
* never truncates one mid-UTF-8). */
char rename_buf[256];
/* The first click of a candidate double-click on a title. The *kind* is
* remembered with it: pane ids and tab ids are separate sequences, so pane 2
* and tab 2 both exist, and without this a click on one followed by a click
* on the other reads as a double-click on neither. */
int64_t name_click_ms;
uint32_t name_click_id;
/* The same idea for a pane's contents: a second click on the *same cell* of
* the same pane selects the word there. Per cell as well as per pane, so
* clicking two different words quickly selects the second one rather than
* everything between them. */
int64_t cell_click_ms;
uint32_t cell_click_id;
uint16_t cell_click_x, cell_click_y;
int name_click_kind;
/* Set while a layout is being built, by a `focus=true` pane, and consumed
* by the tab that contains it: focus belongs to a tab, and the tab does not
* exist yet when the pane declaring it is made. */
node_t *restore_focus;
/* Which tab a layout said was active, resolved after every tab exists --
* the index is only meaningful once the array has stopped growing. */
size_t restore_tab;
/* Set for the duration of one app_apply_layout: every pane it builds starts
* suspended whatever the file said. `open-workspace suspended:true` is the
* "open ten projects, run zero processes" case, which is a different question
* from the one a project's layout answered about which of *its* panes are
* expensive -- and it has to be decided before anything spawns, because a pane
* is created suspended or created running and there is no un-running one. */
bool force_suspend;
const char *const *argv;
/* What this session is called. The server knows it because it opened the
* socket under that name; the app only knows it because it is worth saying
* out loud when several are running. */
char session[64];
/* The last layout pass made this tab a list rather than a layout (D6). Same
* rule as `animating`: derived every pass, never remembered across one. */
bool flattened;
/* the screen we last composed into: its hit list is what a click resolves
* against, so routing can never consult geometry the user never saw */
corner_t corners[16];
size_t ncorners;
const screen_t *painted;
/* Something on the frame we last composed is animated: a shader whose amount
* reads the clock ran over it. Derived every frame from the chains actually
* applied, like the rect and the collapsed flag and for the same reason —
* remembering "this session animates" would keep a clock running for a pulse
* that was hung off a state no pane is in any more. */
bool animating;
/* The logo splash: shown until this instant, 0 for not at all. Set when a
* client attaches (app_splash), ended early by any key or click. The
* timestamp doubles as the seed that picks the colour effect and the
* particle motion this one wears, unless splash_fx / splash_motion (>= 0,
* from the control API) already chose. */
int64_t splash_until;
int splash_fx;
int splash_motion;
};
/* One config per process, owned by app.c (ensure_config/app_reload_config
* write it); everything else reads it. The macros are the palette spelled
* short, because chrome-drawing code says these names hundreds of times. */
extern config_t CFG;
#define WEIGHT_UNIT 1000
#define WEIGHT_MIN 150 /* a pane can be squeezed, not squeezed out */
#define WEIGHT_STEP 120 /* one keyboard nudge */
/* Which edges of a float a resize is moving. */
#define FEDGE_L 1u
#define FEDGE_R 2u
#define FEDGE_T 4u
#define FEDGE_B 8u
#define MIN_PANE_COLS (CFG.min_pane_cols)
#define MIN_PANE_ROWS (CFG.min_pane_rows)
#define FRAME_FOCUS (CFG.frame_focus)
#define FRAME_IDLE (CFG.frame_idle)
#define TITLE_FOCUS (CFG.title_focus)
#define BTN_FG (CFG.button_fg)
#define BTN_BG (CFG.button_bg)
#define BTN_BG_IDLE (CFG.button_bg_idle)
#define TITLE_IDLE (CFG.title_idle)
#define GUIDE (CFG.guide)
#define RESIZE_C (CFG.resize)
#define DROP_C (CFG.drop_target)
#define SCROLL_FG (CFG.scroll_fg)
#define SCROLL_BG (CFG.scroll_bg)
#define HEADER (CFG.header)
#define HEADER_HOVER (CFG.header_hover)
#define HEADER_HOVER_TITLE (CFG.header_hover_title)
#define TAB_ACTIVE_FG (CFG.tab_active_fg)
#define TAB_ACTIVE_BG (CFG.tab_active_bg)
#define TAB_ACTIVE_HOVER_FG (CFG.tab_active_hover_fg)
#define TAB_IDLE (CFG.tab_idle)
#define TAB_HOVER (CFG.tab_hover)
#define PREFIX_FG (CFG.prefix_fg)
#define PREFIX_BG (CFG.prefix_bg)
#define TAB_COUNT (CFG.tab_count)
#define STATUS_C (CFG.status)
#define STATUS_STATE (CFG.status_state)
#define FINDER_FG (CFG.finder_fg)
#define FINDER_BG (CFG.finder_bg)
#define FINDER_SEL_FG (CFG.finder_sel_fg)
#define FINDER_SEL_BG (CFG.finder_sel_bg)
#define TOAST_FG (CFG.toast_fg)
#define TOAST_BG (CFG.toast_bg)
#define RENAME_FG (CFG.rename_fg)
#define RENAME_BG (CFG.rename_bg)
#define BELL_C (CFG.bell)
#define PANE_BTN (CFG.pane_button)
#define PANE_BTN_HOVER (CFG.pane_button_hover)
#define MINBAR (CFG.minbar)
#define MINBAR_HOVER (CFG.minbar_hover)
#define HINT_C (CFG.hint)
#define DEAD_C (CFG.dead)
#define MODAL_FG (CFG.modal_fg)
#define MODAL_BG (CFG.modal_bg)
#define MODAL_BORDER (CFG.modal_border)
#define MODAL_TITLE (CFG.modal_title)
#define MODAL_BTN (CFG.modal_button)
#define MODAL_BTN_HOVER (CFG.modal_button_hover)
static const color_t NO_COLOR = {0};
/* The current tab: small enough to live here, called from everywhere. */
static inline tab_t *cur(app_t *a) { return &a->tabs[a->cur]; }
typedef void (*leaf_fn)(node_t *, void *);
/* Formerly static in app.c; shared across the app_*.c units since the
* split. Grouped by the unit that defines them. */
/* src/app.c */
const char *const *default_argv(app_t *a);
uint16_t cells(const char *str);
void exit_words(const pane_t *p, char *out, size_t cap);
node_t *leaf_new(app_t *a);
node_t *leaf_new_ex(app_t *a, const char *const argv[], const char *cwd,
bool suspended, const char *label);
void node_free(node_t *n);
int64_t now_ms_(void);
bool ptr_on(const app_t *a, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
bool run_action(app_t *a, action_t act);
void sanitise_purpose(const char *in, char *out, size_t cap);
tab_t *tab_add(app_t *a, const char *name);
size_t tab_of(app_t *a, node_t *n);
void walk_all(app_t *a, leaf_fn fn, void *ud);
/* src/app_layout.c */
void close_leaf(app_t *a, node_t *leaf);
node_t *first_leaf_of(node_t *n);
void focus_dir(app_t *a, int dx, int dy);
void layout(app_t *a);
node_t *pane_by_id(app_t *a, uint32_t id);
split_dir_t side_dir(char side);
bool split_fits(node_t *leaf, split_dir_t dir);
void split_focus(app_t *a, split_dir_t dir);
void split_focus_auto(app_t *a);
void split_focus_ui(app_t *a, split_dir_t dir);
void transfer_weight(node_t *from, node_t *to, int amount);
void float_move(app_t *a, node_t *n, int dx, int dy);
void float_resize(app_t *a, node_t *n, unsigned edges, int dx, int dy);
bool focus_float_move(app_t *a, int dx, int dy);
bool focus_float_grow(app_t *a, int sign);
uint8_t float_edges_at(const node_t *n, uint16_t mx, uint16_t my);
/* src/app_ui.c */
void picker_accept(app_t *a, const char *action);
void purpose_begin(app_t *a, uint32_t id);
void rename_begin(app_t *a, uint32_t id);
void rename_tab_begin(app_t *a, uint32_t id);
void draw_splash(app_t *a, screen_t *s);
/* round two */
void drop_pane_on_strip(app_t *a);
void focus_next(app_t *a);
const char *live_cwd(const pane_t *p, char *buf, size_t cap);
void move_tab(app_t *a, size_t from, size_t to);
bool picker_key(app_t *a, const input_event_t *ev);
bool push_pane_a_tab(app_t *a, bool forward);
void rename_end(app_t *a, bool keep);
bool rename_key(app_t *a, const input_event_t *ev);
void resize_focus(app_t *a, int dx, int dy);
void rotate_layout_ui(app_t *a);
void split_node(app_t *a, node_t *leaf, split_dir_t dir, bool before);
tab_t *tab_by_id(app_t *a, uint32_t id);
size_t tab_index(app_t *a, uint32_t id);
bool swap_panes(app_t *a, uint32_t id_a, uint32_t id_b);
bool insert_pane_beside(app_t *a, uint32_t src_id, uint32_t dst_id, char side);
struct byid {
uint32_t id;
node_t *found;
};
/* round three */
void chain_clear(inband_chain_t *c);
void walk(node_t *n, leaf_fn fn, void *ud);
void byid_cb(node_t *n, void *ud);
void find_corners(app_t *a);
void ensure_config(void);
void draw_toasts(app_t *a, screen_t *s);
void draw_tab_strip(app_t *a, screen_t *s);
void draw_status_line(app_t *a, screen_t *s);
void draw_node(app_t *a, screen_t *s, node_t *n);
void draw_min_bar(app_t *a, screen_t *s);
void draw_corners(app_t *a, screen_t *s);
void draw_floats(app_t *a, screen_t *s);
size_t count_leaves(node_t *n);
size_t collect_minimized(node_t *n, node_t **out, size_t cap, size_t k);
size_t collect_floating(node_t *n, node_t **out, size_t cap, size_t k);
size_t collect_leaves(node_t *n, node_t **out, size_t cap, size_t k);
#endif /* SLOSH_APP_INTERNAL_H */
|