comparison mupdf-source/platform/x11/x11_main.c @ 2:b50eed0cc0ef upstream

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