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