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