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