1 #include "../../arcan_shmif.h"
2 #include "../../arcan_tui.h"
3 #include "../tui_int.h"
4 #include "../screen/libtsm.h"
5 #include <pthread.h>
6 #include <errno.h>
7 #include <assert.h>
8
9 typedef void* TTF_Font;
10 #include "../raster/raster.h"
11 #include "../raster/draw.h"
12
13 /*
14 * This is used to translate from the virtual screen in TSM to our own front/back
15 * buffer structure that is then 'rendered' into the packing format used in
16 * tui_raster.c
17 *
18 * Eventually this will be entirely unnecessary and we can rightfully kill off
19 * TSM and ust draw into our buffers directly, as the line wrapping / tracking
20 * behavior etc. doesn't really match how things are structured anymore
21 */
tsm_draw_callback(struct tsm_screen * screen,uint32_t id,const uint32_t * ch,size_t len,unsigned width,unsigned x,unsigned y,const struct tui_screen_attr * attr,tsm_age_t age,void * data)22 static int tsm_draw_callback(struct tsm_screen* screen, uint32_t id,
23 const uint32_t* ch, size_t len, unsigned width, unsigned x, unsigned y,
24 const struct tui_screen_attr* attr, tsm_age_t age, void* data)
25 {
26 struct tui_context* tui = data;
27
28 if (!(age && tui->age && age <= tui->age) && x < tui->cols && y < tui->rows){
29 size_t pos = y * tui->cols + x;
30 tui->front[pos].draw_ch = tui->front[pos].ch = *ch;
31 tui->front[pos].attr = *attr;
32 tui->front[pos].fstamp = tui->fstamp;
33 tui->dirty |= DIRTY_PARTIAL;
34 }
35
36 return 0;
37 }
38
resize_cellbuffer(struct tui_context * tui)39 static void resize_cellbuffer(struct tui_context* tui)
40 {
41 if (tui->base){
42 free(tui->base);
43 }
44
45 tui->base = NULL;
46
47 size_t buffer_sz = 2 * tui->rows * tui->cols * sizeof(struct tui_cell);
48 size_t rbuf_sz =
49 sizeof(struct tui_raster_header) + /* always there */
50 ((tui->rows * tui->cols + 2) * raster_cell_sz) + /* worst case, includes cursor */
51 ((tui->rows+2) * sizeof(struct tui_raster_line))
52 ;
53
54 tui->base = malloc(buffer_sz);
55 if (!tui->base){
56 LOG("couldn't allocate screen buffers\n");
57 return;
58 }
59
60 memset(tui->base, '\0', buffer_sz);
61 memset(tui->acon.vidb, '\0', rbuf_sz);
62
63 tui->front = tui->base;
64 tui->back = &tui->base[tui->rows * tui->cols];
65 tui->dirty |= DIRTY_FULL;
66 }
67
68 /* sweep a row from a start offset until the first deviation
69 * between front and back offset */
find_row_ofs(struct tui_context * tui,size_t row,size_t ofs)70 static ssize_t find_row_ofs(
71 struct tui_context* tui, size_t row, size_t ofs)
72 {
73 size_t pos = row * tui->cols;
74 struct tui_cell* front = &tui->front[pos];
75 struct tui_cell* back = &tui->back[pos];
76
77 for (pos = ofs; pos < tui->cols; pos++){
78 if (!tui_attr_equal(front[pos].attr,
79 back[pos].attr) || front[pos].ch != back[pos].ch){
80 return pos;
81 }
82 }
83 return -1;
84 }
85
pack_u32(uint32_t src,uint8_t * outb)86 static void pack_u32(uint32_t src, uint8_t* outb)
87 {
88 outb[0] = (uint8_t)(src >> 0);
89 outb[1] = (uint8_t)(src >> 8);
90 outb[2] = (uint8_t)(src >> 16);
91 outb[3] = (uint8_t)(src >> 24);
92 }
93
cell_to_rcell(struct tui_context * tui,struct tui_cell * tcell,uint8_t * outb,uint8_t has_cursor)94 static size_t cell_to_rcell(struct tui_context* tui,
95 struct tui_cell* tcell, uint8_t* outb, uint8_t has_cursor)
96 {
97 uint8_t* fc = tcell->attr.fc;
98 uint8_t* bc = tcell->attr.bc;
99
100 /* if indexed, then fc[0] and bc[0] refer to color index to draw rather
101 * than the actual values */
102 if (tcell->attr.aflags & TUI_ATTR_COLOR_INDEXED){
103 fc = tui->colors[fc[0] % TUI_COL_LIMIT].rgb;
104 bc = tui->colors[bc[0] % TUI_COL_LIMIT].bg;
105 }
106
107 /* inverse isn't an attribute on the packing level, we simply modify the colors
108 * as the 'inverse' attribute is a left-over from the terminal emulation days */
109 if (tcell->attr.aflags & TUI_ATTR_INVERSE){
110 /* use the tactic of picking 'new foreground / background' based on the
111 * intensity of the current-cell colours rather than say, fg <=> bg */
112 float intens =
113 (0.299f * fc[0] +
114 0.587f * fc[1] +
115 0.114f * fc[2]) / 255.0f;
116
117 if (intens < 0.5f){
118 *outb++ = 0xff; *outb++ = 0xff; *outb++ = 0xff;
119 }
120 else {
121 *outb++ = 0x00; *outb++ = 0x00; *outb++ = 0x00;
122 }
123 *outb++ = fc[0];
124 *outb++ = fc[1];
125 *outb++ = fc[2];
126 }
127 else {
128 *outb++ = fc[0];
129 *outb++ = fc[1];
130 *outb++ = fc[2];
131 *outb++ = bc[0];
132 *outb++ = bc[1];
133 *outb++ = bc[2];
134 }
135
136 /* this deviates from the tui cell here, the terminal- legacy blink
137 * and protect bits are not kept */
138 *outb++ = (
139 (!!(tcell->attr.aflags & TUI_ATTR_BOLD)) << 0 |
140 (!!(tcell->attr.aflags & TUI_ATTR_UNDERLINE)) << 1 |
141 (!!(tcell->attr.aflags & TUI_ATTR_ITALIC)) << 2 |
142 (!!(tcell->attr.aflags & TUI_ATTR_STRIKETHROUGH)) << 3 |
143 (!!(tcell->attr.aflags & TUI_ATTR_SHAPE_BREAK)) << 4 |
144 has_cursor << 5 |
145 (!!(tcell->attr.aflags & TUI_ATTR_UNDERLINE_ALT)) << 6
146 );
147
148 *outb++ = (
149 (!!(tcell->attr.aflags & TUI_ATTR_BORDER_RIGHT)) << 0 |
150 (!!(tcell->attr.aflags & TUI_ATTR_BORDER_DOWN)) << 1
151 );
152
153 pack_u32(tcell->ch, outb);
154 return raster_cell_sz;
155 }
156
tui_screen_tpack(struct tui_context * tui,struct tpack_gen_opts opts,uint8_t ** rbuf,size_t * rbuf_sz)157 int tui_screen_tpack(struct tui_context* tui,
158 struct tpack_gen_opts opts, uint8_t** rbuf, size_t* rbuf_sz)
159 {
160 /* start with header */
161 int rv = 0;
162 if (!opts.full && tui->dirty == DIRTY_NONE)
163 return rv;
164
165 /* header gets written to the buffer last */
166 struct tui_raster_header hdr = {};
167 arcan_tui_get_color(tui, TUI_COL_BG, hdr.bgc);
168 hdr.bgc[3] = tui->alpha;
169
170 uint8_t* out = tui->acon.vidb;
171 size_t outsz = sizeof(hdr);
172
173 if (opts.back){
174 opts.full = true;
175 opts.synch = false;
176 }
177
178 /* this is set on a manual invalidate, or a screen or cell resize */
179 if (opts.full || (tui->dirty & DIRTY_FULL)){
180 struct tui_cell* front = tui->front;
181 struct tui_cell* back = tui->back;
182 if (opts.back)
183 front = tui->back;
184
185 /* cursor is guaranteed to be overdrawn */
186 tui->last_cursor.active = false;
187
188 hdr.flags |= RPACK_IFRAME;
189 hdr.lines = tui->rows;
190 hdr.cells = tui->rows * tui->cols;
191
192 /* on delta we need to scan the row before writing the header, here
193 * we can just precompute everything */
194 for (size_t row = 0; row < tui->rows; row++){
195 struct tui_raster_line line = {
196 .start_line = row,
197 .ncells = tui->cols,
198 };
199 memcpy(&out[outsz], &line, sizeof(line));
200 outsz += sizeof(line);
201
202 /* when updating, synch front/back cell buffer so partials can
203 * be generated later */
204 for (size_t col = 0; col < tui->cols; col++){
205 struct tui_screen_attr* attr = &front->attr;
206 if (opts.synch)
207 *back = *front;
208 outsz += cell_to_rcell(tui, front, &out[outsz], 0);
209 back++;
210 front++;
211 }
212 }
213 rv = 2;
214 }
215
216 /* delta update, find_row_ofs gives the next mismatch on the row */
217 else if (tui->dirty & DIRTY_PARTIAL){
218 for (size_t row = 0; row < tui->rows; row++){
219 ssize_t ofs = find_row_ofs(tui, row, 0);
220 if (-1 == ofs)
221 continue;
222
223 size_t row_base = row * tui->cols;
224
225 /* if we overdraw the save-cursor position, don't emit the glyph again */
226 if (tui->last_cursor.active &&
227 tui->last_cursor.row == row && tui->last_cursor.col == ofs)
228 tui->last_cursor.active = false;
229
230 /* we forward all lines where there is some kind of difference,
231 * assuming most follow the pattern -----XXXXXX----- XXXXXXXXXX,
232 * with the worst- case being X-------------X that gets 'full-line
233 * with skip-cells'). */
234 struct tui_raster_line line = {
235 .start_line = row,
236 .offset = ofs
237 };
238
239 /* alias line header position */
240 size_t line_dst = outsz;
241 outsz += sizeof(line);
242
243 while(ofs != -1 && ofs < tui->cols){
244 struct tui_cell* attr = &tui->front[row_base + ofs];
245
246 if (opts.synch)
247 tui->back[row_base + ofs] = *attr;
248
249 line.ncells++;
250 outsz += cell_to_rcell(tui, attr, &out[outsz], 0);
251 /* iterate forward */
252 ssize_t last_ofs = ofs;
253 ofs = find_row_ofs(tui, row, ofs+1);
254 if (-1 == ofs)
255 break;
256
257 /* encode 'skip-draw' cell for the ones that don't matter,
258 * just set the most significant attribute bit (ignore) */
259 for (; last_ofs + 1 != ofs; last_ofs++){
260 memset(&out[outsz], '\0', raster_cell_sz);
261 out[outsz + 6] = 128;
262 out[outsz + 7] = 0;
263 outsz += raster_cell_sz;
264 line.ncells++;
265 }
266 }
267
268 memcpy(&out[line_dst], &line, sizeof(struct tui_raster_line));
269 hdr.cells += line.ncells;
270 hdr.lines++;
271 assert(outsz == raster_hdr_sz + raster_line_sz * hdr.lines + raster_cell_sz * hdr.cells);
272 }
273
274 hdr.flags |= RPACK_DFRAME;
275 rv = 1;
276 }
277
278 /* cursor management may expose 2 separate line + cell updates (less
279 * code on the renderer side than having a special header and send the
280 * data there).:
281 * line 1. original glyph with cursor attr set.
282 * line 2. previous cursor position)
283 */
284 if (tui->dirty & DIRTY_CURSOR){
285 /* the correct line-attribute should be resolved here as well */
286 struct tui_raster_line line = {
287 .ncells = 1
288 };
289
290 if (tui->dirty == DIRTY_CURSOR){
291 hdr.flags |= RPACK_DFRAME;
292 rv = 1;
293 }
294
295 /* restore the last cursor position */
296 if (tui->last_cursor.active){
297 hdr.lines++;
298 hdr.cells++;
299 line.start_line = tui->last_cursor.row;
300 line.offset = tui->last_cursor.col;
301
302 /* NOTE: REPLACE WITH PROPER PACKING */
303 memcpy(&tui->acon.vidb[outsz], &line, sizeof(line));
304
305 outsz += raster_line_sz;
306 outsz += cell_to_rcell(tui, &tui->front[
307 line.start_line * tui->cols + line.offset], &out[outsz], 0);
308 }
309
310 /* send the new cursor */
311 hdr.lines++;
312 hdr.cells++;
313 tui->last_cursor.row = tsm_screen_get_cursor_y(tui->screen);
314 tui->last_cursor.col = tsm_screen_get_cursor_x(tui->screen);
315 line.start_line = tui->last_cursor.row;
316 line.offset = tui->last_cursor.col;
317
318 /* NOTE: REPLACE WITH PROPER PACKING */
319 memcpy(&tui->acon.vidb[outsz], &line, sizeof(line));
320 outsz += raster_line_sz;
321 outsz += cell_to_rcell(tui, &tui->front[
322 line.start_line * tui->cols + line.offset], &out[outsz], 1);
323
324 /* figure out what shape we want it in, style, blink rate etc. are
325 * all controlled 'raster' side. */
326 if (tui->cursor_off || tui->cursor_hard_off || tui->sbofs){
327 hdr.cursor_state = CURSOR_NONE;
328 }
329 else {
330 hdr.cursor_state = tui->defocus ? CURSOR_INACTIVE : CURSOR_ACTIVE;
331 }
332
333 assert(outsz == raster_hdr_sz + raster_line_sz * hdr.lines + raster_cell_sz * hdr.cells);
334 tui->last_cursor.active = true;
335 }
336
337 hdr.data_sz = hdr.lines * raster_line_sz +
338 hdr.cells * raster_cell_sz + raster_hdr_sz;
339
340 /* write the header and return */
341 /* NOTE: REPLACE WITH PROPER PACKING */
342 memcpy(tui->acon.vidb, &hdr, sizeof(hdr));
343 *rbuf_sz = outsz;
344 return rv;
345 }
346
update_screen(struct tui_context * tui,bool ign_inact)347 static void update_screen(struct tui_context* tui, bool ign_inact)
348 {
349 /* don't redraw while we have an update pending or when we
350 * are in an invisible state */
351 if (tui->inactive && !ign_inact)
352 return;
353
354 /* dirty will be set from screen resize, fix the pad region */
355 if (tui->dirty & DIRTY_FULL){
356 tsm_screen_selection_reset(tui->screen);
357 }
358 else
359 /* "always" erase previous cursor, except when cfg->nal screen state explicitly
360 * say that cursor drawing should be turned off */
361 ;
362
363 /* basic safe-guard */
364 if (!tui->front)
365 return;
366 }
367
tui_screen_resized(struct tui_context * tui)368 void tui_screen_resized(struct tui_context* tui)
369 {
370 int cols = tui->acon.w / tui->cell_w;
371 int rows = tui->acon.h / tui->cell_h;
372
373 LOG("update screensize (%d * %d), (%d * %d)\n",
374 cols, rows, (int)tui->acon.w, (int)tui->acon.h);
375
376 /* calculate the rpad/bpad regions based on the desired output size and the
377 * amount consumed by the aligned number of cells, this should ideally be zero */
378 tui->pad_w = tui->acon.w - (cols * tui->cell_w);
379 tui->pad_h = tui->acon.h - (rows * tui->cell_h);
380
381 /* if the number of cells has actually changed, we need to propagate */
382 if (cols != tui->cols || rows != tui->rows){
383 if (tui->handlers.resize)
384 tui->handlers.resize(tui,
385 tui->acon.w, tui->acon.h, cols, rows, tui->handlers.tag);
386
387 tui->cols = cols;
388 tui->rows = rows;
389
390 tsm_screen_resize(tui->screen, cols, rows);
391 resize_cellbuffer(tui);
392
393 if (tui->handlers.resized)
394 tui->handlers.resized(tui,
395 tui->acon.w, tui->acon.h, cols, rows, tui->handlers.tag);
396 }
397
398 tui->dirty |= DIRTY_FULL;
399 update_screen(tui, true);
400 }
401
tui_screen_refresh(struct tui_context * tui)402 int tui_screen_refresh(struct tui_context* tui)
403 {
404 /* synch vscreen -> screen buffer */
405 tui->flags = tsm_screen_get_flags(tui->screen);
406
407 /* this will repeatedly call tsm_draw_callback which, in turn, will update
408 * the front buffer with new glyphs. */
409 tui->age = tsm_screen_draw(tui->screen, tsm_draw_callback, tui);
410
411 if (arcan_shmif_signalstatus(&tui->acon) > 0){
412 errno = EAGAIN;
413 return -1;
414 }
415
416 uint8_t* rbuf;
417 size_t rbuf_sz;
418 int rv = tui_screen_tpack(tui,
419 (struct tpack_gen_opts){.synch = true}, &rbuf, &rbuf_sz);
420 tui->dirty = DIRTY_NONE;
421
422 /* if we raster locally or server- side is determined by the rbuf_fwd flag */
423 if (rv){
424 arcan_shmif_signal(&tui->acon, SHMIF_SIGVID | SHMIF_SIGBLK_NONE);
425 /* last offset feedback buffer can be read here for kernel offset / lookup */
426 }
427
428 return 0;
429 }
430