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