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