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 | void windrawstringxor(pdfapp_t *app, int x, int y, char *s); |
69 | 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 | */ |
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 | |
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 | |
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 | |
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 | |
168 | void winprint(pdfapp_t *app) |
169 | { |
170 | fprintf(stderr, "The MuPDF library supports printing, but this application currently does not\n" ); |
171 | } |
172 | |
173 | char *winpassword(pdfapp_t *app, char *filename) |
174 | { |
175 | char *r = password; |
176 | password = NULL; |
177 | return r; |
178 | } |
179 | |
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 | |
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 | |
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 | |
291 | void winclose(pdfapp_t *app) |
292 | { |
293 | if (pdfapp_preclose(app)) |
294 | { |
295 | closing = 1; |
296 | } |
297 | } |
298 | |
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 | |
306 | int wingetsavepath(pdfapp_t *app, char *buf, int len) |
307 | { |
308 | /* FIXME: temporary dummy implementation */ |
309 | return 0; |
310 | } |
311 | |
312 | void winreplacefile(char *source, char *target) |
313 | { |
314 | rename(source, target); |
315 | } |
316 | |
317 | void 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 | |
353 | void 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 | |
375 | static int winresolution(void) |
376 | { |
377 | return DisplayWidth(xdpy, xscr) * 25.4f / |
378 | DisplayWidthMM(xdpy, xscr) + 0.5f; |
379 | } |
380 | |
381 | void 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 | |
394 | void 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 | |
406 | void winhelp(pdfapp_t *app) |
407 | { |
408 | fprintf(stderr, "%s\n%s" , pdfapp_version(app), pdfapp_usage(app)); |
409 | } |
410 | |
411 | void 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 | |
469 | void 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 | |
486 | static 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 | |
492 | static 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 | |
516 | static 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 | |
596 | void winrepaint(pdfapp_t *app) |
597 | { |
598 | dirty = 1; |
599 | if (app->in_transit) |
600 | transition_dirty = 1; |
601 | } |
602 | |
603 | void winrepaintsearch(pdfapp_t *app) |
604 | { |
605 | dirtysearch = 1; |
606 | } |
607 | |
608 | void 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 | |
620 | void 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 | |
640 | void 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 | |
646 | void 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 | |
676 | void windocopy(pdfapp_t *app) |
677 | { |
678 | docopy(app, XA_PRIMARY); |
679 | } |
680 | |
681 | void 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 | |
733 | void 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 | |
751 | void 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 | |
781 | int winquery(pdfapp_t *app, const char *query) |
782 | { |
783 | return QUERY_NO; |
784 | } |
785 | |
786 | int wingetcertpath(char *buf, int len) |
787 | { |
788 | return 0; |
789 | } |
790 | |
791 | static 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 | |
823 | static 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 | |
837 | static void signal_handler(int signal) |
838 | { |
839 | if (signal == SIGHUP) |
840 | reloading = 1; |
841 | } |
842 | |
843 | static 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 | |
859 | int 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 | |