1#include "pdfapp.h"
2
3#include <X11/Xlib.h>
4#include <X11/Xutil.h>
5#include <X11/Xatom.h>
6#include <X11/cursorfont.h>
7#include <X11/keysym.h>
8#include <X11/XF86keysym.h>
9
10#include <string.h>
11#include <stdlib.h>
12#include <stdio.h>
13
14#include <sys/select.h>
15#include <sys/time.h>
16#include <sys/types.h>
17#include <sys/wait.h>
18#include <unistd.h>
19#include <signal.h>
20
21#define mupdf_icon_bitmap_16_width 16
22#define mupdf_icon_bitmap_16_height 16
23static unsigned char mupdf_icon_bitmap_16_bits[] = {
24 0x00, 0x00, 0x00, 0x1e, 0x00, 0x2b, 0x80, 0x55, 0x8c, 0x62, 0x8c, 0x51,
25 0x9c, 0x61, 0x1c, 0x35, 0x3c, 0x1f, 0x3c, 0x0f, 0xfc, 0x0f, 0xec, 0x0d,
26 0xec, 0x0d, 0xcc, 0x0c, 0xcc, 0x0c, 0x00, 0x00 };
27
28#define mupdf_icon_bitmap_16_mask_width 16
29#define mupdf_icon_bitmap_16_mask_height 16
30static unsigned char mupdf_icon_bitmap_16_mask_bits[] = {
31 0x00, 0x1e, 0x00, 0x3f, 0x80, 0x7f, 0xce, 0xff, 0xde, 0xff, 0xde, 0xff,
32 0xfe, 0xff, 0xfe, 0x7f, 0xfe, 0x3f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
33 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xce, 0x1c };
34
35#ifndef timeradd
36#define timeradd(a, b, result) \
37 do { \
38 (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
39 (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
40 if ((result)->tv_usec >= 1000000) \
41 { \
42 ++(result)->tv_sec; \
43 (result)->tv_usec -= 1000000; \
44 } \
45 } while (0)
46#endif
47
48#ifndef timersub
49#define timersub(a, b, result) \
50 do { \
51 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
52 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
53 if ((result)->tv_usec < 0) { \
54 --(result)->tv_sec; \
55 (result)->tv_usec += 1000000; \
56 } \
57 } while (0)
58#endif
59
60extern int ximage_init(Display *display, int screen, Visual *visual);
61extern int ximage_get_depth(void);
62extern Visual *ximage_get_visual(void);
63extern Colormap ximage_get_colormap(void);
64extern void ximage_blit(Drawable d, GC gc, int dstx, int dsty,
65 unsigned char *srcdata,
66 int srcx, int srcy, int srcw, int srch, int srcstride);
67
68void windrawstringxor(pdfapp_t *app, int x, int y, char *s);
69void cleanup(pdfapp_t *app);
70
71static Display *xdpy;
72static Atom XA_CLIPBOARD;
73static Atom XA_TARGETS;
74static Atom XA_TIMESTAMP;
75static Atom XA_UTF8_STRING;
76static Atom WM_DELETE_WINDOW;
77static Atom NET_WM_NAME;
78static Atom NET_WM_STATE;
79static Atom NET_WM_STATE_FULLSCREEN;
80static Atom WM_RELOAD_PAGE;
81static int x11fd;
82static int xscr;
83static Window xwin;
84static Pixmap xicon, xmask;
85static GC xgc;
86static XEvent xevt;
87static int mapped = 0;
88static Cursor xcarrow, xchand, xcwait, xccaret;
89static int justcopied = 0;
90static int dirty = 0;
91static int transition_dirty = 0;
92static int dirtysearch = 0;
93static char *password = "";
94static XColor xbgcolor;
95static int reqw = 0;
96static int reqh = 0;
97static char copylatin1[1024 * 16] = "";
98static char copyutf8[1024 * 48] = "";
99static Time copytime;
100static char *filename;
101static char message[1024] = "";
102
103static pdfapp_t gapp;
104static int closing = 0;
105static int reloading = 0;
106static int showingpage = 0;
107static int showingmessage = 0;
108
109static int advance_scheduled = 0;
110static struct timeval tmo;
111static struct timeval tmo_advance;
112static struct timeval tmo_at;
113
114/*
115 * Dialog boxes
116 */
117static void showmessage(pdfapp_t *app, int timeout, char *msg)
118{
119 struct timeval now;
120
121 showingmessage = 1;
122 showingpage = 0;
123
124 fz_strlcpy(message, msg, sizeof message);
125
126 if ((!tmo_at.tv_sec && !tmo_at.tv_usec) || tmo.tv_sec < timeout)
127 {
128 tmo.tv_sec = timeout;
129 tmo.tv_usec = 0;
130 gettimeofday(&now, NULL);
131 timeradd(&now, &tmo, &tmo_at);
132 }
133}
134
135void winerror(pdfapp_t *app, char *msg)
136{
137 fprintf(stderr, "mupdf: error: %s\n", msg);
138 cleanup(app);
139 exit(1);
140}
141
142void winwarn(pdfapp_t *app, char *msg)
143{
144 char buf[1024];
145 snprintf(buf, sizeof buf, "warning: %s", msg);
146 showmessage(app, 10, buf);
147 fprintf(stderr, "mupdf: %s\n", buf);
148}
149
150void winalert(pdfapp_t *app, pdf_alert_event *alert)
151{
152 char buf[1024];
153 snprintf(buf, sizeof buf, "Alert %s: %s", alert->title, alert->message);
154 fprintf(stderr, "%s\n", buf);
155 switch (alert->button_group_type)
156 {
157 case PDF_ALERT_BUTTON_GROUP_OK:
158 case PDF_ALERT_BUTTON_GROUP_OK_CANCEL:
159 alert->button_pressed = PDF_ALERT_BUTTON_OK;
160 break;
161 case PDF_ALERT_BUTTON_GROUP_YES_NO:
162 case PDF_ALERT_BUTTON_GROUP_YES_NO_CANCEL:
163 alert->button_pressed = PDF_ALERT_BUTTON_YES;
164 break;
165 }
166}
167
168void winprint(pdfapp_t *app)
169{
170 fprintf(stderr, "The MuPDF library supports printing, but this application currently does not\n");
171}
172
173char *winpassword(pdfapp_t *app, char *filename)
174{
175 char *r = password;
176 password = NULL;
177 return r;
178}
179
180char *wintextinput(pdfapp_t *app, char *inittext, int retry)
181{
182 /* We don't support text input on the x11 viewer */
183 return NULL;
184}
185
186int winchoiceinput(pdfapp_t *app, int nopts, const char *opts[], int *nvals, const char *vals[])
187{
188 /* FIXME: temporary dummy implementation */
189 return 0;
190}
191
192/*
193 * X11 magic
194 */
195
196static void winopen(void)
197{
198 XWMHints *wmhints;
199 XClassHint *classhint;
200
201#ifdef HAVE_CURL
202 if (!XInitThreads())
203 fz_throw(gapp.ctx, FZ_ERROR_GENERIC, "cannot initialize X11 for multi-threading");
204#endif
205
206 xdpy = XOpenDisplay(NULL);
207 if (!xdpy)
208 fz_throw(gapp.ctx, FZ_ERROR_GENERIC, "cannot open display");
209
210 XA_CLIPBOARD = XInternAtom(xdpy, "CLIPBOARD", False);
211 XA_TARGETS = XInternAtom(xdpy, "TARGETS", False);
212 XA_TIMESTAMP = XInternAtom(xdpy, "TIMESTAMP", False);
213 XA_UTF8_STRING = XInternAtom(xdpy, "UTF8_STRING", False);
214 WM_DELETE_WINDOW = XInternAtom(xdpy, "WM_DELETE_WINDOW", False);
215 NET_WM_NAME = XInternAtom(xdpy, "_NET_WM_NAME", False);
216 NET_WM_STATE = XInternAtom(xdpy, "_NET_WM_STATE", False);
217 NET_WM_STATE_FULLSCREEN = XInternAtom(xdpy, "_NET_WM_STATE_FULLSCREEN", False);
218 WM_RELOAD_PAGE = XInternAtom(xdpy, "_WM_RELOAD_PAGE", False);
219
220 xscr = DefaultScreen(xdpy);
221
222 ximage_init(xdpy, xscr, DefaultVisual(xdpy, xscr));
223
224 xcarrow = XCreateFontCursor(xdpy, XC_left_ptr);
225 xchand = XCreateFontCursor(xdpy, XC_hand2);
226 xcwait = XCreateFontCursor(xdpy, XC_watch);
227 xccaret = XCreateFontCursor(xdpy, XC_xterm);
228
229 xbgcolor.red = 0x7000;
230 xbgcolor.green = 0x7000;
231 xbgcolor.blue = 0x7000;
232
233 XAllocColor(xdpy, DefaultColormap(xdpy, xscr), &xbgcolor);
234
235 xwin = XCreateWindow(xdpy, DefaultRootWindow(xdpy),
236 10, 10, 200, 100, 0,
237 ximage_get_depth(),
238 InputOutput,
239 ximage_get_visual(),
240 0,
241 NULL);
242 if (xwin == None)
243 fz_throw(gapp.ctx, FZ_ERROR_GENERIC, "cannot create window");
244
245 XSetWindowColormap(xdpy, xwin, ximage_get_colormap());
246 XSelectInput(xdpy, xwin,
247 StructureNotifyMask | ExposureMask | KeyPressMask |
248 PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
249
250 mapped = 0;
251
252 xgc = XCreateGC(xdpy, xwin, 0, NULL);
253
254 XDefineCursor(xdpy, xwin, xcarrow);
255
256 wmhints = XAllocWMHints();
257 if (wmhints)
258 {
259 wmhints->flags = IconPixmapHint | IconMaskHint;
260 xicon = XCreateBitmapFromData(xdpy, xwin,
261 (char*)mupdf_icon_bitmap_16_bits,
262 mupdf_icon_bitmap_16_width,
263 mupdf_icon_bitmap_16_height);
264 xmask = XCreateBitmapFromData(xdpy, xwin,
265 (char*)mupdf_icon_bitmap_16_mask_bits,
266 mupdf_icon_bitmap_16_mask_width,
267 mupdf_icon_bitmap_16_mask_height);
268 if (xicon && xmask)
269 {
270 wmhints->icon_pixmap = xicon;
271 wmhints->icon_mask = xmask;
272 XSetWMHints(xdpy, xwin, wmhints);
273 }
274 XFree(wmhints);
275 }
276
277 classhint = XAllocClassHint();
278 if (classhint)
279 {
280 classhint->res_name = "mupdf";
281 classhint->res_class = "MuPDF";
282 XSetClassHint(xdpy, xwin, classhint);
283 XFree(classhint);
284 }
285
286 XSetWMProtocols(xdpy, xwin, &WM_DELETE_WINDOW, 1);
287
288 x11fd = ConnectionNumber(xdpy);
289}
290
291void winclose(pdfapp_t *app)
292{
293 if (pdfapp_preclose(app))
294 {
295 closing = 1;
296 }
297}
298
299int winsavequery(pdfapp_t *app)
300{
301 fprintf(stderr, "mupdf: discarded changes to document\n");
302 /* FIXME: temporary dummy implementation */
303 return DISCARD;
304}
305
306int wingetsavepath(pdfapp_t *app, char *buf, int len)
307{
308 /* FIXME: temporary dummy implementation */
309 return 0;
310}
311
312void winreplacefile(char *source, char *target)
313{
314 rename(source, target);
315}
316
317void wincopyfile(char *source, char *target)
318{
319 FILE *in, *out;
320 char buf[32 << 10];
321 int n;
322
323 in = fopen(source, "rb");
324 if (!in)
325 {
326 winerror(&gapp, "cannot open source file for copying");
327 return;
328 }
329 out = fopen(target, "wb");
330 if (!out)
331 {
332 winerror(&gapp, "cannot open target file for copying");
333 fclose(in);
334 return;
335 }
336
337 for (;;)
338 {
339 n = fread(buf, 1, sizeof buf, in);
340 fwrite(buf, 1, n, out);
341 if (n < sizeof buf)
342 {
343 if (ferror(in))
344 winerror(&gapp, "cannot read data from source file");
345 break;
346 }
347 }
348
349 fclose(out);
350 fclose(in);
351}
352
353void cleanup(pdfapp_t *app)
354{
355 fz_context *ctx = app->ctx;
356
357 pdfapp_close(app);
358
359 XDestroyWindow(xdpy, xwin);
360
361 XFreePixmap(xdpy, xicon);
362
363 XFreeCursor(xdpy, xccaret);
364 XFreeCursor(xdpy, xcwait);
365 XFreeCursor(xdpy, xchand);
366 XFreeCursor(xdpy, xcarrow);
367
368 XFreeGC(xdpy, xgc);
369
370 XCloseDisplay(xdpy);
371
372 fz_drop_context(ctx);
373}
374
375static int winresolution(void)
376{
377 return DisplayWidth(xdpy, xscr) * 25.4f /
378 DisplayWidthMM(xdpy, xscr) + 0.5f;
379}
380
381void wincursor(pdfapp_t *app, int curs)
382{
383 if (curs == ARROW)
384 XDefineCursor(xdpy, xwin, xcarrow);
385 if (curs == HAND)
386 XDefineCursor(xdpy, xwin, xchand);
387 if (curs == WAIT)
388 XDefineCursor(xdpy, xwin, xcwait);
389 if (curs == CARET)
390 XDefineCursor(xdpy, xwin, xccaret);
391 XFlush(xdpy);
392}
393
394void wintitle(pdfapp_t *app, char *s)
395{
396 XStoreName(xdpy, xwin, s);
397#ifdef X_HAVE_UTF8_STRING
398 Xutf8SetWMProperties(xdpy, xwin, s, s, NULL, 0, NULL, NULL, NULL);
399#else
400 XmbSetWMProperties(xdpy, xwin, s, s, NULL, 0, NULL, NULL, NULL);
401#endif
402 XChangeProperty(xdpy, xwin, NET_WM_NAME, XA_UTF8_STRING, 8,
403 PropModeReplace, (unsigned char *)s, strlen(s));
404}
405
406void winhelp(pdfapp_t *app)
407{
408 fprintf(stderr, "%s\n%s", pdfapp_version(app), pdfapp_usage(app));
409}
410
411void winresize(pdfapp_t *app, int w, int h)
412{
413 int image_w = gapp.layout_w;
414 int image_h = gapp.layout_h;
415 XWindowChanges values;
416 int mask, width, height;
417
418 if (gapp.image)
419 {
420 image_w = fz_pixmap_width(gapp.ctx, gapp.image);
421 image_h = fz_pixmap_height(gapp.ctx, gapp.image);
422 }
423
424 mask = CWWidth | CWHeight;
425 values.width = w;
426 values.height = h;
427 XConfigureWindow(xdpy, xwin, mask, &values);
428
429 reqw = w;
430 reqh = h;
431
432 if (!mapped)
433 {
434 gapp.winw = w;
435 gapp.winh = h;
436 width = -1;
437 height = -1;
438
439 XMapWindow(xdpy, xwin);
440 XFlush(xdpy);
441
442 while (1)
443 {
444 XNextEvent(xdpy, &xevt);
445 if (xevt.type == ConfigureNotify)
446 {
447 width = xevt.xconfigure.width;
448 height = xevt.xconfigure.height;
449 }
450 if (xevt.type == MapNotify)
451 break;
452 }
453
454 XSetForeground(xdpy, xgc, WhitePixel(xdpy, xscr));
455 XFillRectangle(xdpy, xwin, xgc, 0, 0, image_w, image_h);
456 XFlush(xdpy);
457
458 if (width != reqw || height != reqh)
459 {
460 gapp.shrinkwrap = 0;
461 dirty = 1;
462 pdfapp_onresize(&gapp, width, height);
463 }
464
465 mapped = 1;
466 }
467}
468
469void winfullscreen(pdfapp_t *app, int state)
470{
471 XEvent xev;
472 xev.xclient.type = ClientMessage;
473 xev.xclient.serial = 0;
474 xev.xclient.send_event = True;
475 xev.xclient.window = xwin;
476 xev.xclient.message_type = NET_WM_STATE;
477 xev.xclient.format = 32;
478 xev.xclient.data.l[0] = state;
479 xev.xclient.data.l[1] = NET_WM_STATE_FULLSCREEN;
480 xev.xclient.data.l[2] = 0;
481 XSendEvent(xdpy, DefaultRootWindow(xdpy), False,
482 SubstructureRedirectMask | SubstructureNotifyMask,
483 &xev);
484}
485
486static void fillrect(int x, int y, int w, int h)
487{
488 if (w > 0 && h > 0)
489 XFillRectangle(xdpy, xwin, xgc, x, y, w, h);
490}
491
492static void winblitstatusbar(pdfapp_t *app)
493{
494 if (gapp.issearching)
495 {
496 char buf[sizeof(gapp.search) + 50];
497 sprintf(buf, "Search: %s", gapp.search);
498 XSetForeground(xdpy, xgc, WhitePixel(xdpy, xscr));
499 fillrect(0, 0, gapp.winw, 30);
500 windrawstring(&gapp, 10, 20, buf);
501 }
502 else if (showingmessage)
503 {
504 XSetForeground(xdpy, xgc, WhitePixel(xdpy, xscr));
505 fillrect(0, 0, gapp.winw, 30);
506 windrawstring(&gapp, 10, 20, message);
507 }
508 else if (showingpage)
509 {
510 char buf[42];
511 snprintf(buf, sizeof buf, "Page %d/%d", gapp.pageno, gapp.pagecount);
512 windrawstringxor(&gapp, 10, 20, buf);
513 }
514}
515
516static void winblit(pdfapp_t *app)
517{
518 if (gapp.image)
519 {
520 int image_w = fz_pixmap_width(gapp.ctx, gapp.image);
521 int image_h = fz_pixmap_height(gapp.ctx, gapp.image);
522 int image_n = fz_pixmap_components(gapp.ctx, gapp.image);
523 unsigned char *image_samples = fz_pixmap_samples(gapp.ctx, gapp.image);
524 int x0 = gapp.panx;
525 int y0 = gapp.pany;
526 int x1 = gapp.panx + image_w;
527 int y1 = gapp.pany + image_h;
528
529 if (app->invert)
530 XSetForeground(xdpy, xgc, BlackPixel(xdpy, DefaultScreen(xdpy)));
531 else
532 XSetForeground(xdpy, xgc, xbgcolor.pixel);
533 fillrect(0, 0, x0, gapp.winh);
534 fillrect(x1, 0, gapp.winw - x1, gapp.winh);
535 fillrect(0, 0, gapp.winw, y0);
536 fillrect(0, y1, gapp.winw, gapp.winh - y1);
537
538 if (gapp.iscopying || justcopied)
539 {
540 pdfapp_invert(&gapp, gapp.selr);
541 justcopied = 1;
542 }
543
544 pdfapp_inverthit(&gapp);
545
546 if (image_n == 4)
547 ximage_blit(xwin, xgc,
548 x0, y0,
549 image_samples,
550 0, 0,
551 image_w,
552 image_h,
553 image_w * image_n);
554 else if (image_n == 2)
555 {
556 int i = image_w*image_h;
557 unsigned char *color = malloc(i*4);
558 if (color)
559 {
560 unsigned char *s = image_samples;
561 unsigned char *d = color;
562 for (; i > 0 ; i--)
563 {
564 d[2] = d[1] = d[0] = *s++;
565 d[3] = *s++;
566 d += 4;
567 }
568 ximage_blit(xwin, xgc,
569 x0, y0,
570 color,
571 0, 0,
572 image_w,
573 image_h,
574 image_w * 4);
575 free(color);
576 }
577 }
578
579 pdfapp_inverthit(&gapp);
580
581 if (gapp.iscopying || justcopied)
582 {
583 pdfapp_invert(&gapp, gapp.selr);
584 justcopied = 1;
585 }
586 }
587 else
588 {
589 XSetForeground(xdpy, xgc, xbgcolor.pixel);
590 fillrect(0, 0, gapp.winw, gapp.winh);
591 }
592
593 winblitstatusbar(app);
594}
595
596void winrepaint(pdfapp_t *app)
597{
598 dirty = 1;
599 if (app->in_transit)
600 transition_dirty = 1;
601}
602
603void winrepaintsearch(pdfapp_t *app)
604{
605 dirtysearch = 1;
606}
607
608void winadvancetimer(pdfapp_t *app, float duration)
609{
610 struct timeval now;
611
612 gettimeofday(&now, NULL);
613 memset(&tmo_advance, 0, sizeof(tmo_advance));
614 tmo_advance.tv_sec = (int)duration;
615 tmo_advance.tv_usec = 1000000 * (duration - tmo_advance.tv_sec);
616 timeradd(&tmo_advance, &now, &tmo_advance);
617 advance_scheduled = 1;
618}
619
620void windrawstringxor(pdfapp_t *app, int x, int y, char *s)
621{
622 int prevfunction;
623 XGCValues xgcv;
624
625 XGetGCValues(xdpy, xgc, GCFunction, &xgcv);
626 prevfunction = xgcv.function;
627 xgcv.function = GXxor;
628 XChangeGC(xdpy, xgc, GCFunction, &xgcv);
629
630 XSetForeground(xdpy, xgc, WhitePixel(xdpy, DefaultScreen(xdpy)));
631
632 XDrawString(xdpy, xwin, xgc, x, y, s, strlen(s));
633 XFlush(xdpy);
634
635 XGetGCValues(xdpy, xgc, GCFunction, &xgcv);
636 xgcv.function = prevfunction;
637 XChangeGC(xdpy, xgc, GCFunction, &xgcv);
638}
639
640void windrawstring(pdfapp_t *app, int x, int y, char *s)
641{
642 XSetForeground(xdpy, xgc, BlackPixel(xdpy, DefaultScreen(xdpy)));
643 XDrawString(xdpy, xwin, xgc, x, y, s, strlen(s));
644}
645
646void docopy(pdfapp_t *app, Atom copy_target)
647{
648 unsigned short copyucs2[16 * 1024];
649 char *latin1 = copylatin1;
650 char *utf8 = copyutf8;
651 unsigned short *ucs2;
652 int ucs;
653
654 pdfapp_oncopy(&gapp, copyucs2, 16 * 1024);
655
656 for (ucs2 = copyucs2; ucs2[0] != 0; ucs2++)
657 {
658 ucs = ucs2[0];
659
660 utf8 += fz_runetochar(utf8, ucs);
661
662 if (ucs < 256)
663 *latin1++ = ucs;
664 else
665 *latin1++ = '?';
666 }
667
668 *utf8 = 0;
669 *latin1 = 0;
670
671 XSetSelectionOwner(xdpy, copy_target, xwin, copytime);
672
673 justcopied = 1;
674}
675
676void windocopy(pdfapp_t *app)
677{
678 docopy(app, XA_PRIMARY);
679}
680
681void onselreq(Window requestor, Atom selection, Atom target, Atom property, Time time)
682{
683 XEvent nevt;
684
685 advance_scheduled = 0;
686
687 if (property == None)
688 property = target;
689
690 nevt.xselection.type = SelectionNotify;
691 nevt.xselection.send_event = True;
692 nevt.xselection.display = xdpy;
693 nevt.xselection.requestor = requestor;
694 nevt.xselection.selection = selection;
695 nevt.xselection.target = target;
696 nevt.xselection.property = property;
697 nevt.xselection.time = time;
698
699 if (target == XA_TARGETS)
700 {
701 Atom atomlist[4];
702 atomlist[0] = XA_TARGETS;
703 atomlist[1] = XA_TIMESTAMP;
704 atomlist[2] = XA_STRING;
705 atomlist[3] = XA_UTF8_STRING;
706 XChangeProperty(xdpy, requestor, property, target,
707 32, PropModeReplace,
708 (unsigned char *)atomlist, sizeof(atomlist)/sizeof(Atom));
709 }
710
711 else if (target == XA_STRING)
712 {
713 XChangeProperty(xdpy, requestor, property, target,
714 8, PropModeReplace,
715 (unsigned char *)copylatin1, strlen(copylatin1));
716 }
717
718 else if (target == XA_UTF8_STRING)
719 {
720 XChangeProperty(xdpy, requestor, property, target,
721 8, PropModeReplace,
722 (unsigned char *)copyutf8, strlen(copyutf8));
723 }
724
725 else
726 {
727 nevt.xselection.property = None;
728 }
729
730 XSendEvent(xdpy, requestor, False, 0, &nevt);
731}
732
733void winreloadpage(pdfapp_t *app)
734{
735 XEvent xev;
736 Display *dpy = XOpenDisplay(NULL);
737
738 xev.xclient.type = ClientMessage;
739 xev.xclient.serial = 0;
740 xev.xclient.send_event = True;
741 xev.xclient.window = xwin;
742 xev.xclient.message_type = WM_RELOAD_PAGE;
743 xev.xclient.format = 32;
744 xev.xclient.data.l[0] = 0;
745 xev.xclient.data.l[1] = 0;
746 xev.xclient.data.l[2] = 0;
747 XSendEvent(dpy, xwin, 0, 0, &xev);
748 XCloseDisplay(dpy);
749}
750
751void winopenuri(pdfapp_t *app, char *buf)
752{
753 char *browser = getenv("BROWSER");
754 pid_t pid;
755 if (!browser)
756 {
757#ifdef __APPLE__
758 browser = "open";
759#else
760 browser = "xdg-open";
761#endif
762 }
763 /* Fork once to start a child process that we wait on. This
764 * child process forks again and immediately exits. The
765 * grandchild process continues in the background. The purpose
766 * of this strange two-step is to avoid zombie processes. See
767 * bug 695701 for an explanation. */
768 pid = fork();
769 if (pid == 0)
770 {
771 if (fork() == 0)
772 {
773 execlp(browser, browser, buf, (char*)0);
774 fprintf(stderr, "cannot exec '%s'\n", browser);
775 }
776 exit(0);
777 }
778 waitpid(pid, NULL, 0);
779}
780
781int winquery(pdfapp_t *app, const char *query)
782{
783 return QUERY_NO;
784}
785
786int wingetcertpath(char *buf, int len)
787{
788 return 0;
789}
790
791static void onkey(int c, int modifiers)
792{
793 advance_scheduled = 0;
794
795 if (justcopied)
796 {
797 justcopied = 0;
798 winrepaint(&gapp);
799 }
800
801 if (!gapp.issearching && c == 'P')
802 {
803 struct timeval now;
804 struct timeval tmo;
805 tmo.tv_sec = 2;
806 tmo.tv_usec = 0;
807 gettimeofday(&now, NULL);
808 timeradd(&now, &tmo, &tmo_at);
809 showingpage = 1;
810 winrepaint(&gapp);
811 return;
812 }
813
814 pdfapp_onkey(&gapp, c, modifiers);
815
816 if (gapp.issearching)
817 {
818 showingpage = 0;
819 showingmessage = 0;
820 }
821}
822
823static void onmouse(int x, int y, int btn, int modifiers, int state)
824{
825 if (state != 0)
826 advance_scheduled = 0;
827
828 if (state != 0 && justcopied)
829 {
830 justcopied = 0;
831 winrepaint(&gapp);
832 }
833
834 pdfapp_onmouse(&gapp, x, y, btn, modifiers, state);
835}
836
837static void signal_handler(int signal)
838{
839 if (signal == SIGHUP)
840 reloading = 1;
841}
842
843static void usage(const char *argv0)
844{
845 fprintf(stderr, "usage: %s [options] file.pdf [page]\n", argv0);
846 fprintf(stderr, "\t-p -\tpassword\n");
847 fprintf(stderr, "\t-r -\tresolution\n");
848 fprintf(stderr, "\t-A -\tset anti-aliasing quality in bits (0=off, 8=best)\n");
849 fprintf(stderr, "\t-C -\tRRGGBB (tint color in hexadecimal syntax)\n");
850 fprintf(stderr, "\t-W -\tpage width for EPUB layout\n");
851 fprintf(stderr, "\t-H -\tpage height for EPUB layout\n");
852 fprintf(stderr, "\t-I -\tinvert colors\n");
853 fprintf(stderr, "\t-S -\tfont size for EPUB layout\n");
854 fprintf(stderr, "\t-U -\tuser style sheet for EPUB layout\n");
855 fprintf(stderr, "\t-X\tdisable document styles for EPUB layout\n");
856 exit(1);
857}
858
859int main(int argc, char **argv)
860{
861 int c;
862 int len;
863 char buf[128];
864 KeySym keysym;
865 int oldx = 0;
866 int oldy = 0;
867 int resolution = -1;
868 int pageno = 1;
869 fd_set fds;
870 int width = -1;
871 int height = -1;
872 fz_context *ctx;
873 struct timeval now;
874 struct timeval *timeout;
875 struct timeval tmo_advance_delay;
876 int kbps = 0;
877
878 ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT);
879 if (!ctx)
880 {
881 fprintf(stderr, "cannot initialise context\n");
882 exit(1);
883 }
884
885 pdfapp_init(ctx, &gapp);
886
887 while ((c = fz_getopt(argc, argv, "Ip:r:A:C:W:H:S:U:Xb:")) != -1)
888 {
889 switch (c)
890 {
891 case 'C':
892 c = strtol(fz_optarg, NULL, 16);
893 gapp.tint = 1;
894 gapp.tint_white = c;
895 break;
896 case 'p': password = fz_optarg; break;
897 case 'r': resolution = atoi(fz_optarg); break;
898 case 'I': gapp.invert = 1; break;
899 case 'A': fz_set_aa_level(ctx, atoi(fz_optarg)); break;
900 case 'W': gapp.layout_w = fz_atof(fz_optarg); break;
901 case 'H': gapp.layout_h = fz_atof(fz_optarg); break;
902 case 'S': gapp.layout_em = fz_atof(fz_optarg); break;
903 case 'U': gapp.layout_css = fz_optarg; break;
904 case 'X': gapp.layout_use_doc_css = 0; break;
905 case 'b': kbps = fz_atoi(fz_optarg); break;
906 default: usage(argv[0]);
907 }
908 }
909
910 if (argc - fz_optind == 0)
911 usage(argv[0]);
912
913 filename = argv[fz_optind++];
914
915 if (argc - fz_optind == 1)
916 pageno = atoi(argv[fz_optind++]);
917
918 winopen();
919
920 if (resolution == -1)
921 resolution = winresolution();
922 if (resolution < MINRES)
923 resolution = MINRES;
924 if (resolution > MAXRES)
925 resolution = MAXRES;
926
927 gapp.transitions_enabled = 1;
928 gapp.scrw = DisplayWidth(xdpy, xscr);
929 gapp.scrh = DisplayHeight(xdpy, xscr);
930 gapp.default_resolution = resolution;
931 gapp.resolution = resolution;
932 gapp.pageno = pageno;
933
934 tmo_at.tv_sec = 0;
935 tmo_at.tv_usec = 0;
936 timeout = NULL;
937
938 if (kbps)
939 pdfapp_open_progressive(&gapp, filename, 0, kbps);
940 else
941 pdfapp_open(&gapp, filename, 0);
942
943 FD_ZERO(&fds);
944
945 signal(SIGHUP, signal_handler);
946
947 while (!closing)
948 {
949 while (!closing && XPending(xdpy) && !transition_dirty)
950 {
951 XNextEvent(xdpy, &xevt);
952
953 switch (xevt.type)
954 {
955 case Expose:
956 dirty = 1;
957 break;
958
959 case ConfigureNotify:
960 if (gapp.image)
961 {
962 if (xevt.xconfigure.width != reqw ||
963 xevt.xconfigure.height != reqh)
964 gapp.shrinkwrap = 0;
965 }
966 width = xevt.xconfigure.width;
967 height = xevt.xconfigure.height;
968
969 break;
970
971 case KeyPress:
972 len = XLookupString(&xevt.xkey, buf, sizeof buf, &keysym, NULL);
973
974 if (!gapp.issearching)
975 switch (keysym)
976 {
977 case XK_Escape:
978 len = 1; buf[0] = '\033';
979 break;
980
981 case XK_Up:
982 case XK_KP_Up:
983 len = 1; buf[0] = 'k';
984 break;
985 case XK_Down:
986 case XK_KP_Down:
987 len = 1; buf[0] = 'j';
988 break;
989
990 case XK_Left:
991 case XK_KP_Left:
992 len = 1; buf[0] = 'h';
993 break;
994 case XK_Right:
995 case XK_KP_Right:
996 len = 1; buf[0] = 'l';
997 break;
998
999 case XK_Page_Up:
1000 case XK_KP_Page_Up:
1001 case XF86XK_Back:
1002 len = 1; buf[0] = ',';
1003 break;
1004 case XK_Page_Down:
1005 case XK_KP_Page_Down:
1006 case XF86XK_Forward:
1007 len = 1; buf[0] = '.';
1008 break;
1009 }
1010 if (xevt.xkey.state & ControlMask && keysym == XK_c)
1011 docopy(&gapp, XA_CLIPBOARD);
1012 else if (len)
1013 onkey(buf[0], xevt.xkey.state);
1014
1015 onmouse(oldx, oldy, 0, 0, 0);
1016
1017 break;
1018
1019 case MotionNotify:
1020 oldx = xevt.xmotion.x;
1021 oldy = xevt.xmotion.y;
1022 onmouse(xevt.xmotion.x, xevt.xmotion.y, 0, xevt.xmotion.state, 0);
1023 break;
1024
1025 case ButtonPress:
1026 onmouse(xevt.xbutton.x, xevt.xbutton.y, xevt.xbutton.button, xevt.xbutton.state, 1);
1027 break;
1028
1029 case ButtonRelease:
1030 copytime = xevt.xbutton.time;
1031 onmouse(xevt.xbutton.x, xevt.xbutton.y, xevt.xbutton.button, xevt.xbutton.state, -1);
1032 break;
1033
1034 case SelectionRequest:
1035 onselreq(xevt.xselectionrequest.requestor,
1036 xevt.xselectionrequest.selection,
1037 xevt.xselectionrequest.target,
1038 xevt.xselectionrequest.property,
1039 xevt.xselectionrequest.time);
1040 break;
1041
1042 case ClientMessage:
1043 if (xevt.xclient.message_type == WM_RELOAD_PAGE)
1044 pdfapp_reloadpage(&gapp);
1045 else if (xevt.xclient.format == 32 && xevt.xclient.data.l[0] == WM_DELETE_WINDOW)
1046 closing = 1;
1047 break;
1048 }
1049 }
1050
1051 if (closing)
1052 continue;
1053
1054 if (width != -1 || height != -1)
1055 {
1056 pdfapp_onresize(&gapp, width, height);
1057 width = -1;
1058 height = -1;
1059 }
1060
1061 if (dirty || dirtysearch)
1062 {
1063 if (dirty)
1064 winblit(&gapp);
1065 else if (dirtysearch)
1066 winblitstatusbar(&gapp);
1067 dirty = 0;
1068 transition_dirty = 0;
1069 dirtysearch = 0;
1070 pdfapp_postblit(&gapp);
1071 }
1072
1073 if (!showingpage && !showingmessage && (tmo_at.tv_sec || tmo_at.tv_usec))
1074 {
1075 tmo_at.tv_sec = 0;
1076 tmo_at.tv_usec = 0;
1077 timeout = NULL;
1078 }
1079
1080 if (XPending(xdpy) || transition_dirty)
1081 continue;
1082
1083 timeout = NULL;
1084
1085 if (tmo_at.tv_sec || tmo_at.tv_usec)
1086 {
1087 gettimeofday(&now, NULL);
1088 timersub(&tmo_at, &now, &tmo);
1089 if (tmo.tv_sec <= 0)
1090 {
1091 tmo_at.tv_sec = 0;
1092 tmo_at.tv_usec = 0;
1093 timeout = NULL;
1094 showingpage = 0;
1095 showingmessage = 0;
1096 winrepaint(&gapp);
1097 }
1098 else
1099 timeout = &tmo;
1100 }
1101
1102 if (advance_scheduled)
1103 {
1104 gettimeofday(&now, NULL);
1105 timersub(&tmo_advance, &now, &tmo_advance_delay);
1106 if (tmo_advance_delay.tv_sec <= 0)
1107 {
1108 /* Too late already */
1109 onkey(' ', 0);
1110 onmouse(oldx, oldy, 0, 0, 0);
1111 advance_scheduled = 0;
1112 }
1113 else if (timeout == NULL)
1114 {
1115 timeout = &tmo_advance_delay;
1116 }
1117 else
1118 {
1119 struct timeval tmp;
1120 timersub(&tmo_advance_delay, timeout, &tmp);
1121 if (tmp.tv_sec < 0)
1122 {
1123 timeout = &tmo_advance_delay;
1124 }
1125 }
1126 }
1127
1128 FD_SET(x11fd, &fds);
1129 if (select(x11fd + 1, &fds, NULL, NULL, timeout) < 0)
1130 {
1131 if (reloading)
1132 {
1133 pdfapp_reloadfile(&gapp);
1134 reloading = 0;
1135 }
1136 }
1137 if (!FD_ISSET(x11fd, &fds))
1138 {
1139 if (timeout == &tmo_advance_delay)
1140 {
1141 onkey(' ', 0);
1142 onmouse(oldx, oldy, 0, 0, 0);
1143 advance_scheduled = 0;
1144 }
1145 else
1146 {
1147 tmo_at.tv_sec = 0;
1148 tmo_at.tv_usec = 0;
1149 timeout = NULL;
1150 showingpage = 0;
1151 showingmessage = 0;
1152 winrepaint(&gapp);
1153 }
1154 }
1155 }
1156
1157 cleanup(&gapp);
1158
1159 return 0;
1160}
1161