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