]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/pterm.c
1cf6e6e11298cf190a6bf5ad196d5b0ed0b4046c
[PuTTY.git] / unix / pterm.c
1 /*
2  * pterm - a fusion of the PuTTY terminal emulator with a Unix pty
3  * back end, all running as a GTK application. Wish me luck.
4  */
5
6 #include <string.h>
7 #include <assert.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <time.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <unistd.h>
14 #include <gtk/gtk.h>
15 #include <gdk/gdkkeysyms.h>
16
17 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */
18 #include "putty.h"
19
20 #define CAT2(x,y) x ## y
21 #define CAT(x,y) CAT2(x,y)
22 #define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
23
24 #define NCOLOURS (lenof(((Config *)0)->colours))
25
26 struct gui_data {
27     GtkWidget *area;
28     GdkPixmap *pixmap;
29     GdkFont *fonts[2];                 /* normal and bold (for now!) */
30     GdkCursor *rawcursor, *textcursor;
31     GdkColor cols[NCOLOURS];
32     GdkColormap *colmap;
33     GdkGC *black_gc, *white_gc;
34 };
35
36 static struct gui_data the_inst;
37 static struct gui_data *inst = &the_inst;   /* so we always write `inst->' */
38 static int send_raw_mouse;
39
40 void ldisc_update(int echo, int edit)
41 {
42     /*
43      * This is a stub in pterm. If I ever produce a Unix
44      * command-line ssh/telnet/rlogin client (i.e. a port of plink)
45      * then it will require some termios manoeuvring analogous to
46      * that in the Windows plink.c, but here it's meaningless.
47      */
48 }
49
50 int askappend(char *filename)
51 {
52     /*
53      * FIXME: for the moment we just wipe the log file. Since I
54      * haven't yet enabled logging, this shouldn't matter yet!
55      */
56     return 2;
57 }
58
59 void logevent(char *string)
60 {
61     /*
62      * FIXME: event log entries are currently ignored.
63      */
64 }
65
66 /*
67  * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
68  * into a cooked one (SELECT, EXTEND, PASTE).
69  * 
70  * In Unix, this is not configurable; the X button arrangement is
71  * rock-solid across all applications, everyone has a three-button
72  * mouse or a means of faking it, and there is no need to switch
73  * buttons around at all.
74  */
75 Mouse_Button translate_button(Mouse_Button button)
76 {
77     if (button == MBT_LEFT)
78         return MBT_SELECT;
79     if (button == MBT_MIDDLE)
80         return MBT_PASTE;
81     if (button == MBT_RIGHT)
82         return MBT_EXTEND;
83     return 0;                          /* shouldn't happen */
84 }
85
86 /*
87  * Minimise or restore the window in response to a server-side
88  * request.
89  */
90 void set_iconic(int iconic)
91 {
92     /* FIXME: currently ignored */
93 }
94
95 /*
96  * Move the window in response to a server-side request.
97  */
98 void move_window(int x, int y)
99 {
100     /* FIXME: currently ignored */
101 }
102
103 /*
104  * Move the window to the top or bottom of the z-order in response
105  * to a server-side request.
106  */
107 void set_zorder(int top)
108 {
109     /* FIXME: currently ignored */
110 }
111
112 /*
113  * Refresh the window in response to a server-side request.
114  */
115 void refresh_window(void)
116 {
117     /* FIXME: currently ignored */
118 }
119
120 /*
121  * Maximise or restore the window in response to a server-side
122  * request.
123  */
124 void set_zoomed(int zoomed)
125 {
126     /* FIXME: currently ignored */
127 }
128
129 /*
130  * Report whether the window is iconic, for terminal reports.
131  */
132 int is_iconic(void)
133 {
134     return 0;                          /* FIXME */
135 }
136
137 /*
138  * Report the window's position, for terminal reports.
139  */
140 void get_window_pos(int *x, int *y)
141 {
142     *x = 3; *y = 4;                    /* FIXME */
143 }
144
145 /*
146  * Report the window's pixel size, for terminal reports.
147  */
148 void get_window_pixels(int *x, int *y)
149 {
150     *x = 1; *y = 2;                    /* FIXME */
151 }
152
153 /*
154  * Return the window or icon title.
155  */
156 char *get_window_title(int icon)
157 {
158     return "FIXME: window title retrieval not yet implemented";
159 }
160
161 gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data)
162 {
163     /*
164      * FIXME: warn on close?
165      */
166     return FALSE;
167 }
168
169 gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
170 {
171     struct gui_data *inst = (struct gui_data *)data;
172
173     if (inst->pixmap)
174         gdk_pixmap_unref(inst->pixmap);
175
176     inst->pixmap = gdk_pixmap_new(widget->window, 9*80, 15*24, -1);
177
178     inst->fonts[0] = gdk_font_load("9x15t");   /* XXCONFIG */
179     inst->fonts[1] = NULL;             /* XXCONFIG */
180     inst->black_gc = widget->style->black_gc;
181     inst->white_gc = widget->style->white_gc;
182
183     /*
184      * Set up the colour map.
185      */
186     inst->colmap = gdk_colormap_get_system();
187     {
188         static const int ww[] = {
189             6, 7, 8, 9, 10, 11, 12, 13,
190             14, 15, 16, 17, 18, 19, 20, 21,
191             0, 1, 2, 3, 4, 5
192         };
193         gboolean success[NCOLOURS];
194         int i;
195
196         assert(lenof(ww) == NCOLOURS);
197
198         for (i = 0; i < NCOLOURS; i++) {
199             inst->cols[i].red = cfg.colours[ww[i]][0] * 0x0101;
200             inst->cols[i].green = cfg.colours[ww[i]][1] * 0x0101;
201             inst->cols[i].blue = cfg.colours[ww[i]][2] * 0x0101;
202         }
203
204         gdk_colormap_alloc_colors(inst->colmap, inst->cols, NCOLOURS,
205                                   FALSE, FALSE, success);
206         for (i = 0; i < NCOLOURS; i++) {
207             if (!success[i])
208                 g_error("pterm: couldn't allocate colour %d (#%02x%02x%02x)\n",
209                         i, cfg.colours[i][0], cfg.colours[i][1], cfg.colours[i][2]);
210         }
211     }
212
213     return TRUE;
214 }
215
216 gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
217 {
218     /* struct gui_data *inst = (struct gui_data *)data; */
219
220     /*
221      * Pass the exposed rectangle to terminal.c, which will call us
222      * back to do the actual painting.
223      */
224     term_paint(NULL, 
225                event->area.x / 9, event->area.y / 15,
226                (event->area.x + event->area.width - 1) / 9,
227                (event->area.y + event->area.height - 1) / 15);
228     return TRUE;
229 }
230
231 #define KEY_PRESSED(k) \
232     (inst->keystate[(k) / 32] & (1 << ((k) % 32)))
233
234 gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
235 {
236     /* struct gui_data *inst = (struct gui_data *)data; */
237     char output[32];
238     int start, end;
239
240     if (event->type == GDK_KEY_PRESS) {
241 #ifdef KEY_DEBUGGING
242         {
243             int i;
244             printf("keypress: keyval = %04x, state = %08x; string =",
245                    event->keyval, event->state);
246             for (i = 0; event->string[i]; i++)
247                 printf(" %02x", (unsigned char) event->string[i]);
248             printf("\n");
249         }
250 #endif
251
252         /*
253          * NYI:
254          *  - Shift-PgUp/PgDn for scrollbar
255          *  - nethack mode
256          *  - alt+numpad
257          *  - Compose key (!!! requires Unicode faff before even trying)
258          *  - Shift-Ins for paste (need to deal with pasting first)
259          */
260
261         /* ALT+things gives leading Escape. */
262         output[0] = '\033';
263         strncpy(output+1, event->string, 31);
264         output[31] = '\0';
265         end = strlen(output);
266         if (event->state & GDK_MOD1_MASK)
267             start = 0;
268         else
269             start = 1;
270
271         /* Control-` is the same as Control-\ (unless gtk has a better idea) */
272         if (!event->string[0] && event->keyval == '`' &&
273             (event->state & GDK_CONTROL_MASK)) {
274             output[1] = '\x1C';
275             end = 2;
276         }
277
278         /* Control-Break is the same as Control-C */
279         if (event->keyval == GDK_Break &&
280             (event->state & GDK_CONTROL_MASK)) {
281             output[1] = '\003';
282             end = 2;
283         }
284
285         /* Control-2, Control-Space and Control-@ are NUL */
286         if (!event->string[0] &&
287             (event->keyval == ' ' || event->keyval == '2' ||
288              event->keyval == '@') &&
289             (event->state & (GDK_SHIFT_MASK |
290                              GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {
291             output[1] = '\0';
292             end = 2;
293         }
294
295         /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
296         if (!event->string[0] && event->keyval == ' ' &&
297             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
298             (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
299             output[1] = '\240';
300             end = 2;
301         }
302
303         /* We don't let GTK tell us what Backspace is! We know better. */
304         if (event->keyval == GDK_BackSpace &&
305             !(event->state & GDK_SHIFT_MASK)) {
306             output[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
307             end = 2;
308         }
309
310         /* Shift-Tab is ESC [ Z */
311         if (event->keyval == GDK_ISO_Left_Tab ||
312             (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) {
313             end = 1 + sprintf(output+1, "\033[Z");
314         }
315
316         /*
317          * Application keypad mode.
318          */
319         if (app_keypad_keys && !cfg.no_applic_k) {
320             int xkey = 0;
321             switch (event->keyval) {
322               case GDK_Num_Lock: xkey = 'P'; break;
323               case GDK_KP_Divide: xkey = 'Q'; break;
324               case GDK_KP_Multiply: xkey = 'R'; break;
325               case GDK_KP_Subtract: xkey = 'S'; break;
326                 /*
327                  * Keypad + is tricky. It covers a space that would
328                  * be taken up on the VT100 by _two_ keys; so we
329                  * let Shift select between the two. Worse still,
330                  * in xterm function key mode we change which two...
331                  */
332               case GDK_KP_Add:
333                 if (cfg.funky_type == 2) {
334                     if (event->state & GDK_SHIFT_MASK)
335                         xkey = 'l';
336                     else
337                         xkey = 'k';
338                 } else if (event->state & GDK_SHIFT_MASK)
339                         xkey = 'm';
340                 else
341                     xkey = 'l';
342                 break;
343               case GDK_KP_Enter: xkey = 'M'; break;
344               case GDK_KP_0: case GDK_KP_Insert: xkey = 'p'; break;
345               case GDK_KP_1: case GDK_KP_End: xkey = 'q'; break;
346               case GDK_KP_2: case GDK_KP_Down: xkey = 'r'; break;
347               case GDK_KP_3: case GDK_KP_Page_Down: xkey = 's'; break;
348               case GDK_KP_4: case GDK_KP_Left: xkey = 't'; break;
349               case GDK_KP_5: case GDK_KP_Begin: xkey = 'u'; break;
350               case GDK_KP_6: case GDK_KP_Right: xkey = 'v'; break;
351               case GDK_KP_7: case GDK_KP_Home: xkey = 'w'; break;
352               case GDK_KP_8: case GDK_KP_Up: xkey = 'x'; break;
353               case GDK_KP_9: case GDK_KP_Page_Up: xkey = 'y'; break;
354               case GDK_KP_Decimal: case GDK_KP_Delete: xkey = 'n'; break;
355             }
356             if (xkey) {
357                 if (vt52_mode) {
358                     if (xkey >= 'P' && xkey <= 'S')
359                         end = 1 + sprintf(output+1, "\033%c", xkey);
360                     else
361                         end = 1 + sprintf(output+1, "\033?%c", xkey);
362                 } else
363                     end = 1 + sprintf(output+1, "\033O%c", xkey);
364                 goto done;
365             }
366         }
367
368         /*
369          * Next, all the keys that do tilde codes. (ESC '[' nn '~',
370          * for integer decimal nn.)
371          *
372          * We also deal with the weird ones here. Linux VCs replace F1
373          * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
374          * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
375          * respectively.
376          */
377         {
378             int code = 0;
379             switch (event->keyval) {
380               case GDK_F1:
381                 code = (event->state & GDK_SHIFT_MASK ? 23 : 11);
382                 break;
383               case GDK_F2:
384                 code = (event->state & GDK_SHIFT_MASK ? 24 : 12);
385                 break;
386               case GDK_F3:
387                 code = (event->state & GDK_SHIFT_MASK ? 25 : 13);
388                 break;
389               case GDK_F4:
390                 code = (event->state & GDK_SHIFT_MASK ? 26 : 14);
391                 break;
392               case GDK_F5:
393                 code = (event->state & GDK_SHIFT_MASK ? 28 : 15);
394                 break;
395               case GDK_F6:
396                 code = (event->state & GDK_SHIFT_MASK ? 29 : 17);
397                 break;
398               case GDK_F7:
399                 code = (event->state & GDK_SHIFT_MASK ? 31 : 18);
400                 break;
401               case GDK_F8:
402                 code = (event->state & GDK_SHIFT_MASK ? 32 : 19);
403                 break;
404               case GDK_F9:
405                 code = (event->state & GDK_SHIFT_MASK ? 33 : 20);
406                 break;
407               case GDK_F10:
408                 code = (event->state & GDK_SHIFT_MASK ? 34 : 21);
409                 break;
410               case GDK_F11:
411                 code = 23;
412                 break;
413               case GDK_F12:
414                 code = 24;
415                 break;
416               case GDK_F13:
417                 code = 25;
418                 break;
419               case GDK_F14:
420                 code = 26;
421                 break;
422               case GDK_F15:
423                 code = 28;
424                 break;
425               case GDK_F16:
426                 code = 29;
427                 break;
428               case GDK_F17:
429                 code = 31;
430                 break;
431               case GDK_F18:
432                 code = 32;
433                 break;
434               case GDK_F19:
435                 code = 33;
436                 break;
437               case GDK_F20:
438                 code = 34;
439                 break;
440             }
441             if (!(event->state & GDK_CONTROL_MASK)) switch (event->keyval) {
442               case GDK_Home: case GDK_KP_Home:
443                 code = 1;
444                 break;
445               case GDK_Insert: case GDK_KP_Insert:
446                 code = 2;
447                 break;
448               case GDK_Delete: case GDK_KP_Delete:
449                 code = 3;
450                 break;
451               case GDK_End: case GDK_KP_End:
452                 code = 4;
453                 break;
454               case GDK_Page_Up: case GDK_KP_Page_Up:
455                 code = 5;
456                 break;
457               case GDK_Page_Down: case GDK_KP_Page_Down:
458                 code = 6;
459                 break;
460             }
461             /* Reorder edit keys to physical order */
462             if (cfg.funky_type == 3 && code <= 6)
463                 code = "\0\2\1\4\5\3\6"[code];
464
465             if (vt52_mode && code > 0 && code <= 6) {
466                 end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]);
467                 goto done;
468             }
469
470             if (cfg.funky_type == 5 &&     /* SCO function keys */
471                 code >= 11 && code <= 34) {
472                 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
473                 int index = 0;
474                 switch (event->keyval) {
475                   case GDK_F1: index = 0; break;
476                   case GDK_F2: index = 1; break;
477                   case GDK_F3: index = 2; break;
478                   case GDK_F4: index = 3; break;
479                   case GDK_F5: index = 4; break;
480                   case GDK_F6: index = 5; break;
481                   case GDK_F7: index = 6; break;
482                   case GDK_F8: index = 7; break;
483                   case GDK_F9: index = 8; break;
484                   case GDK_F10: index = 9; break;
485                   case GDK_F11: index = 10; break;
486                   case GDK_F12: index = 11; break;
487                 }
488                 if (event->state & GDK_SHIFT_MASK) index += 12;
489                 if (event->state & GDK_CONTROL_MASK) index += 24;
490                 end = 1 + sprintf(output+1, "\x1B[%c", codes[index]);
491                 goto done;
492             }
493             if (cfg.funky_type == 5 &&     /* SCO small keypad */
494                 code >= 1 && code <= 6) {
495                 char codes[] = "HL.FIG";
496                 if (code == 3) {
497                     output[1] = '\x7F';
498                     end = 2;
499                 } else {
500                     end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]);
501                 }
502                 goto done;
503             }
504             if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
505                 int offt = 0;
506                 if (code > 15)
507                     offt++;
508                 if (code > 21)
509                     offt++;
510                 if (vt52_mode)
511                     end = 1 + sprintf(output+1,
512                                       "\x1B%c", code + 'P' - 11 - offt);
513                 else
514                     end = 1 + sprintf(output+1,
515                                       "\x1BO%c", code + 'P' - 11 - offt);
516                 goto done;
517             }
518             if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
519                 end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11);
520                 goto done;
521             }
522             if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
523                 if (vt52_mode)
524                     end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11);
525                 else
526                     end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11);
527                 goto done;
528             }
529             if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
530                 end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw");
531                 goto done;
532             }
533             if (code) {
534                 end = 1 + sprintf(output+1, "\x1B[%d~", code);
535                 goto done;
536             }
537         }
538
539         /*
540          * Cursor keys. (This includes the numberpad cursor keys,
541          * if we haven't already done them due to app keypad mode.)
542          * 
543          * Here we also process un-numlocked un-appkeypadded KP5,
544          * which sends ESC [ G.
545          */
546         {
547             int xkey = 0;
548             switch (event->keyval) {
549               case GDK_Up: case GDK_KP_Up: xkey = 'A'; break;
550               case GDK_Down: case GDK_KP_Down: xkey = 'B'; break;
551               case GDK_Right: case GDK_KP_Right: xkey = 'C'; break;
552               case GDK_Left: case GDK_KP_Left: xkey = 'D'; break;
553               case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break;
554             }
555             if (xkey) {
556                 /*
557                  * The arrow keys normally do ESC [ A and so on. In
558                  * app cursor keys mode they do ESC O A instead.
559                  * Ctrl toggles the two modes.
560                  */
561                 if (vt52_mode) {
562                     end = 1 + sprintf(output+1, "\033%c", xkey);
563                 } else if (!app_cursor_keys ^
564                            !(event->state & GDK_CONTROL_MASK)) {
565                     end = 1 + sprintf(output+1, "\033O%c", xkey);
566                 } else {                    
567                     end = 1 + sprintf(output+1, "\033[%c", xkey);
568                 }
569                 goto done;
570             }
571         }
572
573         done:
574
575 #ifdef KEY_DEBUGGING
576         {
577             int i;
578             printf("generating sequence:");
579             for (i = start; i < end; i++)
580                 printf(" %02x", (unsigned char) output[i]);
581             printf("\n");
582         }
583 #endif
584         ldisc_send(output+start, end-start, 1);
585         term_out();
586     }
587
588     return TRUE;
589 }
590
591 gint timer_func(gpointer data)
592 {
593     /* struct gui_data *inst = (struct gui_data *)data; */
594
595     term_update();
596     return TRUE;
597 }
598
599 void pty_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
600 {
601     /* struct gui_data *inst = (struct gui_data *)data; */
602     char buf[4096];
603     int ret;
604
605     ret = read(sourcefd, buf, sizeof(buf));
606
607     /*
608      * Clean termination condition is that either ret == 0, or ret
609      * < 0 and errno == EIO. Not sure why the latter, but it seems
610      * to happen. Boo.
611      */
612     if (ret == 0 || (ret < 0 && errno == EIO)) {
613         exit(0);
614     }
615
616     if (ret < 0) {
617         perror("read pty master");
618         exit(1);
619     }
620     if (ret > 0)
621         from_backend(0, buf, ret);
622     term_out();
623 }
624
625 void destroy(GtkWidget *widget, gpointer data)
626 {
627     gtk_main_quit();
628 }
629
630 gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data)
631 {
632     has_focus = event->in;
633     term_out();
634     term_update();
635     return FALSE;
636 }
637
638 /*
639  * set or clear the "raw mouse message" mode
640  */
641 void set_raw_mouse_mode(int activate)
642 {
643     activate = activate && !cfg.no_mouse_rep;
644     send_raw_mouse = activate;
645     if (send_raw_mouse)
646         gdk_window_set_cursor(inst->area->window, inst->rawcursor);
647     else
648         gdk_window_set_cursor(inst->area->window, inst->textcursor);
649 }
650
651 void request_resize(int w, int h)
652 {
653     /* FIXME: currently ignored */
654 }
655
656 void palette_set(int n, int r, int g, int b)
657 {
658     /* FIXME: currently ignored */
659 }
660 void palette_reset(void)
661 {
662     /* FIXME: currently ignored */
663 }
664
665 void write_clip(wchar_t * data, int len, int must_deselect)
666 {
667     /* FIXME: currently ignored */
668 }
669
670 void get_clip(wchar_t ** p, int *len)
671 {
672     if (p) {
673         /* FIXME: currently nonfunctional */
674         *p = NULL;
675         *len = 0;
676     }
677 }
678
679 void set_title(char *title)
680 {
681     /* FIXME: currently ignored */
682 }
683
684 void set_icon(char *title)
685 {
686     /* FIXME: currently ignored */
687 }
688
689 void set_sbar(int total, int start, int page)
690 {
691     /* FIXME: currently ignored */
692 }
693
694 void sys_cursor(int x, int y)
695 {
696     /*
697      * This is meaningless under X.
698      */
699 }
700
701 void beep(int mode)
702 {
703     gdk_beep();
704 }
705
706 int CharWidth(Context ctx, int uc)
707 {
708     /*
709      * Under X, any fixed-width font really _is_ fixed-width.
710      * Double-width characters will be dealt with using a separate
711      * font. For the moment we can simply return 1.
712      */
713     return 1;
714 }
715
716 Context get_ctx(void)
717 {
718     GdkGC *gc;
719     if (!inst->area->window)
720         return NULL;
721     gc = gdk_gc_new(inst->area->window);
722     return gc;
723 }
724
725 void free_ctx(Context ctx)
726 {
727     GdkGC *gc = (GdkGC *)ctx;
728     gdk_gc_unref(gc);
729 }
730
731 /*
732  * Draw a line of text in the window, at given character
733  * coordinates, in given attributes.
734  *
735  * We are allowed to fiddle with the contents of `text'.
736  */
737 void do_text(Context ctx, int x, int y, char *text, int len,
738              unsigned long attr, int lattr)
739 {
740     int nfg, nbg, t;
741     GdkGC *gc = (GdkGC *)ctx;
742
743     /*
744      * NYI:
745      *  - ATTR_WIDE (is this for Unicode CJK? I hope so)
746      *  - LATTR_* (ESC # 4 double-width and double-height stuff)
747      *  - cursor shapes other than block
748      *  - VT100 line drawing stuff; code pages in general!
749      *  - shadow bolding
750      *  - underline
751      */
752
753     nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
754     nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
755     if (attr & ATTR_REVERSE) {
756         t = nfg;
757         nfg = nbg;
758         nbg = t;
759     }
760     if (cfg.bold_colour && (attr & ATTR_BOLD))
761         nfg++;
762     if (cfg.bold_colour && (attr & ATTR_BLINK))
763         nbg++;
764     if (attr & TATTR_ACTCURS) {
765         nfg = NCOLOURS-2;
766         nbg = NCOLOURS-1;
767     }
768
769     gdk_gc_set_foreground(gc, &inst->cols[nbg]);
770     gdk_draw_rectangle(inst->pixmap, gc, 1, x*9, y*15, len*9, 15);
771     
772     gdk_gc_set_foreground(gc, &inst->cols[nfg]);
773     gdk_draw_text(inst->pixmap, inst->fonts[0], gc,
774                   x*9, y*15 + inst->fonts[0]->ascent, text, len);
775
776     if (attr & ATTR_UNDER) {
777         int uheight = inst->fonts[0]->ascent + 1;
778         if (uheight >= 15)
779             uheight = 14;
780         gdk_draw_line(inst->pixmap, gc, x*9, y*15 + uheight,
781                       (x+len)*9-1, y*15+uheight);
782     }
783
784     gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
785                     x*9, y*15, x*9, y*15, len*9, 15);
786 }
787
788 void do_cursor(Context ctx, int x, int y, char *text, int len,
789                unsigned long attr, int lattr)
790 {
791     int passive;
792     GdkGC *gc = (GdkGC *)ctx;
793
794     /*
795      * NYI: cursor shapes other than block
796      */
797     if (attr & TATTR_PASCURS) {
798         attr &= ~TATTR_PASCURS;
799         passive = 1;
800     } else
801         passive = 0;
802     do_text(ctx, x, y, text, len, attr, lattr);
803     if (passive) {
804         gdk_gc_set_foreground(gc, &inst->cols[NCOLOURS-1]);
805         gdk_draw_rectangle(inst->pixmap, gc, 0, x*9, y*15, len*9-1, 15-1);
806         gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
807                         x*9, y*15, x*9, y*15, len*9, 15);
808     }
809 }
810
811 void modalfatalbox(char *p, ...)
812 {
813     va_list ap;
814     fprintf(stderr, "FATAL ERROR: ");
815     va_start(ap, p);
816     vfprintf(stderr, p, ap);
817     va_end(ap);
818     fputc('\n', stderr);
819     exit(1);
820 }
821
822 int main(int argc, char **argv)
823 {
824     GtkWidget *window;
825     extern int pty_master_fd;          /* declared in pty.c */
826
827     gtk_init(&argc, &argv);
828
829     do_defaults(NULL, &cfg);
830
831     init_ucs();
832
833     back = &pty_backend;
834     back->init(NULL, 0, NULL, 0);
835
836     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
837     inst->area = gtk_drawing_area_new();
838     gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area),
839                           9*80, 15*24);/* FIXME: proper resizing stuff */
840
841     gtk_container_add(GTK_CONTAINER(window), inst->area);
842
843     gtk_signal_connect(GTK_OBJECT(window), "destroy",
844                        GTK_SIGNAL_FUNC(destroy), inst);
845     gtk_signal_connect(GTK_OBJECT(window), "delete_event",
846                        GTK_SIGNAL_FUNC(delete_window), inst);
847     gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
848                        GTK_SIGNAL_FUNC(key_event), inst);
849     gtk_signal_connect(GTK_OBJECT(window), "focus_in_event",
850                        GTK_SIGNAL_FUNC(focus_event), inst);
851     gtk_signal_connect(GTK_OBJECT(window), "focus_out_event",
852                        GTK_SIGNAL_FUNC(focus_event), inst);
853     gtk_signal_connect(GTK_OBJECT(inst->area), "configure_event",
854                        GTK_SIGNAL_FUNC(configure_area), inst);
855     gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event",
856                        GTK_SIGNAL_FUNC(expose_area), inst);
857     gtk_timeout_add(20, timer_func, inst);
858     gdk_input_add(pty_master_fd, GDK_INPUT_READ, pty_input_func, inst);
859     gtk_widget_add_events(GTK_WIDGET(inst->area),
860                           GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
861
862     gtk_widget_show(inst->area);
863     gtk_widget_show(window);
864
865     inst->textcursor = gdk_cursor_new(GDK_XTERM);
866     inst->rawcursor = gdk_cursor_new(GDK_ARROW);
867     gdk_window_set_cursor(inst->area->window, inst->textcursor);
868
869     term_init();
870     term_size(24, 80, 2000);
871
872     gtk_main();
873
874     return 0;
875 }