1 #include "termdriver.h"
2 
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 
7 #define strneq(a,b,n) (strncmp(a,b,n)==0)
8 
9 #define CSI_MORE_SUBPARAM 0x80000000
10 #define CSI_NEXT_SUB(x)   ((x) & CSI_MORE_SUBPARAM)
11 #define CSI_PARAM(x)      ((x) & ~CSI_MORE_SUBPARAM)
12 
13 struct XTermDriver {
14   TickitTermDriver driver;
15 
16   int dcs_offset;
17   char dcs_buffer[16];
18 
19   struct {
20     unsigned int altscreen:1;
21     unsigned int cursorvis:1;
22     unsigned int cursorblink:1;
23     unsigned int cursorshape:2;
24     unsigned int mouse:2;
25     unsigned int keypad:1;
26   } mode;
27 
28   struct {
29     unsigned int cursorshape:1;
30     unsigned int slrm:1;
31     unsigned int csi_sub_colon:1;
32     unsigned int rgb8:1;
33   } cap;
34 
35   struct {
36     unsigned int cursorvis:1;
37     unsigned int cursorblink:1;
38     unsigned int cursorshape:2;
39     unsigned int slrm:1;
40   } initialised;
41 };
42 
print(TickitTermDriver * ttd,const char * str,size_t len)43 static bool print(TickitTermDriver *ttd, const char *str, size_t len)
44 {
45   tickit_termdrv_write_str(ttd, str, len);
46   return true;
47 }
48 
goto_abs(TickitTermDriver * ttd,int line,int col)49 static bool goto_abs(TickitTermDriver *ttd, int line, int col)
50 {
51   if(line != -1 && col > 0)
52     tickit_termdrv_write_strf(ttd, "\e[%d;%dH", line+1, col+1);
53   else if(line != -1 && col == 0)
54     tickit_termdrv_write_strf(ttd, "\e[%dH", line+1);
55   else if(line != -1)
56     tickit_termdrv_write_strf(ttd, "\e[%dd", line+1);
57   else if(col > 0)
58     tickit_termdrv_write_strf(ttd, "\e[%dG", col+1);
59   else if(col != -1)
60     tickit_termdrv_write_str(ttd, "\e[G", 3);
61 
62   return true;
63 }
64 
move_rel(TickitTermDriver * ttd,int downward,int rightward)65 static bool move_rel(TickitTermDriver *ttd, int downward, int rightward)
66 {
67   if(downward > 1)
68     tickit_termdrv_write_strf(ttd, "\e[%dB", downward);
69   else if(downward == 1)
70     tickit_termdrv_write_str(ttd, "\e[B", 3);
71   else if(downward == -1)
72     tickit_termdrv_write_str(ttd, "\e[A", 3);
73   else if(downward < -1)
74     tickit_termdrv_write_strf(ttd, "\e[%dA", -downward);
75 
76   if(rightward > 1)
77     tickit_termdrv_write_strf(ttd, "\e[%dC", rightward);
78   else if(rightward == 1)
79     tickit_termdrv_write_str(ttd, "\e[C", 3);
80   else if(rightward == -1)
81     tickit_termdrv_write_str(ttd, "\e[D", 3);
82   else if(rightward < -1)
83     tickit_termdrv_write_strf(ttd, "\e[%dD", -rightward);
84 
85   return true;
86 }
87 
scrollrect(TickitTermDriver * ttd,const TickitRect * rect,int downward,int rightward)88 static bool scrollrect(TickitTermDriver *ttd, const TickitRect *rect, int downward, int rightward)
89 {
90   struct XTermDriver *xd = (struct XTermDriver *)ttd;
91 
92   if(!downward && !rightward)
93     return true;
94 
95   int term_cols;
96   tickit_term_get_size(ttd->tt, NULL, &term_cols);
97 
98   int right = tickit_rect_right(rect);
99 
100   /* Use DECSLRM only for 1 line of insert/delete, because any more and it's
101    * likely better to use the generic system below
102    */
103   if(((xd->cap.slrm && rect->lines == 1) || (right == term_cols))
104       && downward == 0) {
105     if(right < term_cols)
106       tickit_termdrv_write_strf(ttd, "\e[;%ds", right);
107 
108     for(int line = rect->top; line < tickit_rect_bottom(rect); line++) {
109       goto_abs(ttd, line, rect->left);
110       if(rightward > 1)
111         tickit_termdrv_write_strf(ttd, "\e[%dP", rightward);  /* DCH */
112       else if(rightward == 1)
113         tickit_termdrv_write_str(ttd, "\e[P", 3);             /* DCH1 */
114       else if(rightward == -1)
115         tickit_termdrv_write_str(ttd, "\e[@", 3);             /* ICH1 */
116       else if(rightward < -1)
117         tickit_termdrv_write_strf(ttd, "\e[%d@", -rightward); /* ICH */
118     }
119 
120     if(right < term_cols)
121       tickit_termdrv_write_strf(ttd, "\e[s");
122 
123     return true;
124   }
125 
126   if(xd->cap.slrm ||
127      (rect->left == 0 && rect->cols == term_cols && rightward == 0)) {
128     tickit_termdrv_write_strf(ttd, "\e[%d;%dr", rect->top + 1, tickit_rect_bottom(rect));
129 
130     if(rect->left > 0 || right < term_cols)
131       tickit_termdrv_write_strf(ttd, "\e[%d;%ds", rect->left + 1, right);
132 
133     goto_abs(ttd, rect->top, rect->left);
134 
135     if(downward > 1)
136       tickit_termdrv_write_strf(ttd, "\e[%dM", downward);  /* DL */
137     else if(downward == 1)
138       tickit_termdrv_write_str(ttd, "\e[M", 3);            /* DL1 */
139     else if(downward == -1)
140       tickit_termdrv_write_str(ttd, "\e[L", 3);            /* IL1 */
141     else if(downward < -1)
142       tickit_termdrv_write_strf(ttd, "\e[%dL", -downward); /* IL */
143 
144     if(rightward > 1)
145       tickit_termdrv_write_strf(ttd, "\e[%d'~", rightward);  /* DECDC */
146     else if(rightward == 1)
147       tickit_termdrv_write_str(ttd, "\e['~", 4);             /* DECDC1 */
148     else if(rightward == -1)
149       tickit_termdrv_write_str(ttd, "\e['}", 4);             /* DECIC1 */
150     if(rightward < -1)
151       tickit_termdrv_write_strf(ttd, "\e[%d'}", -rightward); /* DECIC */
152 
153     tickit_termdrv_write_str(ttd, "\e[r", 3);
154 
155     if(rect->left > 0 || right < term_cols)
156       tickit_termdrv_write_str(ttd, "\e[s", 3);
157 
158     return true;
159   }
160 
161   return false;
162 }
163 
erasech(TickitTermDriver * ttd,int count,TickitMaybeBool moveend)164 static bool erasech(TickitTermDriver *ttd, int count, TickitMaybeBool moveend)
165 {
166   if(count < 1)
167     return true;
168 
169   /* Only use ECH if we're not in reverse-video mode. xterm doesn't do rv+ECH
170    * properly
171    */
172   if(!tickit_pen_get_bool_attr(tickit_termdrv_current_pen(ttd), TICKIT_PEN_REVERSE)) {
173     if(count == 1)
174       tickit_termdrv_write_str(ttd, "\e[X", 3);
175     else
176       tickit_termdrv_write_strf(ttd, "\e[%dX", count);
177 
178     if(moveend == TICKIT_YES)
179       move_rel(ttd, 0, count);
180   }
181   else {
182      /* TODO: consider tickit_termdrv_write_chrfill(ttd, c, n)
183      */
184     char *spaces = tickit_termdrv_get_tmpbuffer(ttd, 64);
185     memset(spaces, ' ', 64);
186     while(count > 64) {
187       tickit_termdrv_write_str(ttd, spaces, 64);
188       count -= 64;
189     }
190     tickit_termdrv_write_str(ttd, spaces, count);
191 
192     if(moveend == TICKIT_NO)
193       move_rel(ttd, 0, -count);
194   }
195 
196   return true;
197 }
198 
clear(TickitTermDriver * ttd)199 static bool clear(TickitTermDriver *ttd)
200 {
201   tickit_termdrv_write_strf(ttd, "\e[2J", 4);
202   return true;
203 }
204 
205 static struct SgrOnOff { int on, off; } sgr_onoff[] = {
206   {},         /* none */
207   { 30, 39 }, /* fg */
208   { 40, 49 }, /* bg */
209   {  1, 22 }, /* bold */
210   {  4, 24 }, /* under */
211   {  3, 23 }, /* italic */
212   {  7, 27 }, /* reverse */
213   {  9, 29 }, /* strike */
214   { 10, 10 }, /* altfont */
215   {  5, 25 }, /* blink */
216 };
217 
chpen(TickitTermDriver * ttd,const TickitPen * delta,const TickitPen * final)218 static bool chpen(TickitTermDriver *ttd, const TickitPen *delta, const TickitPen *final)
219 {
220   struct XTermDriver *xd = (struct XTermDriver *)ttd;
221 
222   /* There can be at most 16 SGR parameters; 5 from each of 2 colours, and
223    * 6 single attributes
224    */
225   int params[16];
226   int pindex = 0;
227 
228   for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++) {
229     if(!tickit_pen_has_attr(delta, attr))
230       continue;
231 
232     struct SgrOnOff *onoff = &sgr_onoff[attr];
233 
234     int val;
235 
236     switch(attr) {
237     case TICKIT_PEN_FG:
238     case TICKIT_PEN_BG:
239       val = tickit_pen_get_colour_attr(delta, attr);
240       if(val < 0)
241         params[pindex++] = onoff->off;
242       else if(xd->cap.rgb8 && tickit_pen_has_colour_attr_rgb8(delta, attr)) {
243         TickitPenRGB8 rgb = tickit_pen_get_colour_attr_rgb8(delta, attr);
244         params[pindex++] = (onoff->on+8) | CSI_MORE_SUBPARAM;
245         params[pindex++] = 2             | CSI_MORE_SUBPARAM;
246         params[pindex++] = rgb.r         | CSI_MORE_SUBPARAM;
247         params[pindex++] = rgb.g         | CSI_MORE_SUBPARAM;
248         params[pindex++] = rgb.b;
249       }
250       else if(val < 8)
251         params[pindex++] = onoff->on + val;
252       else if(val < 16)
253         params[pindex++] = onoff->on+60 + val-8;
254       else {
255         params[pindex++] = (onoff->on+8) | CSI_MORE_SUBPARAM;
256         params[pindex++] = 5 | CSI_MORE_SUBPARAM;
257         params[pindex++] = val;
258       }
259       break;
260 
261     case TICKIT_PEN_UNDER:
262       val = tickit_pen_get_int_attr(delta, attr);
263       if(!val)
264         params[pindex++] = onoff->off;
265       else if(val == 1)
266         params[pindex++] = onoff->on;
267       else {
268         params[pindex++] = onoff->on | CSI_MORE_SUBPARAM;
269         params[pindex++] = val;
270       }
271       break;
272 
273     case TICKIT_PEN_ALTFONT:
274       val = tickit_pen_get_int_attr(delta, attr);
275       if(val < 0 || val >= 10)
276         params[pindex++] = onoff->off;
277       else
278         params[pindex++] = onoff->on + val;
279       break;
280 
281     case TICKIT_PEN_BOLD:
282     case TICKIT_PEN_ITALIC:
283     case TICKIT_PEN_REVERSE:
284     case TICKIT_PEN_STRIKE:
285     case TICKIT_PEN_BLINK:
286       val = tickit_pen_get_bool_attr(delta, attr);
287       params[pindex++] = val ? onoff->on : onoff->off;
288       break;
289 
290     case TICKIT_N_PEN_ATTRS:
291       break;
292     }
293   }
294 
295   if(pindex == 0)
296     return true;
297 
298   /* If we're going to clear all the attributes then empty SGR is neater */
299   if(!tickit_pen_is_nondefault(final))
300     pindex = 0;
301 
302   /* Render params[] into a CSI string */
303 
304   size_t len = 3; /* ESC [ ... m */
305   for(int i = 0; i < pindex; i++)
306     len += snprintf(NULL, 0, "%d", CSI_PARAM(params[i])) + 1;
307   if(pindex > 0)
308     len--; /* Last one has no final separator */
309 
310   char *buffer = tickit_termdrv_get_tmpbuffer(ttd, len + 1);
311   char *s = buffer;
312 
313   s += sprintf(s, "\e[");
314   for(int i = 0; i < pindex-1; i++)
315     s += sprintf(s, "%d%c", CSI_PARAM(params[i]),
316         CSI_NEXT_SUB(params[i]) && xd->cap.csi_sub_colon ? ':' : ';');
317   if(pindex > 0)
318     s += sprintf(s, "%d", CSI_PARAM(params[pindex-1]));
319   sprintf(s, "m");
320 
321   tickit_termdrv_write_str(ttd, buffer, len);
322 
323   return true;
324 }
325 
getctl_int(TickitTermDriver * ttd,TickitTermCtl ctl,int * value)326 static bool getctl_int(TickitTermDriver *ttd, TickitTermCtl ctl, int *value)
327 {
328   struct XTermDriver *xd = (struct XTermDriver *)ttd;
329 
330   switch(ctl) {
331     case TICKIT_TERMCTL_ALTSCREEN:
332       *value = xd->mode.altscreen;
333       return true;
334 
335     case TICKIT_TERMCTL_CURSORVIS:
336       *value = xd->mode.cursorvis;
337       return true;
338 
339     case TICKIT_TERMCTL_CURSORBLINK:
340       *value = xd->mode.cursorblink;
341       return true;
342 
343     case TICKIT_TERMCTL_MOUSE:
344       *value = xd->mode.mouse;
345       return true;
346 
347     case TICKIT_TERMCTL_CURSORSHAPE:
348       *value = xd->mode.cursorshape;
349       return true;
350 
351     case TICKIT_TERMCTL_KEYPAD_APP:
352       *value = xd->mode.keypad;
353       return true;
354 
355     case TICKIT_TERMCTL_COLORS:
356       *value = xd->cap.rgb8 ? (1<<24) : 256;
357       return true;
358 
359     default:
360       return false;
361   }
362 }
363 
mode_for_mouse(TickitTermMouseMode mode)364 static int mode_for_mouse(TickitTermMouseMode mode)
365 {
366   switch(mode) {
367     case TICKIT_TERM_MOUSEMODE_CLICK: return 1000;
368     case TICKIT_TERM_MOUSEMODE_DRAG:  return 1002;
369     case TICKIT_TERM_MOUSEMODE_MOVE:  return 1003;
370 
371     case TICKIT_TERM_MOUSEMODE_OFF:
372       break;
373   }
374   return 0;
375 }
376 
setctl_int(TickitTermDriver * ttd,TickitTermCtl ctl,int value)377 static bool setctl_int(TickitTermDriver *ttd, TickitTermCtl ctl, int value)
378 {
379   struct XTermDriver *xd = (struct XTermDriver *)ttd;
380 
381   switch(ctl) {
382     case TICKIT_TERMCTL_ALTSCREEN:
383       if(!xd->mode.altscreen == !value)
384         return true;
385 
386       tickit_termdrv_write_str(ttd, value ? "\e[?1049h" : "\e[?1049l", 0);
387       xd->mode.altscreen = !!value;
388       return true;
389 
390     case TICKIT_TERMCTL_CURSORVIS:
391       if(!xd->mode.cursorvis == !value)
392         return true;
393 
394       tickit_termdrv_write_str(ttd, value ? "\e[?25h" : "\e[?25l", 0);
395       xd->mode.cursorvis = !!value;
396       return true;
397 
398     case TICKIT_TERMCTL_CURSORBLINK:
399       if(xd->initialised.cursorblink && !xd->mode.cursorblink == !value)
400         return true;
401 
402       tickit_termdrv_write_str(ttd, value ? "\e[?12h" : "\e[?12l", 0);
403       xd->mode.cursorblink = !!value;
404       return true;
405 
406     case TICKIT_TERMCTL_MOUSE:
407       if(xd->mode.mouse == value)
408         return true;
409 
410       /* Modes 1000, 1002 and 1003 are mutually exclusive; enabling any one
411        * disables the other two
412        */
413       if(!value)
414         tickit_termdrv_write_strf(ttd, "\e[?%dl\e[?1006l", mode_for_mouse(xd->mode.mouse));
415       else
416         tickit_termdrv_write_strf(ttd, "\e[?%dh\e[?1006h", mode_for_mouse(value));
417 
418       xd->mode.mouse = value;
419       return true;
420 
421     case TICKIT_TERMCTL_CURSORSHAPE:
422       if(xd->initialised.cursorshape && xd->mode.cursorshape == value)
423         return true;
424 
425       if(xd->cap.cursorshape)
426         tickit_termdrv_write_strf(ttd, "\e[%d q", value * 2 + (xd->mode.cursorblink ? -1 : 0));
427       xd->mode.cursorshape = value;
428       return true;
429 
430     case TICKIT_TERMCTL_KEYPAD_APP:
431       if(!xd->mode.keypad == !value)
432         return true;
433 
434       tickit_termdrv_write_strf(ttd, value ? "\e=" : "\e>");
435       return true;
436 
437     default:
438       return false;
439   }
440 }
441 
setctl_str(TickitTermDriver * ttd,TickitTermCtl ctl,const char * value)442 static bool setctl_str(TickitTermDriver *ttd, TickitTermCtl ctl, const char *value)
443 {
444   switch(ctl) {
445     case TICKIT_TERMCTL_ICON_TEXT:
446       tickit_termdrv_write_strf(ttd, "\e]1;%s\e\\", value);
447       return true;
448 
449     case TICKIT_TERMCTL_TITLE_TEXT:
450       tickit_termdrv_write_strf(ttd, "\e]2;%s\e\\", value);
451       return true;
452 
453     case TICKIT_TERMCTL_ICONTITLE_TEXT:
454       tickit_termdrv_write_strf(ttd, "\e]0;%s\e\\", value);
455       return true;
456 
457     default:
458       return false;
459   }
460 }
461 
start(TickitTermDriver * ttd)462 static void start(TickitTermDriver *ttd)
463 {
464   // Enable DECSLRM
465   tickit_termdrv_write_strf(ttd, "\e[?69h");
466 
467   // Find out if DECSLRM is actually supported
468   tickit_termdrv_write_strf(ttd, "\e[?69$p");
469 
470   // Also query the current cursor visibility, blink status, and shape
471   tickit_termdrv_write_strf(ttd, "\e[?25$p\e[?12$p\eP$q q\e\\");
472 
473   // Try to work out whether the terminal supports 24bit colours (RGB8) and
474   // whether it understands : to separate sub-params
475   tickit_termdrv_write_strf(ttd, "\e[38;5;255m\e[38:2:0:1:2m\eP$qm\e\\\e[m");
476 
477   /* Some terminals (e.g. xfce4-terminal) don't understand DECRQM and print
478    * the raw bytes directly as output, while still claiming to be TERM=xterm
479    * It doens't hurt at this point to clear the current line just in case.
480    */
481   tickit_termdrv_write_strf(ttd, "\e[G\e[K");
482 
483   tickit_term_flush(ttd->tt);
484 }
485 
started(TickitTermDriver * ttd)486 static bool started(TickitTermDriver *ttd)
487 {
488   struct XTermDriver *xd = (struct XTermDriver *)ttd;
489 
490   return xd->initialised.cursorvis &&
491          xd->initialised.cursorblink &&
492          xd->initialised.cursorshape &&
493          xd->initialised.slrm;
494 }
495 
on_modereport(TickitTermDriver * ttd,int initial,int mode,int value)496 static int on_modereport(TickitTermDriver *ttd, int initial, int mode, int value)
497 {
498   struct XTermDriver *xd = (struct XTermDriver *)ttd;
499 
500   if(initial == '?') // DEC mode
501     switch(mode) {
502       case 12: // Cursor blink
503         if(value == 1)
504           xd->mode.cursorblink = 1;
505         xd->initialised.cursorblink = 1;
506         break;
507       case 25: // DECTCEM == Cursor visibility
508         if(value == 1)
509           xd->mode.cursorvis = 1;
510         xd->initialised.cursorvis = 1;
511         break;
512       case 69: // DECVSSM
513         if(value == 1 || value == 2)
514           xd->cap.slrm = 1;
515         xd->initialised.slrm = 1;
516         break;
517     }
518 
519   return 1;
520 }
521 
on_decrqss(TickitTermDriver * ttd,const char * args,size_t arglen)522 static int on_decrqss(TickitTermDriver *ttd, const char *args, size_t arglen)
523 {
524   struct XTermDriver *xd = (struct XTermDriver *)ttd;
525 
526   if(strneq(args + arglen - 2, " q", 2)) { // DECSCUSR
527     int value;
528     if(sscanf(args, "%d", &value)) {
529       // value==1 or 2 => shape == 1, 3 or 4 => 2, etc..
530       int shape = (value+1) / 2;
531       xd->mode.cursorshape = shape;
532       xd->cap.cursorshape = 1;
533     }
534     xd->initialised.cursorshape = 1;
535   }
536   else if(strneq(args + arglen - 1, "m", 1)) { // SGR
537     // skip the initial number, find the first separator
538     while(arglen && args[0] >= '0' && args[0] <= '9')
539       args++, arglen--;
540     if(!arglen)
541       return 1;
542 
543     if(args[0] == ':')
544       xd->cap.csi_sub_colon = 1;
545     args++, arglen--;
546 
547     // If the palette index is 2 then the terminal understands rgb8
548     int value;
549     if(sscanf(args, "%d", &value) && value == 2)
550       xd->cap.rgb8 = 1;
551   }
552 
553   return 1;
554 }
555 
teardown(TickitTermDriver * ttd)556 static void teardown(TickitTermDriver *ttd)
557 {
558   struct XTermDriver *xd = (struct XTermDriver *)ttd;
559 
560   if(xd->mode.mouse)
561     tickit_termdrv_write_strf(ttd, "\e[?%dl\e[?1006l", mode_for_mouse(xd->mode.mouse));
562   if(!xd->mode.cursorvis)
563     tickit_termdrv_write_str(ttd, "\e[?25h", 0);
564   if(xd->mode.altscreen)
565     tickit_termdrv_write_str(ttd, "\e[?1049l", 0);
566   if(xd->mode.keypad)
567     tickit_termdrv_write_strf(ttd, "\e>");
568 
569   // Reset pen
570   tickit_termdrv_write_str(ttd, "\e[m", 3);
571 }
572 
resume(TickitTermDriver * ttd)573 static void resume(TickitTermDriver *ttd)
574 {
575   struct XTermDriver *xd = (struct XTermDriver *)ttd;
576 
577   if(xd->mode.keypad)
578     tickit_termdrv_write_strf(ttd, "\e=");
579   if(xd->mode.altscreen)
580     tickit_termdrv_write_str(ttd, "\e[?1049h", 0);
581   if(!xd->mode.cursorvis)
582     tickit_termdrv_write_str(ttd, "\e[?25l", 0);
583   if(xd->mode.mouse)
584     tickit_termdrv_write_strf(ttd, "\e[?%dh\e[?1006h", mode_for_mouse(xd->mode.mouse));
585 }
586 
destroy(TickitTermDriver * ttd)587 static void destroy(TickitTermDriver *ttd)
588 {
589   struct XTermDriver *xd = (struct XTermDriver *)ttd;
590 
591   free(xd);
592 }
593 
594 static TickitTermDriverVTable xterm_vtable = {
595   .destroy    = destroy,
596   .start      = start,
597   .started    = started,
598   .stop       = teardown,
599   .pause      = teardown,
600   .resume     = resume,
601   .print      = print,
602   .goto_abs   = goto_abs,
603   .move_rel   = move_rel,
604   .scrollrect = scrollrect,
605   .erasech    = erasech,
606   .clear      = clear,
607   .chpen      = chpen,
608   .getctl_int = getctl_int,
609   .setctl_int = setctl_int,
610   .setctl_str = setctl_str,
611   .on_modereport = on_modereport,
612   .on_decrqss    = on_decrqss,
613 };
614 
new(const TickitTermProbeArgs * args)615 static TickitTermDriver *new(const TickitTermProbeArgs *args)
616 {
617   const char *termtype = args->termtype;
618 
619   if(strncmp(termtype, "xterm", 5) != 0)
620     return NULL;
621 
622   switch(termtype[5]) {
623     case 0: case '-':
624       break;
625     default:
626       return NULL;
627   }
628 
629   struct XTermDriver *xd = malloc(sizeof(struct XTermDriver));
630   xd->driver.vtable = &xterm_vtable;
631 
632   xd->dcs_offset = -1;
633 
634   memset(&xd->mode, 0, sizeof xd->mode);
635   xd->mode.cursorvis = 1;
636 
637   memset(&xd->cap, 0, sizeof xd->cap);
638 
639   memset(&xd->initialised, 0, sizeof xd->initialised);
640 
641   return (TickitTermDriver*)xd;
642 }
643 
644 TickitTermDriverProbe tickit_termdrv_probe_xterm = {
645   .new = new,
646 };
647