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