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