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