]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/pterm.c
77743edf257f8288b9dacd84010ebb38e6d4c451
[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 *window, *area, *sbar;
28     GtkBox *hbox;
29     GtkAdjustment *sbar_adjust;
30     GdkPixmap *pixmap;
31     GdkFont *fonts[2];                 /* normal and bold (for now!) */
32     GdkCursor *rawcursor, *textcursor, *blankcursor, *currcursor;
33     GdkColor cols[NCOLOURS];
34     GdkColormap *colmap;
35     wchar_t *pastein_data;
36     int pastein_data_len;
37     char *pasteout_data;
38     int pasteout_data_len;
39     int font_width, font_height;
40     int ignore_sbar;
41     GdkAtom compound_text_atom;
42     char wintitle[sizeof(((Config *)0)->wintitle)];
43 };
44
45 static struct gui_data the_inst;
46 static struct gui_data *inst = &the_inst;   /* so we always write `inst->' */
47 static int send_raw_mouse;
48
49 void ldisc_update(int echo, int edit)
50 {
51     /*
52      * This is a stub in pterm. If I ever produce a Unix
53      * command-line ssh/telnet/rlogin client (i.e. a port of plink)
54      * then it will require some termios manoeuvring analogous to
55      * that in the Windows plink.c, but here it's meaningless.
56      */
57 }
58
59 int askappend(char *filename)
60 {
61     /*
62      * Logging in an xterm-alike is liable to be something you only
63      * do at serious diagnostic need. Hence, I'm going to take the
64      * easy option for now and assume we always want to overwrite
65      * log files. I can always make it properly configurable later.
66      */
67     return 2;
68 }
69
70 void logevent(char *string)
71 {
72     /*
73      * This is not a very helpful function: events are logged
74      * pretty much exclusively by the back end, and our pty back
75      * end is self-contained. So we need do nothing.
76      */
77 }
78
79 /*
80  * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
81  * into a cooked one (SELECT, EXTEND, PASTE).
82  * 
83  * In Unix, this is not configurable; the X button arrangement is
84  * rock-solid across all applications, everyone has a three-button
85  * mouse or a means of faking it, and there is no need to switch
86  * buttons around at all.
87  */
88 Mouse_Button translate_button(Mouse_Button button)
89 {
90     if (button == MBT_LEFT)
91         return MBT_SELECT;
92     if (button == MBT_MIDDLE)
93         return MBT_PASTE;
94     if (button == MBT_RIGHT)
95         return MBT_EXTEND;
96     return 0;                          /* shouldn't happen */
97 }
98
99 /*
100  * Minimise or restore the window in response to a server-side
101  * request.
102  */
103 void set_iconic(int iconic)
104 {
105     /* FIXME: currently ignored */
106 }
107
108 /*
109  * Move the window in response to a server-side request.
110  */
111 void move_window(int x, int y)
112 {
113     /* FIXME: currently ignored */
114 }
115
116 /*
117  * Move the window to the top or bottom of the z-order in response
118  * to a server-side request.
119  */
120 void set_zorder(int top)
121 {
122     /* FIXME: currently ignored */
123 }
124
125 /*
126  * Refresh the window in response to a server-side request.
127  */
128 void refresh_window(void)
129 {
130     /* FIXME: currently ignored */
131 }
132
133 /*
134  * Maximise or restore the window in response to a server-side
135  * request.
136  */
137 void set_zoomed(int zoomed)
138 {
139     /* FIXME: currently ignored */
140 }
141
142 /*
143  * Report whether the window is iconic, for terminal reports.
144  */
145 int is_iconic(void)
146 {
147     return 0;                          /* FIXME */
148 }
149
150 /*
151  * Report the window's position, for terminal reports.
152  */
153 void get_window_pos(int *x, int *y)
154 {
155     *x = 3; *y = 4;                    /* FIXME */
156 }
157
158 /*
159  * Report the window's pixel size, for terminal reports.
160  */
161 void get_window_pixels(int *x, int *y)
162 {
163     *x = 1; *y = 2;                    /* FIXME */
164 }
165
166 /*
167  * Return the window or icon title.
168  */
169 char *get_window_title(int icon)
170 {
171     return inst->wintitle;
172 }
173
174 gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data)
175 {
176     /*
177      * We could implement warn-on-close here if we really wanted
178      * to.
179      */
180     return FALSE;
181 }
182
183 void show_mouseptr(int show)
184 {
185     if (!cfg.hide_mouseptr)
186         show = 1;
187     if (show)
188         gdk_window_set_cursor(inst->area->window, inst->currcursor);
189     else
190         gdk_window_set_cursor(inst->area->window, inst->blankcursor);
191 }
192
193 gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
194 {
195     struct gui_data *inst = (struct gui_data *)data;
196     int w, h, need_size = 0;
197
198     w = (event->width - 2*cfg.window_border) / inst->font_width;
199     h = (event->height - 2*cfg.window_border) / inst->font_height;
200
201     if (w != cfg.width || h != cfg.height) {
202         if (inst->pixmap) {
203             gdk_pixmap_unref(inst->pixmap);
204             inst->pixmap = NULL;
205         }
206         cfg.width = w;
207         cfg.height = h;
208         need_size = 1;
209     }
210     if (!inst->pixmap) {
211         GdkGC *gc;
212
213         inst->pixmap = gdk_pixmap_new(widget->window,
214                                       (cfg.width * inst->font_width +
215                                        2*cfg.window_border),
216                                       (cfg.height * inst->font_height +
217                                        2*cfg.window_border), -1);
218
219         gc = gdk_gc_new(inst->area->window);
220         gdk_gc_set_foreground(gc, &inst->cols[18]);   /* default background */
221         gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,
222                            cfg.width * inst->font_width + 2*cfg.window_border,
223                            cfg.height * inst->font_height + 2*cfg.window_border);
224         gdk_gc_unref(gc);
225     }
226
227     if (need_size) {
228         term_size(h, w, cfg.savelines);
229     }
230
231     /*
232      * Set up the colour map.
233      */
234     palette_reset();
235
236     return TRUE;
237 }
238
239 gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
240 {
241     /* struct gui_data *inst = (struct gui_data *)data; */
242
243     /*
244      * Pass the exposed rectangle to terminal.c, which will call us
245      * back to do the actual painting.
246      */
247     if (inst->pixmap) {
248         gdk_draw_pixmap(widget->window,
249                         widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
250                         inst->pixmap,
251                         event->area.x, event->area.y,
252                         event->area.x, event->area.y,
253                         event->area.width, event->area.height);
254     }
255     return TRUE;
256 }
257
258 #define KEY_PRESSED(k) \
259     (inst->keystate[(k) / 32] & (1 << ((k) % 32)))
260
261 gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
262 {
263     /* struct gui_data *inst = (struct gui_data *)data; */
264     char output[32];
265     int start, end;
266
267     if (event->type == GDK_KEY_PRESS) {
268 #ifdef KEY_DEBUGGING
269         {
270             int i;
271             printf("keypress: keyval = %04x, state = %08x; string =",
272                    event->keyval, event->state);
273             for (i = 0; event->string[i]; i++)
274                 printf(" %02x", (unsigned char) event->string[i]);
275             printf("\n");
276         }
277 #endif
278
279         /*
280          * NYI:
281          *  - nethack mode
282          *  - alt+numpad
283          *  - Compose key (!!! requires Unicode faff before even trying)
284          */
285
286         /*
287          * Shift-PgUp and Shift-PgDn don't even generate keystrokes
288          * at all.
289          */
290         if (event->keyval == GDK_Page_Up && (event->state & GDK_SHIFT_MASK)) {
291             term_scroll(0, -cfg.height/2);
292             return TRUE;
293         }
294         if (event->keyval == GDK_Page_Down && (event->state & GDK_SHIFT_MASK)) {
295             term_scroll(0, +cfg.height/2);
296             return TRUE;
297         }
298
299         /*
300          * Neither does Shift-Ins.
301          */
302         if (event->keyval == GDK_Insert && (event->state & GDK_SHIFT_MASK)) {
303             request_paste();
304             return TRUE;
305         }
306
307         /* ALT+things gives leading Escape. */
308         output[0] = '\033';
309         strncpy(output+1, event->string, 31);
310         output[31] = '\0';
311         end = strlen(output);
312         if (event->state & GDK_MOD1_MASK)
313             start = 0;
314         else
315             start = 1;
316
317         /* Control-` is the same as Control-\ (unless gtk has a better idea) */
318         if (!event->string[0] && event->keyval == '`' &&
319             (event->state & GDK_CONTROL_MASK)) {
320             output[1] = '\x1C';
321             end = 2;
322         }
323
324         /* Control-Break is the same as Control-C */
325         if (event->keyval == GDK_Break &&
326             (event->state & GDK_CONTROL_MASK)) {
327             output[1] = '\003';
328             end = 2;
329         }
330
331         /* Control-2, Control-Space and Control-@ are NUL */
332         if (!event->string[0] &&
333             (event->keyval == ' ' || event->keyval == '2' ||
334              event->keyval == '@') &&
335             (event->state & (GDK_SHIFT_MASK |
336                              GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {
337             output[1] = '\0';
338             end = 2;
339         }
340
341         /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
342         if (!event->string[0] && event->keyval == ' ' &&
343             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
344             (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
345             output[1] = '\240';
346             end = 2;
347         }
348
349         /* We don't let GTK tell us what Backspace is! We know better. */
350         if (event->keyval == GDK_BackSpace &&
351             !(event->state & GDK_SHIFT_MASK)) {
352             output[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
353             end = 2;
354         }
355
356         /* Shift-Tab is ESC [ Z */
357         if (event->keyval == GDK_ISO_Left_Tab ||
358             (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) {
359             end = 1 + sprintf(output+1, "\033[Z");
360         }
361
362         /*
363          * NetHack keypad mode.
364          */
365         if (cfg.nethack_keypad) {
366             char *keys = NULL;
367             switch (event->keyval) {
368               case GDK_KP_1: case GDK_KP_End: keys = "bB"; break;
369               case GDK_KP_2: case GDK_KP_Down: keys = "jJ"; break;
370               case GDK_KP_3: case GDK_KP_Page_Down: keys = "nN"; break;
371               case GDK_KP_4: case GDK_KP_Left: keys = "hH"; break;
372               case GDK_KP_5: case GDK_KP_Begin: keys = ".."; break;
373               case GDK_KP_6: case GDK_KP_Right: keys = "lL"; break;
374               case GDK_KP_7: case GDK_KP_Home: keys = "yY"; break;
375               case GDK_KP_8: case GDK_KP_Up: keys = "kK"; break;
376               case GDK_KP_9: case GDK_KP_Page_Up: keys = "uU"; break;
377             }
378             if (keys) {
379                 end = 2;
380                 if (event->state & GDK_SHIFT_MASK)
381                     output[1] = keys[1];
382                 else
383                     output[1] = keys[0];
384                 goto done;
385             }
386         }
387
388         /*
389          * Application keypad mode.
390          */
391         if (app_keypad_keys && !cfg.no_applic_k) {
392             int xkey = 0;
393             switch (event->keyval) {
394               case GDK_Num_Lock: xkey = 'P'; break;
395               case GDK_KP_Divide: xkey = 'Q'; break;
396               case GDK_KP_Multiply: xkey = 'R'; break;
397               case GDK_KP_Subtract: xkey = 'S'; break;
398                 /*
399                  * Keypad + is tricky. It covers a space that would
400                  * be taken up on the VT100 by _two_ keys; so we
401                  * let Shift select between the two. Worse still,
402                  * in xterm function key mode we change which two...
403                  */
404               case GDK_KP_Add:
405                 if (cfg.funky_type == 2) {
406                     if (event->state & GDK_SHIFT_MASK)
407                         xkey = 'l';
408                     else
409                         xkey = 'k';
410                 } else if (event->state & GDK_SHIFT_MASK)
411                         xkey = 'm';
412                 else
413                     xkey = 'l';
414                 break;
415               case GDK_KP_Enter: xkey = 'M'; break;
416               case GDK_KP_0: case GDK_KP_Insert: xkey = 'p'; break;
417               case GDK_KP_1: case GDK_KP_End: xkey = 'q'; break;
418               case GDK_KP_2: case GDK_KP_Down: xkey = 'r'; break;
419               case GDK_KP_3: case GDK_KP_Page_Down: xkey = 's'; break;
420               case GDK_KP_4: case GDK_KP_Left: xkey = 't'; break;
421               case GDK_KP_5: case GDK_KP_Begin: xkey = 'u'; break;
422               case GDK_KP_6: case GDK_KP_Right: xkey = 'v'; break;
423               case GDK_KP_7: case GDK_KP_Home: xkey = 'w'; break;
424               case GDK_KP_8: case GDK_KP_Up: xkey = 'x'; break;
425               case GDK_KP_9: case GDK_KP_Page_Up: xkey = 'y'; break;
426               case GDK_KP_Decimal: case GDK_KP_Delete: xkey = 'n'; break;
427             }
428             if (xkey) {
429                 if (vt52_mode) {
430                     if (xkey >= 'P' && xkey <= 'S')
431                         end = 1 + sprintf(output+1, "\033%c", xkey);
432                     else
433                         end = 1 + sprintf(output+1, "\033?%c", xkey);
434                 } else
435                     end = 1 + sprintf(output+1, "\033O%c", xkey);
436                 goto done;
437             }
438         }
439
440         /*
441          * Next, all the keys that do tilde codes. (ESC '[' nn '~',
442          * for integer decimal nn.)
443          *
444          * We also deal with the weird ones here. Linux VCs replace F1
445          * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
446          * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
447          * respectively.
448          */
449         {
450             int code = 0;
451             switch (event->keyval) {
452               case GDK_F1:
453                 code = (event->state & GDK_SHIFT_MASK ? 23 : 11);
454                 break;
455               case GDK_F2:
456                 code = (event->state & GDK_SHIFT_MASK ? 24 : 12);
457                 break;
458               case GDK_F3:
459                 code = (event->state & GDK_SHIFT_MASK ? 25 : 13);
460                 break;
461               case GDK_F4:
462                 code = (event->state & GDK_SHIFT_MASK ? 26 : 14);
463                 break;
464               case GDK_F5:
465                 code = (event->state & GDK_SHIFT_MASK ? 28 : 15);
466                 break;
467               case GDK_F6:
468                 code = (event->state & GDK_SHIFT_MASK ? 29 : 17);
469                 break;
470               case GDK_F7:
471                 code = (event->state & GDK_SHIFT_MASK ? 31 : 18);
472                 break;
473               case GDK_F8:
474                 code = (event->state & GDK_SHIFT_MASK ? 32 : 19);
475                 break;
476               case GDK_F9:
477                 code = (event->state & GDK_SHIFT_MASK ? 33 : 20);
478                 break;
479               case GDK_F10:
480                 code = (event->state & GDK_SHIFT_MASK ? 34 : 21);
481                 break;
482               case GDK_F11:
483                 code = 23;
484                 break;
485               case GDK_F12:
486                 code = 24;
487                 break;
488               case GDK_F13:
489                 code = 25;
490                 break;
491               case GDK_F14:
492                 code = 26;
493                 break;
494               case GDK_F15:
495                 code = 28;
496                 break;
497               case GDK_F16:
498                 code = 29;
499                 break;
500               case GDK_F17:
501                 code = 31;
502                 break;
503               case GDK_F18:
504                 code = 32;
505                 break;
506               case GDK_F19:
507                 code = 33;
508                 break;
509               case GDK_F20:
510                 code = 34;
511                 break;
512             }
513             if (!(event->state & GDK_CONTROL_MASK)) switch (event->keyval) {
514               case GDK_Home: case GDK_KP_Home:
515                 code = 1;
516                 break;
517               case GDK_Insert: case GDK_KP_Insert:
518                 code = 2;
519                 break;
520               case GDK_Delete: case GDK_KP_Delete:
521                 code = 3;
522                 break;
523               case GDK_End: case GDK_KP_End:
524                 code = 4;
525                 break;
526               case GDK_Page_Up: case GDK_KP_Page_Up:
527                 code = 5;
528                 break;
529               case GDK_Page_Down: case GDK_KP_Page_Down:
530                 code = 6;
531                 break;
532             }
533             /* Reorder edit keys to physical order */
534             if (cfg.funky_type == 3 && code <= 6)
535                 code = "\0\2\1\4\5\3\6"[code];
536
537             if (vt52_mode && code > 0 && code <= 6) {
538                 end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]);
539                 goto done;
540             }
541
542             if (cfg.funky_type == 5 &&     /* SCO function keys */
543                 code >= 11 && code <= 34) {
544                 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
545                 int index = 0;
546                 switch (event->keyval) {
547                   case GDK_F1: index = 0; break;
548                   case GDK_F2: index = 1; break;
549                   case GDK_F3: index = 2; break;
550                   case GDK_F4: index = 3; break;
551                   case GDK_F5: index = 4; break;
552                   case GDK_F6: index = 5; break;
553                   case GDK_F7: index = 6; break;
554                   case GDK_F8: index = 7; break;
555                   case GDK_F9: index = 8; break;
556                   case GDK_F10: index = 9; break;
557                   case GDK_F11: index = 10; break;
558                   case GDK_F12: index = 11; break;
559                 }
560                 if (event->state & GDK_SHIFT_MASK) index += 12;
561                 if (event->state & GDK_CONTROL_MASK) index += 24;
562                 end = 1 + sprintf(output+1, "\x1B[%c", codes[index]);
563                 goto done;
564             }
565             if (cfg.funky_type == 5 &&     /* SCO small keypad */
566                 code >= 1 && code <= 6) {
567                 char codes[] = "HL.FIG";
568                 if (code == 3) {
569                     output[1] = '\x7F';
570                     end = 2;
571                 } else {
572                     end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]);
573                 }
574                 goto done;
575             }
576             if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
577                 int offt = 0;
578                 if (code > 15)
579                     offt++;
580                 if (code > 21)
581                     offt++;
582                 if (vt52_mode)
583                     end = 1 + sprintf(output+1,
584                                       "\x1B%c", code + 'P' - 11 - offt);
585                 else
586                     end = 1 + sprintf(output+1,
587                                       "\x1BO%c", code + 'P' - 11 - offt);
588                 goto done;
589             }
590             if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
591                 end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11);
592                 goto done;
593             }
594             if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
595                 if (vt52_mode)
596                     end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11);
597                 else
598                     end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11);
599                 goto done;
600             }
601             if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
602                 end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw");
603                 goto done;
604             }
605             if (code) {
606                 end = 1 + sprintf(output+1, "\x1B[%d~", code);
607                 goto done;
608             }
609         }
610
611         /*
612          * Cursor keys. (This includes the numberpad cursor keys,
613          * if we haven't already done them due to app keypad mode.)
614          * 
615          * Here we also process un-numlocked un-appkeypadded KP5,
616          * which sends ESC [ G.
617          */
618         {
619             int xkey = 0;
620             switch (event->keyval) {
621               case GDK_Up: case GDK_KP_Up: xkey = 'A'; break;
622               case GDK_Down: case GDK_KP_Down: xkey = 'B'; break;
623               case GDK_Right: case GDK_KP_Right: xkey = 'C'; break;
624               case GDK_Left: case GDK_KP_Left: xkey = 'D'; break;
625               case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break;
626             }
627             if (xkey) {
628                 /*
629                  * The arrow keys normally do ESC [ A and so on. In
630                  * app cursor keys mode they do ESC O A instead.
631                  * Ctrl toggles the two modes.
632                  */
633                 if (vt52_mode) {
634                     end = 1 + sprintf(output+1, "\033%c", xkey);
635                 } else if (!app_cursor_keys ^
636                            !(event->state & GDK_CONTROL_MASK)) {
637                     end = 1 + sprintf(output+1, "\033O%c", xkey);
638                 } else {                    
639                     end = 1 + sprintf(output+1, "\033[%c", xkey);
640                 }
641                 goto done;
642             }
643         }
644
645         done:
646
647 #ifdef KEY_DEBUGGING
648         {
649             int i;
650             printf("generating sequence:");
651             for (i = start; i < end; i++)
652                 printf(" %02x", (unsigned char) output[i]);
653             printf("\n");
654         }
655 #endif
656         if (end-start > 0) {
657             ldisc_send(output+start, end-start, 1);
658             show_mouseptr(0);
659             term_out();
660         }
661     }
662
663     return TRUE;
664 }
665
666 gint button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
667 {
668     struct gui_data *inst = (struct gui_data *)data;
669     int shift, ctrl, alt, x, y, button, act;
670
671     show_mouseptr(1);
672
673     shift = event->state & GDK_SHIFT_MASK;
674     ctrl = event->state & GDK_CONTROL_MASK;
675     alt = event->state & GDK_MOD1_MASK;
676     if (event->button == 1)
677         button = MBT_LEFT;
678     else if (event->button == 2)
679         button = MBT_MIDDLE;
680     else if (event->button == 3)
681         button = MBT_RIGHT;
682     else
683         return FALSE;                  /* don't even know what button! */
684
685     switch (event->type) {
686       case GDK_BUTTON_PRESS: act = MA_CLICK; break;
687       case GDK_BUTTON_RELEASE: act = MA_RELEASE; break;
688       case GDK_2BUTTON_PRESS: act = MA_2CLK; break;
689       case GDK_3BUTTON_PRESS: act = MA_3CLK; break;
690       default: return FALSE;           /* don't know this event type */
691     }
692
693     if (send_raw_mouse && !(cfg.mouse_override && shift) &&
694         act != MA_CLICK && act != MA_RELEASE)
695         return TRUE;                   /* we ignore these in raw mouse mode */
696
697     x = (event->x - cfg.window_border) / inst->font_width;
698     y = (event->y - cfg.window_border) / inst->font_height;
699
700     term_mouse(button, act, x, y, shift, ctrl, alt);
701
702     return TRUE;
703 }
704
705 gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
706 {
707     struct gui_data *inst = (struct gui_data *)data;
708     int shift, ctrl, alt, x, y, button;
709
710     show_mouseptr(1);
711
712     shift = event->state & GDK_SHIFT_MASK;
713     ctrl = event->state & GDK_CONTROL_MASK;
714     alt = event->state & GDK_MOD1_MASK;
715     if (event->state & GDK_BUTTON1_MASK)
716         button = MBT_LEFT;
717     else if (event->state & GDK_BUTTON2_MASK)
718         button = MBT_MIDDLE;
719     else if (event->state & GDK_BUTTON3_MASK)
720         button = MBT_RIGHT;
721     else
722         return FALSE;                  /* don't even know what button! */
723
724     x = (event->x - cfg.window_border) / inst->font_width;
725     y = (event->y - cfg.window_border) / inst->font_height;
726
727     term_mouse(button, MA_DRAG, x, y, shift, ctrl, alt);
728
729     return TRUE;
730 }
731
732 gint timer_func(gpointer data)
733 {
734     /* struct gui_data *inst = (struct gui_data *)data; */
735     extern int pty_child_is_dead();  /* declared in pty.c */
736
737     if (pty_child_is_dead()) {
738         /*
739          * The primary child process died. We could keep the
740          * terminal open for remaining subprocesses to output to,
741          * but conventional wisdom seems to feel that that's the
742          * Wrong Thing for an xterm-alike, so we bail out now. This
743          * would be easy enough to change or make configurable if
744          * necessary.
745          */
746         exit(0);
747     }
748
749     term_update();
750     return TRUE;
751 }
752
753 gint idle_func(gpointer data)
754 {
755     /* struct gui_data *inst = (struct gui_data *)data; */
756     term_paste();
757     return TRUE;
758 }
759
760 void pty_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
761 {
762     /* struct gui_data *inst = (struct gui_data *)data; */
763     char buf[4096];
764     int ret;
765
766     ret = read(sourcefd, buf, sizeof(buf));
767
768     /*
769      * Clean termination condition is that either ret == 0, or ret
770      * < 0 and errno == EIO. Not sure why the latter, but it seems
771      * to happen. Boo.
772      */
773     if (ret == 0 || (ret < 0 && errno == EIO)) {
774         exit(0);
775     }
776
777     if (ret < 0) {
778         perror("read pty master");
779         exit(1);
780     }
781     if (ret > 0)
782         from_backend(0, buf, ret);
783     term_out();
784 }
785
786 void destroy(GtkWidget *widget, gpointer data)
787 {
788     gtk_main_quit();
789 }
790
791 gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data)
792 {
793     has_focus = event->in;
794     term_out();
795     term_update();
796     show_mouseptr(1);
797     return FALSE;
798 }
799
800 /*
801  * set or clear the "raw mouse message" mode
802  */
803 void set_raw_mouse_mode(int activate)
804 {
805     activate = activate && !cfg.no_mouse_rep;
806     send_raw_mouse = activate;
807     if (send_raw_mouse)
808         inst->currcursor = inst->rawcursor;
809     else
810         inst->currcursor = inst->textcursor;
811     show_mouseptr(1);
812 }
813
814 void request_resize(int w, int h)
815 {
816     /* FIXME: currently ignored */
817 }
818
819 void real_palette_set(int n, int r, int g, int b)
820 {
821     gboolean success[1];
822
823     inst->cols[n].red = r * 0x0101;
824     inst->cols[n].green = g * 0x0101;
825     inst->cols[n].blue = b * 0x0101;
826
827     gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1,
828                               FALSE, FALSE, success);
829     if (!success[0])
830         g_error("pterm: couldn't allocate colour %d (#%02x%02x%02x)\n",
831                 n, r, g, b);
832 }
833
834 void palette_set(int n, int r, int g, int b)
835 {
836     static const int first[21] = {
837         0, 2, 4, 6, 8, 10, 12, 14,
838         1, 3, 5, 7, 9, 11, 13, 15,
839         16, 17, 18, 20, 22
840     };
841     real_palette_set(first[n], r, g, b);
842     if (first[n] >= 18)
843         real_palette_set(first[n] + 1, r, g, b);
844 }
845
846 void palette_reset(void)
847 {
848     /* This maps colour indices in cfg to those used in inst->cols. */
849     static const int ww[] = {
850         6, 7, 8, 9, 10, 11, 12, 13,
851         14, 15, 16, 17, 18, 19, 20, 21,
852         0, 1, 2, 3, 4, 5
853     };
854     gboolean success[NCOLOURS];
855     int i;
856
857     assert(lenof(ww) == NCOLOURS);
858
859     if (!inst->colmap) {
860         inst->colmap = gdk_colormap_get_system();
861     } else {
862         gdk_colormap_free_colors(inst->colmap, inst->cols, NCOLOURS);
863     }
864
865     for (i = 0; i < NCOLOURS; i++) {
866         inst->cols[i].red = cfg.colours[ww[i]][0] * 0x0101;
867         inst->cols[i].green = cfg.colours[ww[i]][1] * 0x0101;
868         inst->cols[i].blue = cfg.colours[ww[i]][2] * 0x0101;
869     }
870
871     gdk_colormap_alloc_colors(inst->colmap, inst->cols, NCOLOURS,
872                               FALSE, FALSE, success);
873     for (i = 0; i < NCOLOURS; i++) {
874         if (!success[i])
875             g_error("pterm: couldn't allocate colour %d (#%02x%02x%02x)\n",
876                     i, cfg.colours[i][0], cfg.colours[i][1], cfg.colours[i][2]);
877     }
878 }
879
880 void write_clip(wchar_t * data, int len, int must_deselect)
881 {
882     if (inst->pasteout_data)
883         sfree(inst->pasteout_data);
884     inst->pasteout_data = smalloc(len);
885     inst->pasteout_data_len = len;
886     wc_to_mb(0, 0, data, len, inst->pasteout_data, inst->pasteout_data_len,
887              NULL, NULL);
888
889     if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY,
890                                 GDK_CURRENT_TIME)) {
891         gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
892                                  GDK_SELECTION_TYPE_STRING, 1);
893         gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
894                                  inst->compound_text_atom, 1);
895     }
896 }
897
898 void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
899                    guint info, guint time_stamp, gpointer data)
900 {
901     gtk_selection_data_set(seldata, GDK_SELECTION_TYPE_STRING, 8,
902                            inst->pasteout_data, inst->pasteout_data_len);
903 }
904
905 gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
906                      gpointer data)
907 {
908     term_deselect();
909     if (inst->pasteout_data)
910         sfree(inst->pasteout_data);
911     inst->pasteout_data = NULL;
912     inst->pasteout_data_len = 0;
913     return TRUE;
914 }
915
916 void request_paste(void)
917 {
918     /*
919      * In Unix, pasting is asynchronous: all we can do at the
920      * moment is to call gtk_selection_convert(), and when the data
921      * comes back _then_ we can call term_do_paste().
922      */
923     gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
924                           GDK_SELECTION_TYPE_STRING, GDK_CURRENT_TIME);
925 }
926
927 void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
928                         gpointer data)
929 {
930     if (seldata->length <= 0 ||
931         seldata->type != GDK_SELECTION_TYPE_STRING)
932         return;                        /* Nothing happens. */
933
934     if (inst->pastein_data)
935         sfree(inst->pastein_data);
936
937     inst->pastein_data = smalloc(seldata->length * sizeof(wchar_t));
938     inst->pastein_data_len = seldata->length;
939     mb_to_wc(0, 0, seldata->data, seldata->length,
940              inst->pastein_data, inst->pastein_data_len);
941
942     term_do_paste();
943 }
944
945 void get_clip(wchar_t ** p, int *len)
946 {
947     if (p) {
948         *p = inst->pastein_data;
949         *len = inst->pastein_data_len;
950     }
951 }
952
953 void set_title(char *title)
954 {
955     strncpy(inst->wintitle, title, lenof(inst->wintitle));
956     inst->wintitle[lenof(inst->wintitle)-1] = '\0';
957     gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle);
958 }
959
960 void set_icon(char *title)
961 {
962     /* FIXME: currently ignored */
963 }
964
965 void set_sbar(int total, int start, int page)
966 {
967     inst->sbar_adjust->lower = 0;
968     inst->sbar_adjust->upper = total;
969     inst->sbar_adjust->value = start;
970     inst->sbar_adjust->page_size = page;
971     inst->sbar_adjust->step_increment = 1;
972     inst->sbar_adjust->page_increment = page/2;
973     inst->ignore_sbar = TRUE;
974     gtk_adjustment_changed(inst->sbar_adjust);
975     inst->ignore_sbar = FALSE;
976 }
977
978 void scrollbar_moved(GtkAdjustment *adj, gpointer data)
979 {
980     if (!inst->ignore_sbar)
981         term_scroll(1, (int)adj->value);
982 }
983
984 void sys_cursor(int x, int y)
985 {
986     /*
987      * This is meaningless under X.
988      */
989 }
990
991 void beep(int mode)
992 {
993     gdk_beep();
994 }
995
996 int CharWidth(Context ctx, int uc)
997 {
998     /*
999      * Under X, any fixed-width font really _is_ fixed-width.
1000      * Double-width characters will be dealt with using a separate
1001      * font. For the moment we can simply return 1.
1002      */
1003     return 1;
1004 }
1005
1006 Context get_ctx(void)
1007 {
1008     GdkGC *gc;
1009     if (!inst->area->window)
1010         return NULL;
1011     gc = gdk_gc_new(inst->area->window);
1012     return gc;
1013 }
1014
1015 void free_ctx(Context ctx)
1016 {
1017     GdkGC *gc = (GdkGC *)ctx;
1018     gdk_gc_unref(gc);
1019 }
1020
1021 /*
1022  * Draw a line of text in the window, at given character
1023  * coordinates, in given attributes.
1024  *
1025  * We are allowed to fiddle with the contents of `text'.
1026  */
1027 void do_text(Context ctx, int x, int y, char *text, int len,
1028              unsigned long attr, int lattr)
1029 {
1030     int nfg, nbg, t;
1031     GdkGC *gc = (GdkGC *)ctx;
1032
1033     /*
1034      * NYI:
1035      *  - Unicode, code pages, and ATTR_WIDE for CJK support.
1036      *  - LATTR_* (ESC # 4 double-width and double-height stuff)
1037      *  - cursor shapes other than block
1038      *  - shadow bolding
1039      */
1040
1041     nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
1042     nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
1043     if (attr & ATTR_REVERSE) {
1044         t = nfg;
1045         nfg = nbg;
1046         nbg = t;
1047     }
1048     if (cfg.bold_colour && (attr & ATTR_BOLD))
1049         nfg++;
1050     if (cfg.bold_colour && (attr & ATTR_BLINK))
1051         nbg++;
1052     if (attr & TATTR_ACTCURS) {
1053         nfg = NCOLOURS-2;
1054         nbg = NCOLOURS-1;
1055     }
1056
1057     gdk_gc_set_foreground(gc, &inst->cols[nbg]);
1058     gdk_draw_rectangle(inst->pixmap, gc, 1,
1059                        x*inst->font_width+cfg.window_border,
1060                        y*inst->font_height+cfg.window_border,
1061                        len*inst->font_width, inst->font_height);
1062
1063     gdk_gc_set_foreground(gc, &inst->cols[nfg]);
1064     gdk_draw_text(inst->pixmap, inst->fonts[0], gc,
1065                   x*inst->font_width+cfg.window_border,
1066                   y*inst->font_height+cfg.window_border+inst->fonts[0]->ascent,
1067                   text, len);
1068
1069     if (attr & ATTR_UNDER) {
1070         int uheight = inst->fonts[0]->ascent + 1;
1071         if (uheight >= inst->font_height)
1072             uheight = inst->font_height - 1;
1073         gdk_draw_line(inst->pixmap, gc, x*inst->font_width+cfg.window_border,
1074                       y*inst->font_height + uheight + cfg.window_border,
1075                       (x+len)*inst->font_width-1+cfg.window_border,
1076                       y*inst->font_height + uheight + cfg.window_border);
1077     }
1078
1079     gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
1080                     x*inst->font_width+cfg.window_border,
1081                     y*inst->font_height+cfg.window_border,
1082                     x*inst->font_width+cfg.window_border,
1083                     y*inst->font_height+cfg.window_border,
1084                     len*inst->font_width, inst->font_height);
1085 }
1086
1087 void do_cursor(Context ctx, int x, int y, char *text, int len,
1088                unsigned long attr, int lattr)
1089 {
1090     int passive;
1091     GdkGC *gc = (GdkGC *)ctx;
1092
1093     /*
1094      * NYI: cursor shapes other than block
1095      */
1096     if (attr & TATTR_PASCURS) {
1097         attr &= ~TATTR_PASCURS;
1098         passive = 1;
1099     } else
1100         passive = 0;
1101     do_text(ctx, x, y, text, len, attr, lattr);
1102     if (passive) {
1103         gdk_gc_set_foreground(gc, &inst->cols[NCOLOURS-1]);
1104         gdk_draw_rectangle(inst->pixmap, gc, 0,
1105                            x*inst->font_width+cfg.window_border,
1106                            y*inst->font_height+cfg.window_border,
1107                            len*inst->font_width-1, inst->font_height-1);
1108         gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
1109                         x*inst->font_width+cfg.window_border,
1110                         y*inst->font_height+cfg.window_border,
1111                         x*inst->font_width+cfg.window_border,
1112                         y*inst->font_height+cfg.window_border,
1113                         len*inst->font_width, inst->font_height);
1114     }
1115 }
1116
1117 GdkCursor *make_mouse_ptr(int cursor_val)
1118 {
1119     /*
1120      * Truly hideous hack: GTK doesn't allow us to set the mouse
1121      * cursor foreground and background colours unless we've _also_
1122      * created our own cursor from bitmaps. Therefore, I need to
1123      * load the `cursor' font and draw glyphs from it on to
1124      * pixmaps, in order to construct my cursors with the fg and bg
1125      * I want. This is a gross hack, but it's more self-contained
1126      * than linking in Xlib to find the X window handle to
1127      * inst->area and calling XRecolorCursor, and it's more
1128      * futureproof than hard-coding the shapes as bitmap arrays.
1129      */
1130     static GdkFont *cursor_font = NULL;
1131     GdkPixmap *source, *mask;
1132     GdkGC *gc;
1133     GdkColor cfg = { 0, 65535, 65535, 65535 };
1134     GdkColor cbg = { 0, 0, 0, 0 };
1135     GdkColor dfg = { 1, 65535, 65535, 65535 };
1136     GdkColor dbg = { 0, 0, 0, 0 };
1137     GdkCursor *ret;
1138     gchar text[2];
1139     gint lb, rb, wid, asc, desc, w, h, x, y;
1140
1141     if (cursor_val == -2) {
1142         gdk_font_unref(cursor_font);
1143         return NULL;
1144     }
1145
1146     if (cursor_val >= 0 && !cursor_font)
1147         cursor_font = gdk_font_load("cursor");
1148
1149     /*
1150      * Get the text extent of the cursor in question. We use the
1151      * mask character for this, because it's typically slightly
1152      * bigger than the main character.
1153      */
1154     if (cursor_val >= 0) {
1155         text[1] = '\0';
1156         text[0] = (char)cursor_val + 1;
1157         gdk_string_extents(cursor_font, text, &lb, &rb, &wid, &asc, &desc);
1158         w = rb-lb; h = asc+desc; x = -lb; y = asc;
1159     } else {
1160         w = h = 1;
1161         x = y = 0;
1162     }
1163
1164     source = gdk_pixmap_new(NULL, w, h, 1);
1165     mask = gdk_pixmap_new(NULL, w, h, 1);
1166
1167     /*
1168      * Draw the mask character on the mask pixmap.
1169      */
1170     gc = gdk_gc_new(mask);
1171     gdk_gc_set_foreground(gc, &dbg);
1172     gdk_draw_rectangle(mask, gc, 1, 0, 0, w, h);
1173     if (cursor_val >= 0) {
1174         text[1] = '\0';
1175         text[0] = (char)cursor_val + 1;
1176         gdk_gc_set_foreground(gc, &dfg);
1177         gdk_draw_text(mask, cursor_font, gc, x, y, text, 1);
1178     }
1179     gdk_gc_unref(gc);
1180
1181     /*
1182      * Draw the main character on the source pixmap.
1183      */
1184     gc = gdk_gc_new(source);
1185     gdk_gc_set_foreground(gc, &dbg);
1186     gdk_draw_rectangle(source, gc, 1, 0, 0, w, h);
1187     if (cursor_val >= 0) {
1188         text[1] = '\0';
1189         text[0] = (char)cursor_val;
1190         gdk_gc_set_foreground(gc, &dfg);
1191         gdk_draw_text(source, cursor_font, gc, x, y, text, 1);
1192     }
1193     gdk_gc_unref(gc);
1194
1195     /*
1196      * Create the cursor.
1197      */
1198     ret = gdk_cursor_new_from_pixmap(source, mask, &cfg, &cbg, x, y);
1199
1200     /*
1201      * Clean up.
1202      */
1203     gdk_pixmap_unref(source);
1204     gdk_pixmap_unref(mask);
1205
1206     return ret;
1207 }
1208
1209 void modalfatalbox(char *p, ...)
1210 {
1211     va_list ap;
1212     fprintf(stderr, "FATAL ERROR: ");
1213     va_start(ap, p);
1214     vfprintf(stderr, p, ap);
1215     va_end(ap);
1216     fputc('\n', stderr);
1217     exit(1);
1218 }
1219
1220 int main(int argc, char **argv)
1221 {
1222     extern int pty_master_fd;          /* declared in pty.c */
1223     extern char **pty_argv;            /* declared in pty.c */
1224     int err = 0;
1225
1226     gtk_init(&argc, &argv);
1227
1228     do_defaults(NULL, &cfg);
1229
1230     while (--argc > 0) {
1231         char *p = *++argv;
1232         if (!strcmp(p, "-fn")) {
1233             if (--argc > 0) {
1234                 strncpy(cfg.font, *++argv, sizeof(cfg.font));
1235                 cfg.font[sizeof(cfg.font)-1] = '\0';
1236             } else
1237                 err = 1, fprintf(stderr, "pterm: -fn expects an argument\n");
1238         }
1239         if (!strcmp(p, "-e")) {
1240             if (--argc > 0) {
1241                 int i;
1242                 pty_argv = smalloc((argc+1) * sizeof(char *));
1243                 ++argv;
1244                 for (i = 0; i < argc; i++)
1245                     pty_argv[i] = argv[i];
1246                 pty_argv[argc] = NULL;
1247                 break;                 /* finished command-line processing */
1248             } else
1249                 err = 1, fprintf(stderr, "pterm: -e expects an argument\n");
1250         }
1251         if (!strcmp(p, "-T")) {
1252             if (--argc > 0) {
1253                 strncpy(cfg.wintitle, *++argv, sizeof(cfg.wintitle));
1254                 cfg.wintitle[sizeof(cfg.wintitle)-1] = '\0';
1255             } else
1256                 err = 1, fprintf(stderr, "pterm: -T expects an argument\n");
1257         }
1258         if (!strcmp(p, "-log")) {
1259             if (--argc > 0) {
1260                 strncpy(cfg.logfilename, *++argv, sizeof(cfg.logfilename));
1261                 cfg.logfilename[sizeof(cfg.logfilename)-1] = '\0';
1262                 cfg.logtype = LGTYP_DEBUG;
1263             } else
1264                 err = 1, fprintf(stderr, "pterm: -log expects an argument\n");
1265         }
1266         if (!strcmp(p, "-hide")) {
1267             cfg.hide_mouseptr = 1;
1268         }
1269         if (!strcmp(p, "-nethack")) {
1270             cfg.nethack_keypad = 1;
1271         }
1272     }
1273
1274     inst->fonts[0] = gdk_font_load(cfg.font);
1275     inst->fonts[1] = NULL;             /* FIXME: what about bold font? */
1276     inst->font_width = gdk_char_width(inst->fonts[0], ' ');
1277     inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent;
1278
1279     inst->compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
1280
1281     init_ucs();
1282
1283     back = &pty_backend;
1284     back->init(NULL, 0, NULL, 0);
1285
1286     inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1287
1288     if (cfg.wintitle[0])
1289         set_title(cfg.wintitle);
1290     else
1291         set_title("pterm");
1292
1293     inst->area = gtk_drawing_area_new();
1294     gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area),
1295                           inst->font_width * cfg.width + 2*cfg.window_border,
1296                           inst->font_height * cfg.height + 2*cfg.window_border);
1297     inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0));
1298     inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
1299     inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
1300     gtk_box_pack_start(inst->hbox, inst->area, TRUE, TRUE, 0);
1301     gtk_box_pack_end(inst->hbox, inst->sbar, FALSE, FALSE, 0);
1302
1303     gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
1304
1305     {
1306         GdkGeometry geom;
1307         geom.min_width = inst->font_width + 2*cfg.window_border;
1308         geom.min_height = inst->font_height + 2*cfg.window_border;
1309         geom.max_width = geom.max_height = -1;
1310         geom.base_width = 2*cfg.window_border;
1311         geom.base_height = 2*cfg.window_border;
1312         geom.width_inc = inst->font_width;
1313         geom.height_inc = inst->font_height;
1314         geom.min_aspect = geom.max_aspect = 0;
1315         gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom,
1316                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
1317                                       GDK_HINT_RESIZE_INC);
1318     }
1319
1320     gtk_signal_connect(GTK_OBJECT(inst->window), "destroy",
1321                        GTK_SIGNAL_FUNC(destroy), inst);
1322     gtk_signal_connect(GTK_OBJECT(inst->window), "delete_event",
1323                        GTK_SIGNAL_FUNC(delete_window), inst);
1324     gtk_signal_connect(GTK_OBJECT(inst->window), "key_press_event",
1325                        GTK_SIGNAL_FUNC(key_event), inst);
1326     gtk_signal_connect(GTK_OBJECT(inst->window), "focus_in_event",
1327                        GTK_SIGNAL_FUNC(focus_event), inst);
1328     gtk_signal_connect(GTK_OBJECT(inst->window), "focus_out_event",
1329                        GTK_SIGNAL_FUNC(focus_event), inst);
1330     gtk_signal_connect(GTK_OBJECT(inst->area), "configure_event",
1331                        GTK_SIGNAL_FUNC(configure_area), inst);
1332     gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event",
1333                        GTK_SIGNAL_FUNC(expose_area), inst);
1334     gtk_signal_connect(GTK_OBJECT(inst->area), "button_press_event",
1335                        GTK_SIGNAL_FUNC(button_event), inst);
1336     gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event",
1337                        GTK_SIGNAL_FUNC(button_event), inst);
1338     gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event",
1339                        GTK_SIGNAL_FUNC(motion_event), inst);
1340     gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received",
1341                        GTK_SIGNAL_FUNC(selection_received), inst);
1342     gtk_signal_connect(GTK_OBJECT(inst->area), "selection_get",
1343                        GTK_SIGNAL_FUNC(selection_get), inst);
1344     gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event",
1345                        GTK_SIGNAL_FUNC(selection_clear), inst);
1346     gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed",
1347                        GTK_SIGNAL_FUNC(scrollbar_moved), inst);
1348     gtk_idle_add(idle_func, inst);
1349     gtk_timeout_add(20, timer_func, inst);
1350     gdk_input_add(pty_master_fd, GDK_INPUT_READ, pty_input_func, inst);
1351     gtk_widget_add_events(GTK_WIDGET(inst->area),
1352                           GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
1353                           GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1354                           GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK);
1355
1356     gtk_widget_show(inst->area);
1357     gtk_widget_show(inst->sbar);
1358     gtk_widget_show(GTK_WIDGET(inst->hbox));
1359     gtk_widget_show(inst->window);
1360
1361     inst->textcursor = make_mouse_ptr(GDK_XTERM);
1362     inst->rawcursor = make_mouse_ptr(GDK_LEFT_PTR);
1363     inst->blankcursor = make_mouse_ptr(-1);
1364     make_mouse_ptr(-2);                /* clean up cursor font */
1365     inst->currcursor = inst->textcursor;
1366     show_mouseptr(1);
1367
1368     term_init();
1369     term_size(24, 80, 2000);
1370
1371     gtk_main();
1372
1373     return 0;
1374 }