eh, make ’em volatile, this is not time-critical code anyway
[alioth/magicpoint.git] / mgp.c
1 /*
2  * Copyright (C) 1997 and 1998 WIDE Project.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  * 3. Neither the name of the project nor the names of its contributors
13  *    may be used to endorse or promote products derived from this software
14  *    without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28
29 #include "mgp.h"
30 #if HAVE_LOCALE_H
31 #include <locale.h>
32 #endif
33 #include <fcntl.h>
34 #ifdef TTY_KEYINPUT
35 #include <termios.h>
36 #endif
37
38 Window plwin[MAXPAGE];
39 Pixmap maskpix;
40 XFontStruct *plfs;
41 XFontStruct *plkfs;
42
43 u_int pg_mode;
44 u_long pl_fh, pl_fw;
45 time_t t_start;
46 u_int t_fin;
47 u_int tbar_mode;
48 int zoomin = 0;
49
50 static int rakugaki = 0;
51 static int rakugaki_x = -1;
52 static int rakugaki_y = -1;
53 static int rakugaki_color = 0;
54 static XColor rakugaki_fore;
55 static XColor rakugaki_back;
56 static const char *rakugaki_forecolors[] = {
57         "red", "green", "blue", "yellow", "black", "white",
58 };
59 static const char *rakugaki_backcolors[] = {
60         "red", "green", "blue", "yellow", "black", "white",
61 };
62 static int demointerval = 0;    /* XXX define option for this */
63
64 u_long depth_mask;
65
66 const char *back_clname = DEFAULT_BACK;
67
68 int latin_unicode_map[3][256]; /* currently we have iso8859-2-4 map */
69
70 static char *tsfile = NULL;
71 static char *dumpdir = NULL;
72
73 const char *gsdevice = DEFAULT_GSDEV;
74 char *htmlimage;
75
76 static time_t srctimestamp;
77
78 static char *xgeometry = NULL;  /*default: full screen*/
79 #ifdef TTY_KEYINPUT
80 static struct termios saveattr, curattr;
81 volatile int ttykey_enable;
82 #endif
83 static pid_t mypid;
84
85 static void genhtml(unsigned int);
86 static void mgp_usage(char *) __dead;
87 static void mgp_show_version(char *) __dead;
88 static void beep(void);
89 static void main_loop(u_int);
90 static void rakugaki_update(struct render_state *, XEvent *);
91 static void rakugaki_updatecolor(Cursor);
92 static void waitkids(int);
93 static int wantreload(void);
94
95 #ifdef TTY_KEYINPUT
96 static void
97 susp(int sig)
98 {
99         sigset_t mask;
100
101         if (ttykey_enable) {
102                 if (sig == SIGTTIN || sig == SIGTTOU) {
103                         ttykey_enable = 0;
104                         return;
105                 }
106                 tcsetattr(0, TCSANOW, &saveattr);
107         }
108         signal(sig, SIG_DFL);
109         sigemptyset(&mask);
110         sigaddset(&mask, sig);
111         sigprocmask(SIG_UNBLOCK, &mask, NULL);
112         kill(mypid, sig);
113         /* resumed */
114         signal(sig, susp);
115         if (ttykey_enable) {
116                 if (tcgetpgrp(0) != mypid)
117                         ttykey_enable = 0;
118                 else
119                         tcsetattr(0, TCSANOW, &curattr);
120         }
121 }
122
123 void
124 try_enable_ttykey(void)
125 {
126
127         if (ttykey_enable)
128                 return;
129         if (tcgetpgrp(0) != mypid)
130                 return;
131
132         ttykey_enable = 1;
133
134         signal(SIGTTIN, susp);
135         signal(SIGTTOU, susp);
136         signal(SIGTSTP, susp);
137         curattr = saveattr;
138         curattr.c_lflag |= ISIG;
139         curattr.c_lflag &= ~(ECHO|ICANON|IEXTEN);
140         curattr.c_iflag &= ~(IXON|IXOFF);
141         curattr.c_cc[VMIN] = 1;
142         curattr.c_cc[VTIME] = 0;
143         /* This call might cause STGTTOU */
144         tcsetattr(0, TCSANOW, &curattr);
145 }
146 #endif
147
148 void
149 cleanup(int sig)
150 {
151         sigset_t mask;
152
153 #ifdef TTY_KEYINPUT
154         if (ttykey_enable)
155                 tcsetattr(0, TCSANOW, &saveattr);
156 #endif
157         signal(SIGTERM, SIG_IGN);
158         kill(0, SIGTERM);       /*kill all of my kids*/
159         if (tsfile)
160                 unlink(tsfile);
161         if (sig > 0) {
162                 signal(sig, SIG_DFL);
163                 sigemptyset(&mask);
164                 sigaddset(&mask, sig);
165                 sigprocmask(SIG_UNBLOCK, &mask, NULL);
166                 kill(mypid, sig);
167         }
168
169         finish_win(); /* finish X connection */
170         exit(-sig);
171 }
172
173 int
174 main(int argc, char *argv[])
175 {
176         int i, j;              /* counters */
177         int tmp_argc;          /* number of current arguments */
178         char **tmp_argv;       /* manipulated 'argv' */
179         int opt;
180         char *progname;
181         unsigned int start_page = 1;
182         char buf[BUFSIZ], *p, *p2;
183
184 #if HAVE_SETLOCALE_CTYPE
185         setlocale(LC_CTYPE, "");
186 #endif
187         progname = argv[0];
188
189         /* secure by default.  If you need fork/exec, use -U */
190         mgp_flag |= FL_NOFORK;
191
192         /*
193          * check for the argument '--title' to set the window's title:
194          * go through the whole 'argv' and cut off this option since it
195          * will not be accepted by getopt()
196          * default title is 'MagicPoint'
197          */
198         tmp_argv=(char**)malloc(argc*sizeof(char*));
199         tmp_argc=argc;
200         i=j=0;
201         while (i < argc)
202         {
203                 if (strcmp(argv[i], "--title") == 0)
204                 {
205                         tmp_argc--;
206                         if (++i < argc)
207                         {
208                                 mgp_wname=strdup(argv[i]);
209                                 tmp_argc--;
210                         }
211                 }
212                 else
213                         tmp_argv[j++]=strdup(argv[i]);
214                 i++;
215         }
216         /* set title to default if not set by user */
217         if (argc-1 <= tmp_argc)
218                 mgp_wname=strdup("MagicPoint");
219
220         argv=tmp_argv;
221         argc=tmp_argc;
222
223         while ((opt = getopt(argc, argv, "Bb:CD:d:E:eF:Gg:hnOoPp:Q:qRST:t:UVvX:x:")) != -1) {
224                 switch (opt) {
225                 case 'B':
226                         mgp_flag |= FL_BIMAGE;
227                         break;
228
229                 case 'b':
230                         back_clname = optarg;
231                         break;
232
233                 case 'C':
234                         mgp_flag |= FL_PRIVATE;
235                         break;
236
237                 case 'D':
238                         dumpdir = optarg;
239                         break;
240
241                 case 'd':
242                         mgp_flag |= FL_DEMO;
243                         if (isdigit(optarg[0])) demointerval = atoi(optarg);
244                         else optind --;
245                         break;
246
247                 case 'E':
248                         htmlimage = optarg;
249                         break;
250
251                 case 'e':
252                         mgp_flag |= FL_GLYPHEDGE;
253                         break;
254
255                 case 'F':
256                         mgp_flag |= FL_FRDCACHE;
257                         p = optarg;
258                         if ((p2 = strsep(&p, ",")))
259                                 cache_mode = atoi(p2);
260                         if ((p2 = strsep(&p, ",")))
261                                 cache_effect = atoi(p2);
262                         if ((p2 = strsep(&p, ",")))
263                                 cache_value = atoi(p2);
264                         break;
265
266                 case 'G':
267                         pg_mode = 1;
268                         break;
269
270                 case 'g':
271                         xgeometry = optarg;
272                         mgp_flag |= FL_OVER;    /* -g implies -o */
273                         break;
274
275                 case 'n':
276                         mgp_flag |= FL_NOSTDIN;
277                         break;
278
279                 case 'O':
280                         mgp_flag |= FL_NODECORATION | FL_OVER;  /* -O implies -o */
281                         break;
282
283                 case 'o':
284                         mgp_flag |= FL_OVER;
285                         break;
286
287                 case 'P':
288                         parse_debug++;
289                         break;
290
291                 case 'p':
292                         start_page = atoi(optarg);
293                         if (start_page <= 0) {
294                                 mgp_usage(progname);
295                                 /*NOTREACHED*/
296                         }
297                         break;
298
299                 case 'Q':
300                         b_quality[caching] = atoi(optarg);
301                         quality_flag = 1;
302                         break;
303
304                 case 'q':
305                         mgp_flag |= FL_NOBEEP;
306                         break;
307
308                 case 'R':
309                         mgp_flag |= FL_NOAUTORELOAD;
310                         break;
311
312                 case 'S':
313                         mgp_flag |= FL_NOFORK;
314                         break;
315
316                 case 'T':
317                         tsfile = optarg;
318                         break;
319
320                 case 't':
321                         t_fin = atoi(optarg);
322                         tbar_mode = 1;
323                         break;
324
325                 case 'U':
326                         mgp_flag &= ~FL_NOFORK;
327                         break;
328
329                 case 'V':
330                         mgp_show_version(progname);
331                         /*NOTREACHED*/
332
333                 case 'v':
334                         mgp_flag |= FL_VERBOSE;
335                         verbose++;
336                         break;
337
338                 case 'X':
339                         gsdevice = optarg;
340                         break;
341
342                 case 'x':
343                         if (strcmp(optarg, "xft") == 0)
344                                 mgp_flag |= FL_NOXFT;
345                         else {
346                                 fprintf(stderr, "unknown rendering engine %s\n",
347                                         optarg);
348                                 mgp_usage(progname);
349                                 /*NOTREACHED*/
350                         }
351                         break;
352
353                 case 'h':
354                 default:
355                         mgp_usage(progname);
356                         /*NOTREACHED*/
357                 }
358         }
359
360         argc -= optind;
361         argv += optind;
362
363         mypid = getpid();
364         setpgid(mypid, mypid);
365         signal(SIGHUP, cleanup);
366         signal(SIGINT, cleanup);
367         signal(SIGQUIT, cleanup);
368         signal(SIGTERM, cleanup);
369
370         if (argc != 1) {
371                 mgp_usage(progname);
372                 /*NOTREACHED*/
373         }
374         mgp_fname = argv[0];
375     {
376         struct stat sb;
377         srctimestamp = (time_t) 0;
378         if (0 <= stat(mgp_fname, &sb))
379                 srctimestamp = sb.st_ctime;
380     }
381
382         init_win1(xgeometry);
383         strlcpy(buf, mgp_fname, sizeof(buf));
384         if ((p = strrchr(buf, '/'))) {
385                 *p = '\0';
386                 Paths[NumPaths++]= expandPath(buf);
387         }
388         loadPathsAndExts();
389         load_file(mgp_fname);
390         if (parse_error)
391                 exit(-1);
392         init_win2();
393
394         signal(SIGCHLD, waitkids);
395
396         if (dumpdir)
397                 genhtml(start_page);
398         else if (mgp_flag & FL_DEMO) {
399                 struct render_state state;
400
401                 memset(&state, 0, sizeof(struct render_state));
402                 state.target = window;  /*XXX*/
403                 state.width = window_width;
404                 state.height = window_height;
405                 while (start_page <= maxpage) {
406                         state_goto(&state, start_page, 0);
407                         draw_page(&state, NULL);
408                         start_page++;
409                         sleep(demointerval);    /*XXX*/
410                 }
411         } else {
412                 init_win3();
413                 main_loop(start_page);
414         }
415
416         cleanup(0);     /* never returns */
417         exit(0);        /* avoid warnings */
418         /*NOTREACHED*/
419 }
420
421 static void
422 genhtml(unsigned int start_page)
423 {
424         struct render_state state;
425         char buf[BUFSIZ];
426         int fd;
427         FILE *html;
428         FILE *txt;
429         unsigned int page;
430         const char *childdebug;
431         const char *convdb[][3] = {
432                 { "jpg", "cjpeg", "djpeg" },
433                 { "png", "pnmtopng", "pngtopnm" },
434                 { NULL, NULL, NULL }
435         };
436         int inum = 0;
437
438         /* check image type */
439         if (htmlimage) {
440                 for (inum = 0; *convdb[inum] != NULL; inum++) {
441                         if (strcmp(*convdb[inum], htmlimage) == 0)
442                                 break;
443                 }
444                 if (*convdb[inum] == NULL) {
445                         fprintf(stderr, "unknown image type %s.\n", htmlimage);
446                         /* print out valid image types */
447                         fprintf(stderr, "Valid image types: ");
448                         for (inum = 0; *convdb[inum] != 0; inum++) {
449                                 fprintf(stderr, "%s ", *convdb[inum] );
450                         }
451                         fprintf(stderr,"\n");
452                         cleanup(-1);
453                 }
454         }
455
456         /* check if we can write to the directory */
457         sprintf(buf, "%s/%ld", dumpdir, (long)time((time_t *)NULL));
458         fd = open(buf, O_WRONLY | O_CREAT, 0644);
459         if (fd < 0) {
460                 fprintf(stderr, "bad dump directory %s.\n", dumpdir);
461                 cleanup(-1);
462         }
463         close(fd);
464         unlink(buf);
465
466         memset(&state, 0, sizeof(struct render_state));
467         state.target = window;  /*XXX*/
468         state.width = window_width;
469         state.height = window_height;
470         childdebug = parse_debug ? "" : "2>/dev/null";
471         for (page = start_page; page <= maxpage; page++) {
472                 fprintf(stderr, "generating page %d... ", page);
473                 state_goto(&state, page, 0);
474                 draw_page(&state, NULL);
475
476 #define EXT convdb[inum][0]
477
478                 /*
479                  * dump out image
480                  */
481                 fprintf(stderr, "(full image)");
482                 sprintf(buf, "xwintoppm -silent -name MagicPoint | "
483                         "%s %s > %s/mgp%05d.%s",
484                         convdb[inum][1], childdebug, dumpdir, page, EXT);
485                 if (system(buf) < 0){   /*XXX security hole*/
486                         fprintf(stderr, "system() failed for %s", buf);
487                         exit(-1);
488                 }
489                 fprintf(stderr, "(thumbnail)");
490                 sprintf(buf, "%s %s/mgp%05d.%s | "
491                         "pnmscale 0.25 | %s %s > %s/mgp%05d.idx.%s",
492                         convdb[inum][2], dumpdir, page, EXT, convdb[inum][1], childdebug,
493                         dumpdir, page, EXT);
494                 if (system(buf) < 0){   /*XXX security hole*/
495                         fprintf(stderr, "system() failed for %s", buf);
496                         exit(-1);
497                 }
498
499                 /*
500                  * dump out html file
501                  */
502                 fprintf(stderr, "(html)");
503                 sprintf(buf, "%s/mgp%05d.html", dumpdir, page);
504                 html = fopen(buf, "w");
505                 if (!html)
506                         continue;
507                 fprintf(html,
508 "<HTML>\n"
509 "<HEAD><TITLE>MagicPoint presentation foils</TITLE></HEAD>\n"
510 "<BODY>\n");
511                 fprintf(html,
512                     "<A HREF=\"index.html\">[index]</A> "
513                     "<A HREF=mgp%05d.txt>[text page]</A> ", page);
514                 if (1 < page) {
515                     fprintf(html,
516                         "<A HREF=mgp%05d.html>[&lt;&lt;start]</A>  "
517                         "<A HREF=mgp%05d.html>[&lt;prev]</A> ",
518                                 1, page - 1);
519                 } else
520                         fprintf(html, "[&lt;&lt;start] [&lt;prev] ");
521                 if (page < maxpage) {
522                     fprintf(html,
523                         "<A HREF=mgp%05d.html>[next&gt;]</A> "
524                         "<A HREF=mgp%05d.html>[last&gt;&gt;]</A>\n",
525                                 page + 1, maxpage);
526                 } else
527                         fprintf(html, "[next&gt;] [last&gt;&gt;]\n");
528                 fprintf(html, "<BR>Page %d: %s<BR>\n", page, page_title(page));
529                 fprintf(html, "<HR>\n");
530                 if (window_width < 0 || window_height < 0) {
531                         fprintf(html, "<IMG SRC=\"mgp%05d.%s\" "
532                                 "ALT=\"Page %d\">\n",
533                                 page, EXT, page);
534                 } else {
535                         fprintf(html, "<IMG SRC=\"mgp%05d.%s\" "
536                                 "WIDTH=%d HEIGHT=%d ALT=\"Page %d\"><BR>\n",
537                                 page, EXT, window_width, window_height,
538                                 page);
539                 }
540                 fprintf(html, "<HR>Generated by "
541                     "<A HREF=\"http://member.wide.ad.jp/wg/mgp/\">MagicPoint</A>\n"
542                     "</BODY></HTML>\n");
543                 fclose(html);
544
545                 /*
546                  * dump out text file
547                  */
548                 fprintf(stderr, "(txt)\n");
549                 sprintf(buf, "%s/mgp%05d.txt", dumpdir, page);
550                 txt = fopen(buf, "w");
551                 if (!txt)
552                         continue;
553                 state_goto(&state, page, 0);
554                 state_init(&state);
555                 while (1) {
556                         if (state.phase == P_NONE || state.phase == P_END)
557                                 break;
558                         if (!state.cp) {
559                                 state_next(&state);
560                                 continue;
561                         }
562                         switch (state.cp->ct_op) {
563                         case CTL_PAUSE:
564                                 if (state.cp->cti_value)
565                                         goto txtdone;
566                                 break;
567                         case CTL_TEXT:
568                                 if (state.cp->ctc_value)
569                                         fprintf(txt, "%s", state.cp->ctc_value);
570                                 break;
571                         case CTL_LINEEND:
572                                 fprintf(txt, "\n");
573                                 break;
574                         }
575
576                         state_next(&state);
577                 }
578 txtdone:;
579                 fclose(txt);
580         }
581
582         fprintf(stderr, "generating top page... ");
583         sprintf(buf, "%s/index.html", dumpdir);
584         html = fopen(buf, "w");
585         if (!html) {
586                 fprintf(stderr, "could not generate top page\n");
587                 cleanup(-1);
588         }
589         fprintf(stderr, "\n");
590         fprintf(html,
591 "<HTML>\n"
592 "<HEAD><TITLE>MagicPoint presentation foils</TITLE></HEAD>\n"
593 "<BODY>\n");
594         for (page = start_page; page <= maxpage; page++) {
595                 if (window_width < 0 || window_height < 0) {
596                         fprintf(html, "<A HREF=\"mgp%05d.html\">"
597                                 "<IMG SRC=\"mgp%05d.idx.%s\" "
598                                 "ALT=\"Page %d\"></A>\n",
599                                 page, page, EXT, page);
600                 } else {
601                         fprintf(html, "<A HREF=\"mgp%05d.html\">"
602                                 "<IMG SRC=\"mgp%05d.idx.%s\" "
603                                 "WIDTH=%d HEIGHT=%d "
604                                 "ALT=\"Page %d\"></A>\n",
605                                 page, page, EXT, window_width / 4,
606                                 window_height / 4, page);
607                 }
608         }
609         fprintf(html, "<HR>\n");
610         fprintf(html, "Generated by "
611                 "<A HREF=\"http://member.wide.ad.jp/wg/mgp/\">"
612                 "MagicPoint</A>\n");
613         fprintf(html, "<BR>\n</BODY></HTML>\n");
614         fclose(html);
615 }
616
617 static void
618 mgp_show_version(char *name)
619 {
620         char *p;
621
622         if ((p = strrchr(name, '/')))
623                 p ++;
624         else
625                 p = name;
626         fprintf(stderr, "%s version %s\n", p, mgp_version);
627         exit(0);
628 }
629
630 static void
631 mgp_usage(char *name)
632 {
633         fprintf(stderr, "Usage: %s [-BCeGhnOoPqRSUVv] [-b bgcolour] [-D htmldir] [-d [interval]]"
634             "\n    [-E htmlimage] [-F mode,effect,value] [-g geometry] [-p page]"
635             "\n    [-Q quality] [-T timestampfile] [-t timeslot] [-X gsdevice]"
636             "\n    [-x engine] file", name);
637
638         fprintf(stderr, "\t-B: Ignore background image\n");
639         fprintf(stderr, "\t-b <color>: Specify background color\n");
640         fprintf(stderr, "\t-C: Use private colormap\n");
641         fprintf(stderr, "\t-D <dir>: Generate html pages for the presentation\n");
642         fprintf(stderr, "\t-d: Demo mode - go through the presentation\n");
643         fprintf(stderr, "\t-E <name>: Use this image format in html (jpg or png)\n");
644         fprintf(stderr, "\t-F<mode>,<effect>,<value>: Use forwarding caches\n");
645         fprintf(stderr, "\t-G: Page guide is on\n");
646         fprintf(stderr, "\t-g <geometry>: Set window geometry\n");
647         fprintf(stderr, "\t-h: Display this help message\n");
648         fprintf(stderr, "\t-n: Disables control key input from tty\n");
649         fprintf(stderr, "\t-O: Obey to the window manager\n");
650         fprintf(stderr, "\t-o: Do not override the window manager\n");
651         fprintf(stderr, "\t-P: print stderr from image conversion tools (by default it's discarded)\n");
652         fprintf(stderr, "\t-p <page>: Start at the specified page\n");
653         fprintf(stderr, "\t-Q <quality>: Set background image quality(0-100)\n");
654         fprintf(stderr, "\t-q Do not beep on errors\n");
655         fprintf(stderr, "\t-R: Do not perform automatic reload\n");
656         fprintf(stderr, "\t-S: Do not process directives that forks process (default)\n");
657         fprintf(stderr, "\t-T <timestampfile>: Update timestampfile on page refresh\n");
658         fprintf(stderr, "\t-t <timeslot>: Enable presentation timer\n");
659         fprintf(stderr, "\t-U: Do process directives that forks process\n\t    or allow one to use non-ASCII filenames (unsecure mode)\n");
660         fprintf(stderr, "\t-V: Show version number and quit\n");
661         fprintf(stderr, "\t-v: Be verbose\n");
662         fprintf(stderr, "\t-w <dir>: Specify a working directory\n");
663         fprintf(stderr, "\t-X <gsdevice>: ghostscript device to use\n");
664         fprintf(stderr, "\t-x <engine>: Disable specified rendering engine\n");
665         fprintf(stderr, "\t--title <title>: Set window title\n");
666
667         exit(0);
668 }
669
670 static void
671 beep(void)
672 {
673         if (!(mgp_flag & FL_NOBEEP))
674                 XBell(display, 0);
675 }
676
677 static void
678 main_loop(u_int start_page)
679 {
680         XEvent e, ahead;
681         KeySym key;
682         u_int i;
683         u_int number = 0;
684         u_int shift = 0;
685         u_int control = 0;
686         static struct render_state state;
687         static Cursor pen_curs;
688         u_int prevpage;
689
690         memset(&state, 0, sizeof(struct render_state));
691         state.target = window;  /*XXX*/
692         state.width = window_width;
693         state.height = window_height;
694         if (!pen_curs) {
695                 pen_curs = XCreateFontCursor(display, XC_dot);
696                 rakugaki_updatecolor(pen_curs);
697         }
698         state_goto(&state, start_page, 0);
699 #if 0
700         /* be conservative about first page... */
701         draw_page(&state, NULL);
702 #endif
703         if (pg_mode) {
704                 pg_on();
705                 pg_draw(&state);
706                 XFlush(display);
707         }
708
709 #ifdef TTY_KEYINPUT
710         if (!(mgp_flag & FL_NOSTDIN)) {
711                 if (tcgetattr(0, &saveattr) < 0)
712                         mgp_flag |= FL_NOSTDIN;
713                 else
714                         try_enable_ttykey();
715         }
716 #endif
717
718         while (1) {
719                 if (!t_start && 1 < state.page)
720                         t_start = time(NULL);
721                 if (rakugaki)
722                         XDefineCursor(display, window, pen_curs);
723                 else
724                         XUndefineCursor(display, window);
725
726                 do {
727                         ; /*nothing*/
728                 } while (draw_one(&state, &e) == False);
729
730                 if (t_fin)
731                         timebar(&state);
732 #if 0 /* last page reload bug fix? */
733                 if (state.cp && state.cp->ct_op == CTL_PAUSE)
734 #else
735                 if ((state.cp && state.cp->ct_op == CTL_PAUSE)
736                         || (state.page == maxpage))
737 #endif
738                     {
739                         if (tsfile) {
740                                 int fd;
741                                 fd = open(tsfile, O_WRONLY|O_TRUNC|O_CREAT,
742                                     0644);
743                                 if (fd < 0) {
744                                     fprintf(stderr, "timestamp file %s "
745                                         "write failed; "
746                                         "timestamp turning off\n",
747                                         tsfile);
748                                     tsfile = NULL;
749                                 } else {
750                                     if (write(fd, &state, sizeof(state)) < 0)
751                                         fprintf(stderr,
752                                             "timestamp file write failed\n");
753                                     close(fd);
754                                 }
755                         }
756
757                         if (wantreload()) {
758                                 draw_reinit(&state);
759                                 cleanup_file();
760                                 load_file(mgp_fname);
761                                 if (maxpage < state.page)
762                                         state.page = 1;
763                                 state_goto(&state, state.page, 1); /*repaint*/
764                         }
765                 }
766
767                 prevpage = state.page;
768
769                 switch (e.type) {
770                 case EnterNotify:
771                         for (i = 1; i <= maxpage; i++) {
772                                 if (e.xany.window == plwin[i]) {
773                                         XClearWindow(display, plwin[i]);
774                                         pl_pdraw(&state, i, gc_plrev);
775                                         pl_title(i);
776                                 }
777                         }
778                         XFlush(display);
779                         break;
780
781                 case LeaveNotify:
782                         for (i = 1; i <= maxpage; i++) {
783                                 if (e.xany.window == plwin[i]) {
784                                         XClearWindow(display, plwin[i]);
785                                         pl_pdraw(&state, i, gc_pl);
786                                         pl_title(0);
787                                 }
788                         }
789                         XFlush(display);
790                         break;
791
792                 case ButtonPress:
793                         if (rakugaki && e.xany.window == window) {
794                                 rakugaki_update(&state, &e);
795                                 break;
796                         }
797
798                         for (i = 1; i <= maxpage; i++) {
799                                 if (e.xany.window == plwin[i]) {
800                                         state_goto(&state, i, 0);
801                                         pl_off();
802                                         break;
803                                 }
804                         }
805                         if (e.xany.window == window) {
806                                 if (e.xbutton.button == 1) {
807                                         struct render_state tstate = state;
808                                         int zid = 0;
809                                         if (zoomin == 1){
810                                                 zoomin = 0;
811                                                 zoomout_zimage(zid);
812                                                 break;
813                                         }
814                                         if ((zid = search_zimage(e.xbutton.x, e.xbutton.y, state.page)) >= 0){
815                                                 zoomin_zimage(zid);
816                                                 zoomin = 1;
817                                                 break;
818                                         }
819
820                                         if (!shift && state.cp
821                                          && state.cp->ct_op == CTL_PAUSE) {
822                                                 state_next(&tstate);
823                                         } else if (state.page + 1 < maxpage) {
824                                                 state_goto(&tstate,
825                                                         state.page + 1, 0);
826                                         } else {
827                                                 beep();
828                                                 break;
829                                         }
830
831                                         if (memcmp(&state, &tstate,
832                                                         sizeof(state)) != 0) {
833                                                 state = tstate;
834                                         } else {
835                                                 /* cannot make a progress */
836                                                 beep();
837                                         }
838                                 } else if (e.xbutton.button == 3) {
839                                         if (state.page > 1) {
840                                                 state_goto(&state,
841                                                         state.page - 1, 0);
842                                         } else
843                                                 beep();
844                                 }
845                         }
846                         break;
847
848                 case ButtonRelease:
849                         if (rakugaki && e.xany.window == window)
850                                 rakugaki_update(&state, &e);
851                         if (e.xbutton.button == 2)
852                                 goto rakugaki_toggle;
853                         break;
854
855                 case MotionNotify:
856                         if (!rakugaki)
857                                 break;
858                         if (e.xany.window == window)
859                                 rakugaki_update(&state, &e);
860                         break;
861
862                 case KeyPress:
863                         key = XLookupKeysym((XKeyEvent *)&e, 0);
864
865                         switch (key) {
866                         case XK_q:
867                         case XK_Escape:
868                                 return;
869                                 /*NOTREACHED*/
870
871                         case XK_f:
872                         case XK_j:
873                         case XK_n:
874                         case XK_Down:
875                         case XK_Next:
876                         case XK_space:
877                             {
878                                 struct render_state tstate;
879                                 tstate = state;
880
881                                 if (number == 0 && state.cp && state.cp->ct_op == CTL_PAUSE) {
882                                         state_next(&tstate);
883                                 } else {
884                                     if (number == 0)
885                                         number = 1;
886
887                                     if (state.page + number
888                                                 <= maxpage) {
889                                                 state_goto(&tstate, state.page + number, 0);
890                                     } else {
891                                         beep();
892                                         break;
893                                     }
894                                 }
895
896                                 if (memcmp(&state, &tstate,
897                                                 sizeof(state)) != 0) {
898                                         state = tstate;
899                                 } else {
900                                         /* cannot make a progress */
901                                         beep();
902                                 }
903                                 number = 0;
904                                 break;
905                             }
906
907                         case XK_b:
908                         case XK_k:
909                         case XK_p:
910                         case XK_Up:
911                         case XK_Prior:
912                         case XK_BackSpace:
913                         case XK_Delete:
914                                 if (number == 0) number = 1;
915                                 if (state.page - number >= 1) {
916                                         state_goto(&state,
917                                                 state.page - number, 0);
918                                 } else
919                                         beep();
920                                 number = 0;
921                                 break;
922
923                         case XK_x:
924                                 if (shift) {
925                                         rakugaki_color++;
926                                         rakugaki_updatecolor(pen_curs);
927                                         XUndefineCursor(display, window);
928                                         XDefineCursor(display, window,
929                                                 pen_curs);
930                                         XFlush(display);
931                                 } else {
932 rakugaki_toggle:
933                                         rakugaki = 1 - rakugaki;
934                                         rakugaki_x = rakugaki_y = -1;
935
936                                         if (rakugaki) {
937                                                 XDefineCursor(display, window,
938                                                         pen_curs);
939                                         } else {
940                                                 XUndefineCursor(display,
941                                                         window);
942                                         }
943                                         XFlush(display);
944                                 }
945                                 break;
946
947                         case XK_t:
948                                 if (tbar_mode)
949                                         tbar_mode = 0;
950                                 else {
951                                         if (t_fin)
952                                                 tbar_mode = 1;
953                                 }
954                                 break;
955
956                         case XK_Control_L:
957                         case XK_Control_R:
958                                 pl_on(&state);
959                                 control = 1;
960                                 number = 0;
961                                 break;
962
963                         case XK_Shift_L:
964                         case XK_Shift_R:
965                                 shift = 1;
966                                 number = 0;
967                                 break;
968
969                         case XK_r:
970                                 if (control) {
971 reload:
972                                         pl_off();
973                                         cached_page = 0;
974                                         reset_background_pixmap();
975                                         draw_reinit(&state);
976                                         cleanup_file();
977                                         load_file(mgp_fname);
978                                         if (maxpage < state.page)
979                                                 state.page = 1;
980                                         state_goto(&state, state.page, 1);
981                                         goto repaint;
982                                 }
983                                 break;
984
985                         case XK_l:      /* used to be control-L */
986 repaint:;
987                             {
988                                 struct ctrl *lastcp;
989                                 lastcp = state.cp;
990                                 state.repaint = 1;
991                                 state_goto(&state, state.page, 1);
992                                 draw_page(&state, lastcp);
993                                 state.repaint = 0;
994                                 number = 0;
995                             }
996                                 break;
997
998                         case XK_g:
999                                 if (shift) {
1000                                         pg_mode = 1 - pg_mode;
1001                                         if (pg_mode) {
1002                                                 pg_on();
1003                                                 pg_draw(&state);
1004                                         } else
1005                                                 pg_off();
1006                                 } else {
1007                                         if (number == 0)
1008                                                 number = maxpage;
1009                                         if (number <= maxpage && number > 0) {
1010                                                 state_goto(&state, number, 0);
1011                                                 state_newpage(&state);
1012                                                 state_init(&state);
1013                                         } else
1014                                                 beep();
1015                                 }
1016                                 number = 0;
1017                                 break;
1018
1019                         case XK_0:
1020                         case XK_1:
1021                         case XK_2:
1022                         case XK_3:
1023                         case XK_4:
1024                         case XK_5:
1025                         case XK_6:
1026                         case XK_7:
1027                         case XK_8:
1028                         case XK_9:
1029                                 number = number * 10 + key - XK_0;
1030                                 break;
1031
1032                         case XK_c:
1033                                 if (verbose)  {
1034                                         if (mgp_flag & FL_FRDCACHE)
1035                                                 printf("turn off forward cache\n");
1036                                         else
1037                                                 printf("turn on forward cache\n");
1038                                 }
1039
1040                                 mgp_flag ^= FL_FRDCACHE;
1041                         break;
1042
1043                         case XK_a:
1044                                 XCopyArea(display, cachewin, window, gc_cache,
1045                                                 0, 0, window_width, window_height, 0, 0);
1046                                 break;
1047
1048                         case XK_w:
1049                                 toggle_fullscreen();
1050                                 break;
1051
1052                         default:
1053                                 number = 0;
1054                                 break;
1055                         }
1056
1057                         break;
1058
1059                 case KeyRelease:
1060                         key = XLookupKeysym((XKeyEvent *)&e, 0);
1061
1062                         switch (key) {
1063                         case XK_Control_L:
1064                         case XK_Control_R:
1065                                 pl_off();
1066                                 control = 0;
1067                                 break;
1068                         case XK_Shift_L:
1069                         case XK_Shift_R:
1070                                 shift = 0;
1071                                 break;
1072                         }
1073                         break;
1074
1075                 case Expose:
1076                         if (e.xexpose.window != window)
1077                                 break;
1078                         if (state.repaint)
1079                                 break;
1080
1081                         /* compress expose event */
1082                         while (XEventsQueued(display, QueuedAfterReading) > 0) {
1083                                 XPeekEvent(display, &ahead);
1084                                 if (ahead.type != Expose && ahead.type != ConfigureNotify)
1085                                         break;
1086                                 if (ahead.xexpose.window != window)
1087                                         break;
1088                                 XNextEvent(display, &e);
1089                         }
1090
1091                         if (wantreload())
1092                                 goto reload;
1093                         if ((state.cp && state.cp->ct_op == CTL_PAUSE) ||
1094                                 (state.page  == maxpage))
1095                                 goto repaint;
1096                         break;
1097
1098                 case ConfigureNotify:
1099                         if ((e.xconfigure.window != window) &&
1100                                 ((mgp_flag & FL_OVER) ||
1101                                 e.xconfigure.window != RootWindow(display, screen)))
1102                                 break;
1103                         /* compress expose event */
1104                         while (XEventsQueued(display, QueuedAfterReading) > 0) {
1105                                 XPeekEvent(display, &ahead);
1106                                 if (ahead.type != Expose && ahead.type != ConfigureNotify)
1107                                         break;
1108                                 if (ahead.xconfigure.window != window)
1109                                         break;
1110                                 XNextEvent(display, &e);
1111                         }
1112                         if (window_width != e.xconfigure.width
1113                          || window_height != e.xconfigure.height) {
1114                                 struct ctrl *lastcp;
1115
1116                                 if (!(mgp_flag & FL_OVER))
1117                                         XMoveResizeWindow(display, window, 0, 0,
1118                                                 e.xconfigure.width, e.xconfigure.height);
1119
1120                                 if (pg_mode)
1121                                         pg_off();
1122                                 pl_off();
1123                                 window_width = e.xconfigure.width;
1124                                 window_height = e.xconfigure.height;
1125                                 state.width = e.xconfigure.width;
1126                                 state.height = e.xconfigure.height;
1127                                 if (mgp_flag & FL_FRDCACHE) {
1128                                         cached_page = 0;
1129                                         reset_background_pixmap();
1130                                 }
1131                                 reset_background_pixmap();
1132                                 draw_reinit(&state);    /*notify*/
1133                                 lastcp = state.cp;
1134                                 state_goto(&state, state.page, 1);
1135                                 draw_page(&state, lastcp);
1136
1137                         }
1138                         if (pg_mode) {
1139                                 pg_on();
1140                                 pg_draw(&state);
1141                         }
1142                         if (wantreload())
1143                                 goto reload;
1144                         break;
1145                 }
1146
1147                 /* page may have changed... */
1148                 if (pg_mode)
1149                         pg_draw(&state);
1150                 if (prevpage != state.page) {
1151                         pl_pdraw(&state, prevpage, gc_pl);
1152                         pl_pdraw(&state, state.page, gc_plrev);
1153                         pl_title(state.page);
1154                 }
1155
1156                 if (state.phase == P_END) {
1157                         if (state.page < maxpage)
1158                                 state_goto(&state, state.page + 1, 0);
1159                 }
1160         }
1161 }
1162
1163 static void
1164 rakugaki_update(struct render_state *state, XEvent *e)
1165 {
1166         int x, y;
1167
1168         if (e->type == MotionNotify) {
1169                 XMotionEvent *em;
1170                 em = (XMotionEvent *)e;
1171                 x = em->x; y = em->y;
1172         } else if (e->type == ButtonPress) {
1173                 XButtonPressedEvent *eb;
1174                 eb = (XButtonPressedEvent *)e;
1175                 x = eb->x; y = eb->y;
1176                 if (e->xbutton.button != 1) return;
1177         } else {
1178                 rakugaki_x = rakugaki_y = -1;
1179                 return;
1180         }
1181
1182         if (rakugaki_x < 0 || rakugaki_y < 0)
1183                 XDrawRectangle(display, state->target, gcpen, x, y, 1, 1);
1184         else {
1185                 XDrawLine(display, state->target, gcpen,
1186                         x, y,
1187                         rakugaki_x, rakugaki_y);
1188                 XDrawLine(display, state->target, gcpen,
1189                         x + 1, y,
1190                         rakugaki_x + 1, rakugaki_y);
1191                 XDrawLine(display, state->target, gcpen,
1192                         x, y + 1,
1193                         rakugaki_x, rakugaki_y + 1);
1194                 XDrawLine(display, state->target, gcpen,
1195                         x + 1, y + 1,
1196                         rakugaki_x + 1, rakugaki_y + 1);
1197         }
1198         rakugaki_x = x;
1199         rakugaki_y = y;
1200 }
1201
1202 static void
1203 rakugaki_updatecolor(Cursor cursor)
1204 {
1205         XColor junk;
1206         int maxidx;
1207
1208         maxidx = sizeof(rakugaki_forecolors)/sizeof(rakugaki_forecolors[0]);
1209         if (maxidx <= rakugaki_color)
1210                 rakugaki_color %= maxidx;
1211
1212         XAllocNamedColor(display, DefaultColormap(display, screen),
1213                 rakugaki_forecolors[rakugaki_color], &rakugaki_fore, &junk);
1214         XAllocNamedColor(display, DefaultColormap(display, screen),
1215                 rakugaki_backcolors[rakugaki_color], &rakugaki_back, &junk);
1216
1217         /*
1218          * due to the design of the cursor, it looks more natural if we swap
1219          * the background color and foreground color.
1220          */
1221         XRecolorCursor(display, cursor, &rakugaki_back, &rakugaki_fore);
1222
1223         XSetForeground(display, gcpen, rakugaki_fore.pixel);
1224 }
1225
1226 static struct {
1227         void *key;
1228         pid_t pid;
1229         Window window_id;
1230         int flag;
1231 } childtab[64];
1232 static int childidx = 0;
1233
1234 pid_t
1235 checkchild(void *key)
1236 {
1237         int i;
1238
1239         for (i = 0; i < childidx; i++) {
1240                 if (childtab[i].pid == (pid_t)-1)
1241                         continue;
1242                 if (childtab[i].key == key)
1243                         return childtab[i].pid;
1244         }
1245         return (pid_t)-1;
1246 }
1247
1248 Window
1249 checkchildwin(void *key)
1250 {
1251         int i;
1252
1253         for (i = 0; i < childidx; i++) {
1254                 if (childtab[i].pid == (pid_t)-1)
1255                         continue;
1256                 if (childtab[i].key == key)
1257                         return childtab[i].window_id;
1258         }
1259         return (Window)-1;
1260 }
1261
1262 void
1263 regchild(pid_t pid, void *key, Window window_id, int flag)
1264 {
1265         int i;
1266
1267         for (i = 0; i < childidx; i++) {
1268                 if (childtab[i].pid == (pid_t)-1) {
1269                         childtab[i].pid = pid;
1270                         childtab[i].key = key;
1271                         childtab[i].window_id = window_id;
1272                         childtab[i].flag = flag;
1273                         return;
1274                 }
1275         }
1276         childtab[childidx].pid = pid;
1277         childtab[childidx].key = key;
1278         childtab[childidx].flag = flag;
1279         childtab[childidx].window_id = window_id;
1280         childidx++;
1281 }
1282
1283 void
1284 purgechild(int flag)
1285 {
1286         int i;
1287
1288         for (i = 0; i < childidx; i++) {
1289                 if (childtab[i].pid == (pid_t)-1)
1290                         continue;
1291                 if (childtab[i].flag == flag){
1292                         kill(childtab[i].pid, SIGTERM);
1293                 }
1294         }
1295 }
1296
1297 static void
1298 waitkids(int sig)
1299 {
1300         int status;
1301         int i;
1302         pid_t pid;
1303
1304         if (sig != SIGCHLD) {
1305                 fprintf(stderr, "signal different from expected: %d\n", sig);
1306                 cleanup(-1);
1307         }
1308         while ((pid_t) 0 < (pid = waitpid(-1, &status, WNOHANG))) {
1309                 for (i = 0; i < childidx; i++) {
1310                         if (childtab[i].pid == pid) {
1311                                 childtab[i].pid = (pid_t)-1;
1312                                 childtab[i].window_id = (Window)-1;
1313                         }
1314                 }
1315         }
1316 }
1317
1318 static int
1319 wantreload(void)
1320 {
1321         struct stat sb;
1322
1323         if (mgp_flag & FL_NOAUTORELOAD)
1324                 return 0;
1325
1326         if (0 <= stat(mgp_fname, &sb)) {
1327                 if (srctimestamp < sb.st_ctime) {
1328                         srctimestamp = sb.st_ctime;
1329                         return 1;
1330                 }
1331         }
1332
1333         return 0;
1334 }
1335
1336 /*
1337   remap child window which was invoked by xsystem directive.
1338   this is an adhoc solution for window-maker.
1339 */
1340 void
1341 remapchild(void)
1342 {
1343         int     i;
1344
1345         for (i = 0; i < childidx; i++) {
1346                 if (childtab[i].window_id > 0){
1347                         XMapSubwindows(display, window);
1348                         XFlush(display);
1349                         return;
1350                 }
1351         }
1352         return;
1353 }