1 #include "termdriver.h"
2
3 // This entire driver requires unibilium
4 #ifdef HAVE_UNIBILIUM
5 #include "unibilium.h"
6
7 #include <string.h>
8 #include <stdarg.h>
9
10 /* I would love if terminfo gave us these extra strings, but it does not. At a
11 * minor risk of non-portability, define them here.
12 */
13 struct TermInfoExtraStrings {
14 const char *enter_altscreen_mode;
15 const char *exit_altscreen_mode;
16
17 const char *enter_mouse_mode;
18 const char *exit_mouse_mode;
19 };
20
21 static const struct TermInfoExtraStrings extra_strings_default = {
22 // Alternate screen + cursor save mode
23 .enter_altscreen_mode = "\e[?1049h",
24 .exit_altscreen_mode = "\e[?1049l",
25 };
26
27 static const struct TermInfoExtraStrings extra_strings_vt200_mouse = {
28 // Alternate screen + cursor save mode
29 .enter_altscreen_mode = "\e[?1049h",
30 .exit_altscreen_mode = "\e[?1049l",
31
32 // Mouse click/drag reporting
33 // Also speculatively enable SGR protocol
34 .enter_mouse_mode = "\e[?1002h\e[?1006h",
35 .exit_mouse_mode = "\e[?1002l\e[?1006l",
36 };
37
38 /* Also, some terminfo databases are incomplete. Lets provide some fallbacks
39 */
40 struct TermInfoFallback {
41 enum unibi_string cap;
42 const char *value;
43 };
44
45 const static struct TermInfoFallback terminfo_fallback[] = {
46 { unibi_erase_chars, "\e[%dX" },
47 { unibi_parm_dch, "\e[%dP" },
48 { 0 },
49 };
50
51 struct TIDriver {
52 TickitTermDriver driver;
53
54 unibi_term *ut;
55
56 struct {
57 unsigned int altscreen:1;
58 unsigned int cursorvis:1;
59 unsigned int mouse:1;
60 } mode;
61
62 struct {
63 unsigned int bce:1;
64 int colours;
65 } cap;
66
67 struct {
68 // Positioning
69 const char *cup; // cursor_address
70 const char *vpa; // row_address == vertical position absolute
71 const char *hpa; // column_address = horizontal position absolute
72
73 // Moving
74 const char *cuu; const char *cuu1; // Cursor Up
75 const char *cud; const char *cud1; // Cursor Down
76 const char *cuf; const char *cuf1; // Cursor Forward == Right
77 const char *cub; const char *cub1; // Cursor Backward == Left
78
79 // Editing
80 const char *ich; const char *ich1; // Insert Character
81 const char *dch; const char *dch1; // Delete Character
82 const char *il; const char *il1; // Insert Line
83 const char *dl; const char *dl1; // Delete Line
84 const char *ech; // Erase Character
85 const char *ed2; // Erase Data 2 == Clear screen
86 const char *stbm; // Set Top/Bottom Margins
87
88 // Formatting
89 const char *sgr; // Select Graphic Rendition
90 const char *sgr0; // Exit Attribute Mode
91 const char *sgr_i0, *sgr_i1; // SGR italic off/on
92 const char *sgr_fg; // SGR foreground colour
93 const char *sgr_bg; // SGR background colour
94
95 // Mode setting/clearing
96 const char *sm_csr; const char *rm_csr; // Set/reset mode: Cursor visible
97 } str;
98
99 const struct TermInfoExtraStrings *extra;
100 };
101
lookup_ti_string(struct TIDriver * td,const TickitTermProbeArgs * args,enum unibi_string s)102 static const char *lookup_ti_string(struct TIDriver *td, const TickitTermProbeArgs *args, enum unibi_string s)
103 {
104 const char *value = unibi_get_str(td->ut, s);
105
106 for(const struct TermInfoFallback *fb = terminfo_fallback; !value && fb->cap; fb++)
107 if(fb->cap == s)
108 value = fb->value;
109
110 if(args->ti_hook && args->ti_hook->getstr)
111 value = (*args->ti_hook->getstr)(unibi_name_str(s), value, args->ti_hook->data);
112
113 return value;
114 }
115
require_ti_string(struct TIDriver * td,const TickitTermProbeArgs * args,enum unibi_string s)116 static const char *require_ti_string(struct TIDriver *td, const TickitTermProbeArgs *args, enum unibi_string s)
117 {
118 const char *ret = lookup_ti_string(td, args, s);
119 if(ret)
120 return ret;
121
122 fprintf(stderr, "Required TI string '%s' is missing\n", unibi_name_str(s));
123 abort();
124 }
125
print(TickitTermDriver * ttd,const char * str,size_t len)126 static bool print(TickitTermDriver *ttd, const char *str, size_t len)
127 {
128 tickit_termdrv_write_str(ttd, str, len);
129 return true;
130 }
131
run_ti(TickitTermDriver * ttd,const char * str,int n_params,...)132 static void run_ti(TickitTermDriver *ttd, const char *str, int n_params, ...)
133 {
134 unibi_var_t params[9];
135 va_list args;
136
137 if(!str) {
138 fprintf(stderr, "Abort on attempt to use NULL TI string\n");
139 abort();
140 }
141
142 va_start(args, n_params);
143 for(int i = 0; i < 10 && i < n_params; i++)
144 params[i] = unibi_var_from_num(va_arg(args, int));
145
146 char tmp[64];
147 char *buf = tmp;
148 size_t len = unibi_run(str, params, buf, sizeof(tmp));
149
150 if(len > sizeof(tmp)) {
151 buf = tickit_termdrv_get_tmpbuffer(ttd, len);
152 unibi_run(str, params, buf, len);
153 }
154
155 tickit_termdrv_write_str(ttd, buf, len);
156 }
157
goto_abs(TickitTermDriver * ttd,int line,int col)158 static bool goto_abs(TickitTermDriver *ttd, int line, int col)
159 {
160 struct TIDriver *td = (struct TIDriver*)ttd;
161
162 if(line != -1 && col != -1)
163 run_ti(ttd, td->str.cup, 2, line, col);
164 else if(line != -1) {
165 if(!td->str.vpa)
166 return false;
167
168 run_ti(ttd, td->str.vpa, 1, line);
169 }
170 else if(col != -1) {
171 if(col == 0) {
172 tickit_termdrv_write_str(ttd, "\r", 1);
173 return true;
174 }
175
176 if(td->str.hpa)
177 run_ti(ttd, td->str.hpa, 1, col);
178 else if(td->str.cuf) {
179 // Emulate HPA by CR + CUF
180 tickit_termdrv_write_str(ttd, "\r", 1);
181 run_ti(ttd, td->str.cuf, 1, col);
182 }
183 else
184 return false;
185 }
186
187 return true;
188 }
189
move_rel(TickitTermDriver * ttd,int downward,int rightward)190 static bool move_rel(TickitTermDriver *ttd, int downward, int rightward)
191 {
192 struct TIDriver *td = (struct TIDriver*)ttd;
193
194 if(downward == 1 && td->str.cud1)
195 run_ti(ttd, td->str.cud1, 0);
196 else if(downward == -1 && td->str.cuu1)
197 run_ti(ttd, td->str.cuu1, 0);
198 else if(downward > 0)
199 run_ti(ttd, td->str.cud, 1, downward);
200 else if(downward < 0)
201 run_ti(ttd, td->str.cuu, 1, -downward);
202
203 if(rightward == 1 && td->str.cuf1)
204 run_ti(ttd, td->str.cuf1, 0);
205 else if(rightward == -1 && td->str.cub1)
206 run_ti(ttd, td->str.cub1, 0);
207 else if(rightward > 0)
208 run_ti(ttd, td->str.cuf, 1, rightward);
209 else if(rightward < 0)
210 run_ti(ttd, td->str.cub, 1, -rightward);
211
212 return true;
213 }
214
scrollrect(TickitTermDriver * ttd,const TickitRect * rect,int downward,int rightward)215 static bool scrollrect(TickitTermDriver *ttd, const TickitRect *rect, int downward, int rightward)
216 {
217 struct TIDriver *td = (struct TIDriver*)ttd;
218
219 if(!downward && !rightward)
220 return true;
221
222 int term_lines, term_cols;
223 tickit_term_get_size(ttd->tt, &term_lines, &term_cols);
224
225 if((tickit_rect_right(rect) == term_cols) && downward == 0) {
226 for(int line = rect->top; line < tickit_rect_bottom(rect); line++) {
227 goto_abs(ttd, line, rect->left);
228
229 if(rightward == 1 && td->str.dch1)
230 run_ti(ttd, td->str.dch1, 0);
231 else if(rightward == -1 && td->str.ich1)
232 run_ti(ttd, td->str.ich1, 0);
233 else if(rightward > 0)
234 run_ti(ttd, td->str.dch, 1, rightward);
235 else if(rightward < 0)
236 run_ti(ttd, td->str.ich, 1, -rightward);
237 }
238
239 return true;
240 }
241
242 if(rect->left == 0 && rect->cols == term_cols && rightward == 0) {
243 run_ti(ttd, td->str.stbm, 2, rect->top, tickit_rect_bottom(rect) - 1);
244
245 goto_abs(ttd, rect->top, 0);
246
247 if(downward == 1 && td->str.dl1)
248 run_ti(ttd, td->str.dl1, 0);
249 else if(downward == -1 && td->str.il1)
250 run_ti(ttd, td->str.il1, 0);
251 else if(downward > 0)
252 run_ti(ttd, td->str.dl, 1, downward);
253 else if(downward < 0)
254 run_ti(ttd, td->str.il, 1, -downward);
255
256 run_ti(ttd, td->str.stbm, 2, 0, term_lines - 1);
257 return true;
258 }
259
260 return false;
261 }
262
erasech(TickitTermDriver * ttd,int count,TickitMaybeBool moveend)263 static bool erasech(TickitTermDriver *ttd, int count, TickitMaybeBool moveend)
264 {
265 struct TIDriver *td = (struct TIDriver *)ttd;
266
267 if(count < 1)
268 return true;
269
270 /* Even if the terminal can do bce, only use ECH if we're not in
271 * reverse-video mode. Most terminals don't do rv+ECH properly
272 */
273 if(td->cap.bce && !tickit_pen_get_bool_attr(tickit_termdrv_current_pen(ttd), TICKIT_PEN_REVERSE)) {
274 run_ti(ttd, td->str.ech, 1, count);
275
276 if(moveend == TICKIT_YES)
277 move_rel(ttd, 0, count);
278 }
279 else {
280 /* TODO: consider tickit_termdrv_write_chrfill(ttd, c, n)
281 */
282 char *spaces = tickit_termdrv_get_tmpbuffer(ttd, 64);
283 memset(spaces, ' ', 64);
284 while(count > 64) {
285 tickit_termdrv_write_str(ttd, spaces, 64);
286 count -= 64;
287 }
288 tickit_termdrv_write_str(ttd, spaces, count);
289
290 if(moveend == TICKIT_NO)
291 move_rel(ttd, 0, -count);
292 }
293
294 return true;
295 }
296
clear(TickitTermDriver * ttd)297 static bool clear(TickitTermDriver *ttd)
298 {
299 struct TIDriver *td = (struct TIDriver *)ttd;
300
301 run_ti(ttd, td->str.ed2, 0);
302
303 return true;
304 }
305
chpen(TickitTermDriver * ttd,const TickitPen * delta,const TickitPen * final)306 static bool chpen(TickitTermDriver *ttd, const TickitPen *delta, const TickitPen *final)
307 {
308 struct TIDriver *td = (struct TIDriver *)ttd;
309
310 /* TODO: This is all a bit of a mess
311 * Would be nicer to detect if fg/bg colour are changed, and if not, use
312 * the individual enter/exit modes from the delta pen
313 */
314
315 run_ti(ttd, td->str.sgr, 9,
316 0, // standout
317 tickit_pen_get_bool_attr(final, TICKIT_PEN_UNDER),
318 tickit_pen_get_bool_attr(final, TICKIT_PEN_REVERSE),
319 tickit_pen_get_bool_attr(final, TICKIT_PEN_BLINK),
320 0, // dim
321 tickit_pen_get_bool_attr(final, TICKIT_PEN_BOLD),
322 0, // invisible
323 0, // protect
324 0); // alt charset
325
326 if(tickit_pen_has_attr(delta, TICKIT_PEN_ITALIC)) {
327 if(td->str.sgr_i1 && tickit_pen_get_bool_attr(delta, TICKIT_PEN_ITALIC))
328 run_ti(ttd, td->str.sgr_i1, 0);
329 else if(td->str.sgr_i0)
330 run_ti(ttd, td->str.sgr_i0, 0);
331 }
332
333 int c;
334 if((c = tickit_pen_get_colour_attr(final, TICKIT_PEN_FG)) > -1 &&
335 c < td->cap.colours)
336 run_ti(ttd, td->str.sgr_fg, 1, c);
337
338 if((c = tickit_pen_get_colour_attr(final, TICKIT_PEN_BG)) > -1 &&
339 c < td->cap.colours)
340 run_ti(ttd, td->str.sgr_bg, 1, c);
341
342 return true;
343 }
344
getctl_int(TickitTermDriver * ttd,TickitTermCtl ctl,int * value)345 static bool getctl_int(TickitTermDriver *ttd, TickitTermCtl ctl, int *value)
346 {
347 struct TIDriver *td = (struct TIDriver *)ttd;
348
349 switch(ctl) {
350 case TICKIT_TERMCTL_ALTSCREEN:
351 *value = td->mode.altscreen;
352 return true;
353
354 case TICKIT_TERMCTL_CURSORVIS:
355 *value = td->mode.cursorvis;
356 return true;
357
358 case TICKIT_TERMCTL_MOUSE:
359 *value = td->mode.mouse;
360 return true;
361
362 case TICKIT_TERMCTL_COLORS:
363 *value = td->cap.colours;
364 return true;
365
366 default:
367 return false;
368 }
369 }
370
setctl_int(TickitTermDriver * ttd,TickitTermCtl ctl,int value)371 static bool setctl_int(TickitTermDriver *ttd, TickitTermCtl ctl, int value)
372 {
373 struct TIDriver *td = (struct TIDriver *)ttd;
374
375 switch(ctl) {
376 case TICKIT_TERMCTL_ALTSCREEN:
377 if(!td->extra->enter_altscreen_mode)
378 return false;
379
380 if(!td->mode.altscreen == !value)
381 return true;
382
383 tickit_termdrv_write_str(ttd, value ? td->extra->enter_altscreen_mode : td->extra->exit_altscreen_mode, 0);
384 td->mode.altscreen = !!value;
385 return true;
386
387 case TICKIT_TERMCTL_CURSORVIS:
388 if(!td->mode.cursorvis == !value)
389 return true;
390
391 run_ti(ttd, value ? td->str.sm_csr : td->str.rm_csr, 0);
392 td->mode.cursorvis = !!value;
393 return true;
394
395 case TICKIT_TERMCTL_MOUSE:
396 if(!td->extra->enter_mouse_mode)
397 return false;
398
399 if(!td->mode.mouse == !value)
400 return true;
401
402 tickit_termdrv_write_str(ttd, value ? td->extra->enter_mouse_mode : td->extra->exit_mouse_mode, 0);
403 td->mode.mouse = !!value;
404 return true;
405
406 default:
407 return false;
408 }
409 }
410
setctl_str(TickitTermDriver * ttd,TickitTermCtl ctl,const char * value)411 static bool setctl_str(TickitTermDriver *ttd, TickitTermCtl ctl, const char *value)
412 {
413 return false;
414 }
415
attach(TickitTermDriver * ttd,TickitTerm * tt)416 static void attach(TickitTermDriver *ttd, TickitTerm *tt)
417 {
418 struct TIDriver *td = (struct TIDriver *)ttd;
419 unibi_term *ut = td->ut;
420
421 tickit_term_set_size(tt, unibi_get_num(ut, unibi_lines), unibi_get_num(ut, unibi_columns));
422 }
423
start(TickitTermDriver * ttd)424 static void start(TickitTermDriver *ttd)
425 {
426 // Nothing needed
427 }
428
shutdown(TickitTermDriver * ttd)429 static void shutdown(TickitTermDriver *ttd)
430 {
431 struct TIDriver *td = (struct TIDriver *)ttd;
432
433 if(td->mode.mouse)
434 tickit_termdrv_write_str(ttd, td->extra->exit_mouse_mode, 0);
435 if(!td->mode.cursorvis)
436 run_ti(ttd, td->str.sm_csr, 0);
437 if(td->mode.altscreen)
438 tickit_termdrv_write_str(ttd, td->extra->exit_altscreen_mode, 0);
439
440 run_ti(ttd, td->str.sgr0, 0);
441 }
442
resume(TickitTermDriver * ttd)443 static void resume(TickitTermDriver *ttd)
444 {
445 struct TIDriver *td = (struct TIDriver *)ttd;
446
447 if(td->mode.altscreen)
448 tickit_termdrv_write_str(ttd, td->extra->enter_altscreen_mode, 0);
449 if(!td->mode.cursorvis)
450 run_ti(ttd, td->str.rm_csr, 0);
451 if(td->mode.mouse)
452 tickit_termdrv_write_str(ttd, td->extra->enter_mouse_mode, 0);
453 }
454
destroy(TickitTermDriver * ttd)455 static void destroy(TickitTermDriver *ttd)
456 {
457 struct TIDriver *td = (struct TIDriver *)ttd;
458
459 unibi_destroy(td->ut);
460
461 free(td);
462 }
463
464 static TickitTermDriverVTable ti_vtable = {
465 .attach = attach,
466 .destroy = destroy,
467 .start = start,
468 .stop = shutdown,
469 .pause = shutdown,
470 .resume = resume,
471 .print = print,
472 .goto_abs = goto_abs,
473 .move_rel = move_rel,
474 .scrollrect = scrollrect,
475 .erasech = erasech,
476 .clear = clear,
477 .chpen = chpen,
478 .getctl_int = getctl_int,
479 .setctl_int = setctl_int,
480 .setctl_str = setctl_str,
481 };
482
new(const TickitTermProbeArgs * args)483 static TickitTermDriver *new(const TickitTermProbeArgs *args)
484 {
485 unibi_term *ut = unibi_from_term(args->termtype);
486 if(!ut)
487 return NULL;
488
489 struct TIDriver *td = malloc(sizeof(struct TIDriver));
490 td->driver.vtable = &ti_vtable;
491
492 td->ut = ut;
493
494 td->mode.mouse = 0;
495 td->mode.cursorvis = 1;
496 td->mode.altscreen = 0;
497
498 td->cap.bce = unibi_get_bool(ut, unibi_back_color_erase);
499 td->cap.colours = unibi_get_num(ut, unibi_max_colors);
500
501 td->str.cup = require_ti_string(td, args, unibi_cursor_address);
502 td->str.vpa = lookup_ti_string (td, args, unibi_row_address);
503 td->str.hpa = lookup_ti_string (td, args, unibi_column_address);
504 td->str.cuu = require_ti_string(td, args, unibi_parm_up_cursor);
505 td->str.cuu1 = lookup_ti_string (td, args, unibi_cursor_up);
506 td->str.cud = require_ti_string(td, args, unibi_parm_down_cursor);
507 td->str.cud1 = lookup_ti_string (td, args, unibi_cursor_down);
508 td->str.cuf = require_ti_string(td, args, unibi_parm_right_cursor);
509 td->str.cuf1 = lookup_ti_string (td, args, unibi_cursor_right);
510 td->str.cub = require_ti_string(td, args, unibi_parm_left_cursor);
511 td->str.cub1 = lookup_ti_string (td, args, unibi_cursor_left);
512 td->str.ich = require_ti_string(td, args, unibi_parm_ich);
513 td->str.ich1 = lookup_ti_string (td, args, unibi_insert_character);
514 td->str.dch = require_ti_string(td, args, unibi_parm_dch);
515 td->str.dch1 = lookup_ti_string (td, args, unibi_delete_character);
516 td->str.il = require_ti_string(td, args, unibi_parm_insert_line);
517 td->str.il1 = lookup_ti_string (td, args, unibi_insert_line);
518 td->str.dl = require_ti_string(td, args, unibi_parm_delete_line);
519 td->str.dl1 = lookup_ti_string (td, args, unibi_delete_line);
520 td->str.ech = require_ti_string(td, args, unibi_erase_chars);
521 td->str.ed2 = require_ti_string(td, args, unibi_clear_screen);
522 td->str.stbm = require_ti_string(td, args, unibi_change_scroll_region);
523 td->str.sgr = require_ti_string(td, args, unibi_set_attributes);
524 td->str.sgr0 = require_ti_string(td, args, unibi_exit_attribute_mode);
525 td->str.sgr_i0 = lookup_ti_string (td, args, unibi_exit_italics_mode);
526 td->str.sgr_i1 = lookup_ti_string (td, args, unibi_enter_italics_mode);
527 td->str.sgr_fg = require_ti_string(td, args, unibi_set_a_foreground);
528 td->str.sgr_bg = require_ti_string(td, args, unibi_set_a_background);
529
530 td->str.sm_csr = require_ti_string(td, args, unibi_cursor_normal);
531 td->str.rm_csr = require_ti_string(td, args, unibi_cursor_invisible);
532
533 const char *key_mouse = lookup_ti_string(td, args, unibi_key_mouse);
534 if(key_mouse && strcmp(key_mouse, "\e[M") == 0)
535 td->extra = &extra_strings_vt200_mouse;
536 else
537 td->extra = &extra_strings_default;
538
539 return (TickitTermDriver*)td;
540 }
541
542 #else /* not HAVE_UNIBILIUM */
543
new(const TickitTermProbeArgs * args)544 static TickitTermDriver *new(const TickitTermProbeArgs *args)
545 {
546 return NULL;
547 }
548
549 #endif
550
551 TickitTermDriverProbe tickit_termdrv_probe_ti = {
552 .new = new,
553 };
554