1 /*
2 X11 window driver
3 */
4
5 #include "wio.h"
6
7 #include "config.h"
8 #include "console.h"
9 #include "head.h"
10 #include "os.h"
11 #include "shared.h"
12 #include "status.h"
13
14 #include "X11/Intrinsic.h"
15 #include "X11/StringDefs.h"
16 #define XK_MISCELLANY /* We want ALL the keysyms */
17 #include "X11/keysymdef.h"
18
19 #define MAX_FONTS 4
20
21 #include "wio_util.c"
22
23 typedef struct
24 {
25 Pixel fore;
26 Pixel back;
27 Pixel palette[8];
28 Bool inverted;
29 Bool justified;
30 Font font[MAX_FONTS];
31 char *wide;
32 char *high;
33 char *border;
34 } AppData, *AppDataPtr;
35
36 static AppData appdata;
37
38 static XtResource resources[] =
39 {
40 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
41 XtOffset(AppDataPtr, fore), XtRString, "Black"},
42 {XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel),
43 XtOffset(AppDataPtr, back), XtRString, "White"},
44
45 {XtNjustify, XtCJustify, XtRBool, sizeof(Bool),
46 XtOffset(AppDataPtr, justified), XtRString, "true"},
47 {XtNreverseVideo, XtCReverseVideo, XtRBool, sizeof(Bool),
48 XtOffset(AppDataPtr, inverted), XtRString, "false"},
49 {"normal", "Normal", XtRFont, sizeof(Font),
50 XtOffset(AppDataPtr, font[0]), XtRString, XtDefaultFont},
51 {"bold", "Bold", XtRFont, sizeof(Font),
52 XtOffset(AppDataPtr, font[1]), XtRString, XtDefaultFont},
53 {"italic", "Italic", XtRFont, sizeof(Font),
54 XtOffset(AppDataPtr, font[2]), XtRString, XtDefaultFont},
55 {"mono", "Mono", XtRFont, sizeof(Font),
56 XtOffset(AppDataPtr, font[3]), XtRString, XtDefaultFont},
57 {"wide", "Wide", XtRString, sizeof(char *),
58 XtOffset(AppDataPtr, wide), XtRString, "80"},
59 {"high", "High", XtRString, sizeof(char *),
60 XtOffset(AppDataPtr, high), XtRString, "32"},
61 {"border", "Border", XtRString, sizeof(char *),
62 XtOffset(AppDataPtr, border), XtRString, "8"}
63 };
64
65 typedef struct
66 {
67 int wide, high, above, below;
68 Font handle;
69 XFontStruct *fs;
70 } font_data;
71
72 typedef struct
73 {
74 int x, y;
75 } point;
76
77 typedef struct
78 {
79 point min, max;
80 } box;
81
82 #define CLASSNAME "Infocom"
83
84 static Widget toplevel, window_widget;
85 static GC gc;
86 static Pixmap pixmap;
87
88 static font_data font_inf[MAX_FONTS];
89 static point wind;
90 static int font_above, font_below, font_high;
91
92 static int border_size;
93
94 static int show_caret = 0, have_caret = 0;
95 static int have_seen_exposed = 0;
96
97 #define TEXT_SIZE 32
98
99 static int text_held = 0;
100 static char text_chars[TEXT_SIZE];
101
102 #define font_wide (font_inf[3].wide)
103
104 #define x_display XtDisplay(toplevel)
105 #define x_window XtWindow(window_widget)
106
107 static bool colours_valid;
108 static Pixel pixel_colour[8];
109
alloc_colours(void)110 static void alloc_colours(void)
111 {
112 int i;
113 int x_screen = DefaultScreen(x_display);
114 Colormap cmap = DefaultColormap(x_display, x_screen);
115 colours_valid = DefaultDepth(x_display, x_screen) > 3;
116 for(i = 0; i < 8 && colours_valid; ++i)
117 {
118 XColor color;
119 color.red = (i & 1) ? 65535 : 0;
120 color.green = (i & 2) ? 65535 : 0;
121 color.blue = (i & 4) ? 65535 : 0;
122 colours_valid = colours_valid && XAllocColor(x_display, cmap, &color);
123 pixel_colour[i] = color.pixel;
124 }
125 if(!colours_valid)
126 hd_no_colour();
127 }
128
event_poll(void)129 static void event_poll(void)
130 {
131 XEvent event;
132 XtNextEvent(&event);
133 XtDispatchEvent(&event);
134 }
135
load_fonts(void)136 static void load_fonts(void)
137 {
138 int i;
139
140 /* Most of the work has been done by the resource loader */
141 for(i = 0; i < MAX_FONTS; ++i)
142 font_inf[i].handle = appdata.font[i];
143 }
144
calc_fonts(void)145 static void calc_fonts(void)
146 {
147 int i;
148 font_above = 0;
149 font_below = 0;
150 for(i = 0; i < MAX_FONTS; ++i)
151 {
152 XFontStruct *fs = XQueryFont(x_display, font_inf[i].handle);
153 font_inf[i].wide = fs->max_bounds.width;
154 font_inf[i].above = fs->max_bounds.ascent;
155 font_inf[i].below = fs->max_bounds.descent;
156 if(font_inf[i].above > font_above)
157 font_above = font_inf[i].above;
158 if(font_inf[i].below > font_below)
159 font_below = font_inf[i].below;
160 font_inf[i].fs = fs;
161 }
162 font_high = (font_above + font_below);
163 }
164
font_for(unsigned n)165 static int font_for(unsigned n)
166 {
167 static int f[] =
168 {
169 0, /* FONT_NORMAL */
170 0, /* FONT_REVS */
171 1, /* FONT_BOLD */
172 2, /* FONT_EMPH */
173 3, /* FONT_FIXED */
174 3, /* FONT_FIXED_REVS */
175 };
176 return f[n-1];
177 }
178
width_text(char * t,int n,console_attr * attr)179 static int width_text(char *t, int n, console_attr *attr)
180 {
181 return XTextWidth(font_inf[font_for(attr->font)].fs, t, n);
182 }
183
184 #define pixel_for(c,d) (colours_valid && 2 <= (c) && (c) <= 9 ? pixel_colour[(c)-2] : (d))
185
print_text(int x,char * t,int n,console_attr * attr)186 static int print_text(int x, char *t, int n, console_attr *attr)
187 {
188 int w = width_text(t, n, attr);
189 int r = is_reverse(attr->font);
190 Pixel back = pixel_for(attr->back, appdata.back);
191 Pixel fore = pixel_for(attr->fore, appdata.fore);
192 if(r)
193 {
194 Pixel t = fore; fore = back; back = t;
195 }
196 XSetForeground(x_display, gc, back);
197 XFillRectangle(x_display, pixmap, gc, x, 0, w, font_high);
198 XSetForeground(x_display, gc, fore);
199 XSetBackground(x_display, gc, back);
200 XSetFont(x_display, gc, font_inf[font_for(attr->font)].handle);
201 XDrawString(x_display, pixmap, gc, x, font_above, t, n);
202 return x + w;
203 }
204
wipe_area(int x,int w,console_attr * attr)205 static int wipe_area(int x, int w, console_attr *attr)
206 {
207 Pixel back = pixel_for(attr->back, appdata.back);
208 XSetForeground(x_display, gc, back);
209 XFillRectangle(x_display, pixmap, gc, x, 0, w, font_high);
210 return x + w;
211 }
212
width_line(console_cell * slack,char * text,int held)213 static int width_line(console_cell *slack, char *text, int held)
214 {
215 int x, p, z = 0;
216 for(x = p = 0; x < held; x = z)
217 {
218 z = after(slack, x, held);
219 p += width_text(&text[x], z - x, &slack[x].attr);
220 }
221 return p;
222 }
223
repaint_fixed(int y)224 static void repaint_fixed(int y)
225 {
226 int i, p;
227 for(i = p = 0; i < con.shape.fixed; ++i, p += font_wide)
228 {
229 char c = con.row[y].fixed[i].text;
230 if(c)
231 (void) print_text(p, &c, 1, &con.row[y].fixed[i].attr);
232 }
233 }
234
repaint_just(int y,int held)235 static int repaint_just(int y, int held)
236 {
237 int x, z = 0;
238 int c = 0;
239 int p = 0;
240 int s = wind.x;
241 char *text = text_of(y);
242 for(x = 0; x < held; x = z)
243 {
244 z = after_space(con.row[y].slack, x, held);
245 s -= width_text(&text[x], z - x, &con.row[y].slack[x].attr);
246 while(z < held && is_a_space(con.row[y].slack[z]))
247 {
248 ++z;
249 ++c;
250 }
251 }
252 for(x = 0; x < held; x = z)
253 {
254 z = after_space(con.row[y].slack, x, held);
255 p = print_text(p, &text[x], z - x, &con.row[y].slack[x].attr);
256 while(z < held && is_a_space(con.row[y].slack[z]))
257 {
258 int d = s / c;
259 p = wipe_area(p, d, &con.row[y].slack[z].attr);
260 s -= d;
261 c -= 1;
262 ++z;
263 }
264 }
265 return p;
266 }
267
repaint_left(int y,int held)268 static int repaint_left(int y, int held)
269 {
270 int x, z = 0, p = 0;
271 char *text = text_of(y);
272 if(y == con.cursor.y && !con.cursor.align)
273 {
274 int c = width_line(con.row[y].slack, text, min(held, con.cursor.x));
275 if(wind.x < c)
276 p = wind.x - c;
277 }
278 for(x = 0; x < held; x = z)
279 {
280 z = after(con.row[y].slack, x, held);
281 p = print_text(p, &text[x], z - x, &con.row[y].slack[x].attr);
282 }
283 return p;
284 }
285
repaint_slack(int y)286 static void repaint_slack(int y)
287 {
288 int held = find_eol(y);
289 int p = held == 0 ? 0
290 : con.row[y].just && appdata.justified ? repaint_just(y, held)
291 : repaint_left(y, held);
292 if(p < wind.x)
293 {
294 console_attr a;
295 if(held)
296 a = con.row[y].slack[held-1].attr;
297 else
298 a = con.cursor.attr;
299 a.font = 1;
300 wipe_area(p, wind.x - p, &a);
301 }
302 }
303
repaint_cursor(void)304 static void repaint_cursor(void)
305 {
306 int p;
307 int wide = font_wide / 3;
308 int high = font_high;
309 if(con.cursor.align)
310 {
311 p = con.cursor.x * font_wide;
312 }
313 else
314 {
315 char *text = text_of(con.cursor.y);
316 int c = width_line(con.row[con.cursor.y].slack, text, con.cursor.x);
317 p = min(wind.x, c);
318 }
319 XSetForeground(x_display, gc, appdata.fore);
320 if(have_caret)
321 {
322 XSetFunction(x_display, gc, GXinvert);
323 XFillRectangle(x_display, pixmap, gc, p, 0, wide, high);
324 }
325 else
326 {
327 XDrawRectangle(x_display, pixmap, gc, p, 0, wide - 1, high - 1);
328 }
329 XSetFunction(x_display, gc, GXcopy);
330 }
331
repaint(int y)332 void repaint(int y)
333 {
334 if(have_seen_exposed && 0 <= y && y < con.shape.height)
335 {
336 repaint_slack(y);
337 repaint_fixed(y);
338 con.row[y].flag = 0;
339 if(show_caret > 0 && y == con.cursor.y)
340 repaint_cursor();
341 XCopyArea(x_display, pixmap, x_window, gc, 0, 0, wind.x,
342 font_high, border_size, y * font_high + border_size);
343 con.row[y].flag = 0;
344 }
345 }
346
repaint_border(void)347 static void repaint_border(void)
348 {
349 if(border_size)
350 {
351 int h = wind.y + 2 * border_size;
352 int w = wind.x + 2 * border_size;
353 XSetForeground(x_display, gc, appdata.back);
354 XFillRectangle(x_display, x_window, gc, 0, 0, w, border_size);
355 XFillRectangle(x_display, x_window, gc, 0,
356 wind.y + border_size, w, border_size);
357 XFillRectangle(x_display, x_window, gc, 0, 0, border_size, h);
358 XFillRectangle(x_display, x_window, gc, wind.x + border_size,
359 0, border_size, h);
360 }
361 }
362
keys(Widget w,XEvent * e,String * s,Cardinal * n)363 static void keys(Widget w, XEvent * e, String * s, Cardinal * n)
364 {
365 char buff[TEXT_SIZE];
366 KeySym keysym;
367 XComposeStatus compose;
368 char buff_has = XLookupString(&e->xkey, buff, sizeof(buff),
369 &keysym, &compose);
370 switch(keysym)
371 {
372 case XK_BackSpace:
373 buff[0] = 'H' - '@';
374 buff_has = 1;
375 break;
376 case XK_Left:
377 buff[0] = 'B' - '@';
378 buff_has = 1;
379 break;
380 case XK_Right:
381 buff[0] = 'F' - '@';
382 buff_has = 1;
383 break;
384 case XK_Down:
385 buff[0] = 'N' - '@';
386 buff_has = 1;
387 break;
388 case XK_Up:
389 buff[0] = 'P' - '@';
390 buff_has = 1;
391 break;
392 case XK_Begin:
393 buff[0] = 'A' - '@';
394 buff_has = 1;
395 break;
396 case XK_End:
397 buff[0] = 'E' - '@';
398 buff_has = 1;
399 break;
400 case XK_Delete:
401 buff[0] = 'D' - '@';
402 buff_has = 1;
403 break;
404 }
405 if(buff_has && text_held + buff_has <= TEXT_SIZE)
406 {
407 os_mcpy(&text_chars[text_held], buff, buff_has * sizeof(buff[0]));
408 text_held += buff_has;
409 }
410 }
411
focus(Widget w,XtPointer data,XEvent * e,Boolean * proceed)412 static void focus(Widget w, XtPointer data, XEvent * e, Boolean * proceed)
413 {
414 static int have_focus = 0;
415 /* Black magic: see O'Reilly, Vol 1, p313 */
416 switch(e->type)
417 {
418 case FocusIn:
419 have_focus = have_caret = 1;
420 break;
421 case FocusOut:
422 have_focus = have_caret = 0;
423 break;
424 case EnterNotify:
425 have_caret = e->xcrossing.focus;
426 break;
427 case LeaveNotify:
428 have_caret = have_focus;
429 break;
430 }
431 redraw_cursor();
432 *proceed = TRUE;
433 }
434
435
redraw_all(void)436 static void redraw_all(void)
437 {
438 int i = con.shape.height;
439 repaint_border();
440 while(--i >= 0)
441 repaint(i);
442 }
443
redraw(Widget w,XtPointer data,XEvent * e,Boolean * proceed)444 static void redraw(Widget w, XtPointer data, XEvent * e, Boolean * proceed)
445 {
446 int top = (e->xexpose.y - border_size) / font_high;
447 int bot = (e->xexpose.y - border_size + e->xexpose.height + font_high - 1) / font_high;
448 int i = bot;
449 have_seen_exposed = 1;
450 while(--i >= top)
451 repaint(i);
452 *proceed = TRUE;
453 }
454
455 /* Action handlers */
456
toggle_justify(Widget w,XEvent * e,String * s,Cardinal * n)457 static void toggle_justify(Widget w, XEvent * e, String * s, Cardinal * n)
458 {
459 int i = con.shape.height;
460 appdata.justified = !appdata.justified;
461 while(--i >= 0)
462 if(con.row[i].just)
463 repaint(i);
464 }
465
toggle_reverse(Widget w,XEvent * e,String * s,Cardinal * n)466 static void toggle_reverse(Widget w, XEvent * e, String * s, Cardinal * n)
467 {
468 Pixel t = appdata.fore;
469 appdata.fore = appdata.back;
470 appdata.back = t;
471 redraw_all();
472 }
473
474 static XtActionsRec actions[] =
475 {
476 {"justify", toggle_justify},
477 {"reverse", toggle_reverse},
478 {"keys", keys}
479 };
480
481 /* Initialisation */
482
pre_init_io(int * argc,char ** argv)483 void pre_init_io(int *argc, char **argv)
484 {
485 toplevel = XtInitialize(argv[0], CLASSNAME, (XrmOptionDescRec *) 0,
486 0, argc, argv);
487 }
488
init_io(void)489 void init_io(void)
490 {
491 Arg args[2];
492 int screen_width, screen_height;
493
494 XtGetApplicationResources(toplevel, &appdata, resources,
495 XtNumber(resources), NULL, 0);
496
497 if(appdata.inverted)
498 {
499 Pixel temp = appdata.fore;
500 appdata.fore = appdata.back;
501 appdata.back = temp;
502 }
503
504 screen_width = atoi(appdata.wide);
505 if(screen_width <= 0)
506 screen_width = 80;
507 screen_height = atoi(appdata.high);
508 if(screen_height <= 0)
509 screen_height = 32;
510 border_size = atoi(appdata.border);
511 if(border_size < 0)
512 border_size = 8;
513
514 load_fonts();
515 calc_fonts();
516
517 wind.x = screen_width * font_wide;
518 wind.y = font_high * screen_height;
519
520 args[0].name = XtNwidth;
521 args[0].value = (Dimension) wind.x + 2 * border_size;
522 args[1].name = XtNheight;
523 args[1].value = (Dimension) wind.y + 2 * border_size;
524 window_widget = XtCreateManagedWidget("Infocom", widgetClass,
525 toplevel, args, XtNumber(args));
526
527 alloc_colours();
528
529 init_console(screen_height, screen_width);
530
531 gc = XCreateGC(x_display, DefaultRootWindow(x_display), 0L, NULL);
532
533 XtAddEventHandler(window_widget, ExposureMask, FALSE,
534 redraw, (XtPointer) 0);
535 XtAddEventHandler(toplevel,
536 EnterWindowMask | LeaveWindowMask | FocusChangeMask, FALSE,
537 focus, (XtPointer) 0);
538
539 XtAddActions(actions, XtNumber(actions));
540 XtAugmentTranslations(window_widget,
541 XtParseTranslationTable("<Key>: keys()"));
542 XtAugmentTranslations(toplevel,
543 XtParseTranslationTable("<Key>: keys()"));
544
545 XtSetMappedWhenManaged(toplevel, False);
546 XtRealizeWidget(toplevel);
547 {
548 XSizeHints *sh = XAllocSizeHints();
549 XWMHints *wmh = XAllocWMHints();
550 if(sh != NULL)
551 {
552 sh->flags = PMaxSize | PMinSize;
553 sh->min_width = sh->max_width = wind.x + 2 * border_size;
554 sh->min_height = sh->max_height = wind.y + 2 * border_size;
555 }
556 if(wmh != NULL)
557 {
558 wmh->flags = StateHint | InputHint;
559 wmh->initial_state = NormalState;
560 wmh->input = True;
561 }
562 XSetWMProperties(x_display, XtWindow(toplevel), NULL,
563 NULL, NULL, 0, sh, wmh, NULL);
564 }
565 XtMapWidget(toplevel);
566
567 pixmap = XCreatePixmap(x_display, x_window, wind.x, font_high,
568 DefaultDepthOfScreen(XtScreen(toplevel)));
569
570 display("\nInfocom interpreter for X11\n" VERSION "\n\n");
571 }
572
exit_io(void)573 void exit_io(void)
574 {
575 display("\nInfocom has terminated - press any key to quit");
576 get_ch();
577 }
578
579 static char ungot;
580
unget_ch(char c)581 void unget_ch(char c)
582 {
583 ungot = c;
584 }
585
get_ch(void)586 char get_ch(void)
587 {
588 char c;
589
590 show_cursor();
591 force_update();
592 while(text_held == 0)
593 event_poll();
594 hide_cursor();
595 c = text_chars[0];
596 if(--text_held)
597 os_mcpy(&text_chars[0], &text_chars[1], text_held);
598 return c;
599 }
600
char_width(char c)601 int char_width(char c)
602 {
603 char buff[4];
604 buff[0] = c;
605 return XTextWidth(font_inf[font_for(con.cursor.attr.font)].fs, buff, 1);
606 }
607
display_width(void)608 int display_width(void)
609 {
610 return wind.x;
611 }
612
scrollup(void)613 void scrollup(void)
614 {
615 int top = border_size + font_high * (con.top + 1);
616 int bot = border_size + font_high * con.bottom;
617 XCopyArea(x_display, x_window, x_window, gc, border_size,
618 top, wind.x, bot - top, border_size, top - font_high);
619 }
620
redraw_cursor(void)621 void redraw_cursor(void)
622 {
623 touch(con.cursor.y);
624 }
625
hide_cursor(void)626 void hide_cursor(void)
627 {
628 --show_caret;
629 touch(con.cursor.y);
630 }
631
show_cursor(void)632 void show_cursor(void)
633 {
634 ++show_caret;
635 touch(con.cursor.y);
636 }
637
638