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