x11_main.c 26 KB

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