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