1 /* $Id$ $Revision$ */
2 /* vim:set shiftwidth=4 ts=8: */
3
4 /*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: See CVS logs. Details at http://www.graphviz.org/
12 *************************************************************************/
13
14 #include "config.h"
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <inttypes.h>
20 #include <unistd.h>
21 #ifdef HAVE_SYS_TIME_H
22 #include <sys/time.h>
23 #endif
24 #ifdef HAVE_SYS_IOCTL_H
25 #include <sys/ioctl.h>
26 #endif
27 #ifdef HAVE_SYS_TYPES_H
28 #include <sys/types.h>
29 #endif
30 #ifdef HAVE_SYS_SELECT_H
31 #include <sys/select.h>
32 #endif
33 #ifdef HAVE_SYS_INOTIFY_H
34 #include <sys/inotify.h>
35 #endif
36 #include <errno.h>
37 #ifdef HAVE_FCNTL_H
38 #include <fcntl.h>
39 #endif
40
41 #if 0
42 #include <poll.h>
43 #endif
44
45 #include "gvplugin_device.h"
46
47 #include <cairo.h>
48 #ifdef CAIRO_HAS_XLIB_SURFACE
49 #include <cairo-xlib.h>
50 #include <X11/Xutil.h>
51 #include <X11/extensions/Xrender.h>
52
53 typedef struct window_xlib_s {
54 Window win;
55 uint64_t event_mask;
56 Pixmap pix;
57 GC gc;
58 Visual *visual;
59 Colormap cmap;
60 int depth;
61 Atom wm_delete_window_atom;
62 } window_t;
63
handle_configure_notify(GVJ_t * job,XConfigureEvent * cev)64 static void handle_configure_notify(GVJ_t * job, XConfigureEvent * cev)
65 {
66 /*FIXME - should allow for margins */
67 /* - similar zoom_to_fit code exists in: */
68 /* plugin/gtk/callbacks.c */
69 /* plugin/xlib/gvdevice_xlib.c */
70 /* lib/gvc/gvevent.c */
71
72 job->zoom *= 1 + MIN(
73 ((double) cev->width - (double) job->width) / (double) job->width,
74 ((double) cev->height - (double) job->height) / (double) job->height);
75 if (cev->width > job->width || cev->height > job->height)
76 job->has_grown = 1;
77 job->width = cev->width;
78 job->height = cev->height;
79 job->needs_refresh = 1;
80 }
81
handle_expose(GVJ_t * job,XExposeEvent * eev)82 static void handle_expose(GVJ_t * job, XExposeEvent * eev)
83 {
84 window_t *window;
85
86 window = (window_t *)job->window;
87 XCopyArea(eev->display, window->pix, eev->window, window->gc,
88 eev->x, eev->y, eev->width, eev->height, eev->x, eev->y);
89 }
90
handle_client_message(GVJ_t * job,XClientMessageEvent * cmev)91 static void handle_client_message(GVJ_t * job, XClientMessageEvent * cmev)
92 {
93 window_t *window;
94
95 window = (window_t *)job->window;
96 if (cmev->format == 32
97 && (Atom) cmev->data.l[0] == window->wm_delete_window_atom)
98 exit(0);
99 }
100
handle_keypress(GVJ_t * job,XKeyEvent * kev)101 static boolean handle_keypress(GVJ_t *job, XKeyEvent *kev)
102 {
103
104 int i;
105 KeyCode *keycodes;
106
107 keycodes = (KeyCode *)job->keycodes;
108 for (i=0; i < job->numkeys; i++) {
109 if (kev->keycode == keycodes[i])
110 return (job->keybindings[i].callback)(job);
111 }
112 return FALSE;
113 }
114
find_argb_visual(Display * dpy,int scr)115 static Visual *find_argb_visual(Display * dpy, int scr)
116 {
117 XVisualInfo *xvi;
118 XVisualInfo template;
119 int nvi;
120 int i;
121 XRenderPictFormat *format;
122 Visual *visual;
123
124 template.screen = scr;
125 template.depth = 32;
126 template.class = TrueColor;
127 xvi = XGetVisualInfo(dpy,
128 VisualScreenMask |
129 VisualDepthMask |
130 VisualClassMask, &template, &nvi);
131 if (!xvi)
132 return 0;
133 visual = 0;
134 for (i = 0; i < nvi; i++) {
135 format = XRenderFindVisualFormat(dpy, xvi[i].visual);
136 if (format->type == PictTypeDirect && format->direct.alphaMask) {
137 visual = xvi[i].visual;
138 break;
139 }
140 }
141
142 XFree(xvi);
143 return visual;
144 }
145
browser_show(GVJ_t * job)146 static void browser_show(GVJ_t *job)
147 {
148 #if defined HAVE_SYS_TYPES_H && defined HAVE_UNISTD_H
149 char *exec_argv[3] = {BROWSER, NULL, NULL};
150 pid_t pid;
151 int err;
152
153 exec_argv[1] = job->selected_href;
154
155 pid = fork();
156 if (pid == -1) {
157 fprintf(stderr,"fork failed: %s\n", strerror(errno));
158 }
159 else if (pid == 0) {
160 err = execvp(exec_argv[0], exec_argv);
161 fprintf(stderr,"error starting %s: %s\n", exec_argv[0], strerror(errno));
162 }
163 #else
164 fprintf(stdout,"browser_show: %s\n", job->selected_href);
165 #endif
166 }
167
handle_xlib_events(GVJ_t * firstjob,Display * dpy)168 static int handle_xlib_events (GVJ_t *firstjob, Display *dpy)
169 {
170 GVJ_t *job;
171 window_t *window;
172 XEvent xev;
173 pointf pointer;
174 int rc = 0;
175
176 while (XPending(dpy)) {
177 XNextEvent(dpy, &xev);
178
179 for (job = firstjob; job; job = job->next_active) {
180 window = (window_t *)job->window;
181 if (xev.xany.window == window->win) {
182 switch (xev.xany.type) {
183 case ButtonPress:
184 pointer.x = (double)xev.xbutton.x;
185 pointer.y = (double)xev.xbutton.y;
186 (job->callbacks->button_press)(job, xev.xbutton.button, pointer);
187 rc++;
188 break;
189 case MotionNotify:
190 if (job->button) { /* only interested while a button is pressed */
191 pointer.x = (double)xev.xbutton.x;
192 pointer.y = (double)xev.xbutton.y;
193 (job->callbacks->motion)(job, pointer);
194 rc++;
195 }
196 break;
197 case ButtonRelease:
198 pointer.x = (double)xev.xbutton.x;
199 pointer.y = (double)xev.xbutton.y;
200 (job->callbacks->button_release)(job, xev.xbutton.button, pointer);
201 if (job->selected_href && job->selected_href[0] && xev.xbutton.button == 1)
202 browser_show(job);
203 rc++;
204 break;
205 case KeyPress:
206 if (handle_keypress(job, &xev.xkey))
207 return -1; /* exit code */
208 rc++;
209 break;
210 case ConfigureNotify:
211 handle_configure_notify(job, &xev.xconfigure);
212 rc++;
213 break;
214 case Expose:
215 handle_expose(job, &xev.xexpose);
216 rc++;
217 break;
218 case ClientMessage:
219 handle_client_message(job, &xev.xclient);
220 rc++;
221 break;
222 }
223 break;
224 }
225 }
226 }
227 return rc;
228 }
229
update_display(GVJ_t * job,Display * dpy)230 static void update_display(GVJ_t *job, Display *dpy)
231 {
232 window_t *window;
233 cairo_surface_t *surface;
234
235 window = (window_t *)job->window;
236
237 if (job->has_grown) {
238 XFreePixmap(dpy, window->pix);
239 window->pix = XCreatePixmap(dpy, window->win,
240 job->width, job->height, window->depth);
241 job->has_grown = 0;
242 job->needs_refresh = 1;
243 }
244 if (job->needs_refresh) {
245 XFillRectangle(dpy, window->pix, window->gc, 0, 0,
246 job->width, job->height);
247 surface = cairo_xlib_surface_create(dpy,
248 window->pix, window->visual,
249 job->width, job->height);
250 job->context = (void *)cairo_create(surface);
251 job->external_context = TRUE;
252 (job->callbacks->refresh)(job);
253 cairo_surface_destroy(surface);
254 XCopyArea(dpy, window->pix, window->win, window->gc,
255 0, 0, job->width, job->height, 0, 0);
256 job->needs_refresh = 0;
257 }
258 }
259
init_window(GVJ_t * job,Display * dpy,int scr)260 static void init_window(GVJ_t *job, Display *dpy, int scr)
261 {
262 int argb = 0;
263 const char *base = "";
264 XGCValues gcv;
265 XSetWindowAttributes attributes;
266 XWMHints *wmhints;
267 XSizeHints *normalhints;
268 XClassHint *classhint;
269 uint64_t attributemask = 0;
270 char *name;
271 window_t *window;
272 int w, h;
273 double zoom_to_fit;
274
275 window = (window_t *)malloc(sizeof(window_t));
276 if (window == NULL) {
277 fprintf(stderr, "Failed to malloc window_t\n");
278 return;
279 }
280
281 w = 480; /* FIXME - w,h should be set by a --geometry commandline option */
282 h = 325;
283
284 zoom_to_fit = MIN((double) w / (double) job->width,
285 (double) h / (double) job->height);
286 if (zoom_to_fit < 1.0) /* don't make bigger */
287 job->zoom *= zoom_to_fit;
288
289 job->width = w; /* use window geometry */
290 job->height = h;
291
292 job->window = (void *)window;
293 job->fit_mode = 0;
294 job->needs_refresh = 1;
295
296 if (argb && (window->visual = find_argb_visual(dpy, scr))) {
297 window->cmap = XCreateColormap(dpy, RootWindow(dpy, scr),
298 window->visual, AllocNone);
299 attributes.override_redirect = False;
300 attributes.background_pixel = 0;
301 attributes.border_pixel = 0;
302 attributes.colormap = window->cmap;
303 attributemask = ( CWBackPixel
304 | CWBorderPixel
305 | CWOverrideRedirect
306 | CWColormap );
307 window->depth = 32;
308 } else {
309 window->cmap = DefaultColormap(dpy, scr);
310 window->visual = DefaultVisual(dpy, scr);
311 attributes.background_pixel = WhitePixel(dpy, scr);
312 attributes.border_pixel = BlackPixel(dpy, scr);
313 attributemask = (CWBackPixel | CWBorderPixel);
314 window->depth = DefaultDepth(dpy, scr);
315 }
316
317 window->win = XCreateWindow(dpy, RootWindow(dpy, scr),
318 0, 0, job->width, job->height, 0, window->depth,
319 InputOutput, window->visual,
320 attributemask, &attributes);
321
322 name = malloc(strlen("graphviz: ") + strlen(base) + 1);
323 strcpy(name, "graphviz: ");
324 strcat(name, base);
325
326 normalhints = XAllocSizeHints();
327 normalhints->flags = 0;
328 normalhints->x = 0;
329 normalhints->y = 0;
330 normalhints->width = job->width;
331 normalhints->height = job->height;
332
333 classhint = XAllocClassHint();
334 classhint->res_name = "graphviz";
335 classhint->res_class = "Graphviz";
336
337 wmhints = XAllocWMHints();
338 wmhints->flags = InputHint;
339 wmhints->input = True;
340
341 Xutf8SetWMProperties(dpy, window->win, name, base, 0, 0,
342 normalhints, wmhints, classhint);
343 XFree(wmhints);
344 XFree(classhint);
345 XFree(normalhints);
346 free(name);
347
348 window->pix = XCreatePixmap(dpy, window->win, job->width, job->height,
349 window->depth);
350 if (argb)
351 gcv.foreground = 0;
352 else
353 gcv.foreground = WhitePixel(dpy, scr);
354 window->gc = XCreateGC(dpy, window->pix, GCForeground, &gcv);
355 update_display(job, dpy);
356
357 window->event_mask = (
358 ButtonPressMask
359 | ButtonReleaseMask
360 | PointerMotionMask
361 | KeyPressMask
362 | StructureNotifyMask
363 | ExposureMask);
364 XSelectInput(dpy, window->win, window->event_mask);
365 window->wm_delete_window_atom =
366 XInternAtom(dpy, "WM_DELETE_WINDOW", False);
367 XSetWMProtocols(dpy, window->win, &window->wm_delete_window_atom, 1);
368 XMapWindow(dpy, window->win);
369 }
370
handle_stdin_events(GVJ_t * job,int stdin_fd)371 static int handle_stdin_events(GVJ_t *job, int stdin_fd)
372 {
373 int rc=0;
374
375 if (feof(stdin))
376 return -1;
377 (job->callbacks->read)(job, job->input_filename, job->layout_type);
378
379 rc++;
380 return rc;
381 }
382
383 #ifdef HAVE_SYS_INOTIFY_H
handle_file_events(GVJ_t * job,int inotify_fd)384 static int handle_file_events(GVJ_t *job, int inotify_fd)
385 {
386 int avail, ret, len, ln, rc = 0;
387 static char *buf;
388 char *bf, *p;
389 struct inotify_event *event;
390
391 ret = ioctl(inotify_fd, FIONREAD, &avail);
392 if (ret < 0) {
393 fprintf(stderr,"ioctl() failed\n");
394 return -1;;
395 }
396
397 if (avail) {
398 buf = realloc(buf, avail);
399 if (!buf) {
400 fprintf(stderr,"problem with realloc(%d)\n", avail);
401 return -1;
402 }
403 len = read(inotify_fd, buf, avail);
404 if (len != avail) {
405 fprintf(stderr,"avail = %u, len = %u\n", avail, len);
406 return -1;
407 }
408 bf = buf;
409 while (len > 0) {
410 event = (struct inotify_event *)bf;
411 switch (event->mask) {
412 case IN_MODIFY:
413 p = strrchr(job->input_filename, '/');
414 if (p)
415 p++;
416 else
417 p = job->input_filename;
418 if (strcmp((char*)(&(event->name)), p) == 0) {
419 (job->callbacks->read)(job, job->input_filename, job->layout_type);
420 rc++;
421 }
422 break;
423
424 case IN_ACCESS:
425 case IN_ATTRIB:
426 case IN_CLOSE_WRITE:
427 case IN_CLOSE_NOWRITE:
428 case IN_OPEN:
429 case IN_MOVED_FROM:
430 case IN_MOVED_TO:
431 case IN_CREATE:
432 case IN_DELETE:
433 case IN_DELETE_SELF:
434 case IN_MOVE_SELF:
435 case IN_UNMOUNT:
436 case IN_Q_OVERFLOW:
437 case IN_IGNORED:
438 case IN_ISDIR:
439 case IN_ONESHOT:
440 break;
441 }
442 ln = event->len + sizeof(struct inotify_event);
443 bf += ln;
444 len -= ln;
445 }
446 if (len != 0) {
447 fprintf(stderr,"length miscalculation, len = %d\n", len);
448 return -1;
449 }
450 }
451 return rc;
452 }
453 #endif
454
xlib_initialize(GVJ_t * firstjob)455 static void xlib_initialize(GVJ_t *firstjob)
456 {
457 Display *dpy;
458 KeySym keysym;
459 KeyCode *keycodes;
460 const char *display_name = NULL;
461 int i, scr;
462
463 dpy = XOpenDisplay(display_name);
464 if (dpy == NULL) {
465 fprintf(stderr, "Failed to open XLIB display: %s\n",
466 XDisplayName(NULL));
467 return;
468 }
469 scr = DefaultScreen(dpy);
470
471 firstjob->display = (void*)dpy;
472 firstjob->screen = scr;
473
474 keycodes = (KeyCode *)malloc(firstjob->numkeys * sizeof(KeyCode));
475 if (keycodes == NULL) {
476 fprintf(stderr, "Failed to malloc %d*KeyCode\n", firstjob->numkeys);
477 return;
478 }
479 for (i = 0; i < firstjob->numkeys; i++) {
480 keysym = XStringToKeysym(firstjob->keybindings[i].keystring);
481 if (keysym == NoSymbol)
482 fprintf(stderr, "ERROR: No keysym for \"%s\"\n",
483 firstjob->keybindings[i].keystring);
484 else
485 keycodes[i] = XKeysymToKeycode(dpy, keysym);
486 }
487 firstjob->keycodes = (void*)keycodes;
488
489 firstjob->device_dpi.x = DisplayWidth(dpy, scr) * 25.4 / DisplayWidthMM(dpy, scr);
490 firstjob->device_dpi.y = DisplayHeight(dpy, scr) * 25.4 / DisplayHeightMM(dpy, scr);
491 firstjob->device_sets_dpi = TRUE;
492 }
493
xlib_finalize(GVJ_t * firstjob)494 static void xlib_finalize(GVJ_t *firstjob)
495 {
496 GVJ_t *job;
497 Display *dpy = (Display *)(firstjob->display);
498 int scr = firstjob->screen;
499 KeyCode *keycodes= firstjob->keycodes;
500 int numfds, stdin_fd=0, xlib_fd, ret, events;
501 fd_set rfds;
502 boolean watching_stdin_p = FALSE;
503 #ifdef HAVE_SYS_INOTIFY_H
504 int wd=0;
505 int inotify_fd=0;
506 boolean watching_file_p = FALSE;
507 static char *dir;
508 char *p, *cwd = NULL;
509
510 inotify_fd = inotify_init();
511 if (inotify_fd < 0) {
512 fprintf(stderr,"inotify_init() failed\n");
513 return;
514 }
515 #endif
516
517 numfds = xlib_fd = XConnectionNumber(dpy);
518
519 if (firstjob->input_filename) {
520 if (firstjob->graph_index == 0) {
521 #ifdef HAVE_SYS_INOTIFY_H
522 watching_file_p = TRUE;
523
524 if (firstjob->input_filename[0] != '/') {
525 cwd = getcwd(NULL, 0);
526 dir = realloc(dir, strlen(cwd) + 1 + strlen(firstjob->input_filename) + 1);
527 strcpy(dir, cwd);
528 strcat(dir, "/");
529 strcat(dir, firstjob->input_filename);
530 free(cwd);
531 }
532 else {
533 dir = realloc(dir, strlen(firstjob->input_filename) + 1);
534 strcpy(dir, firstjob->input_filename);
535 }
536 p = strrchr(dir,'/');
537 *p = '\0';
538
539 wd = inotify_add_watch(inotify_fd, dir, IN_MODIFY );
540
541 numfds = MAX(inotify_fd, numfds);
542 #endif
543 }
544 }
545 else {
546 watching_stdin_p = TRUE;
547 stdin_fd = fcntl(STDIN_FILENO, F_DUPFD, 0);
548 numfds = MAX(stdin_fd, numfds);
549 }
550
551 for (job = firstjob; job; job = job->next_active)
552 init_window(job, dpy, scr);
553
554 /* This is the event loop */
555 FD_ZERO(&rfds);
556 while (1) {
557 events = 0;
558
559 #ifdef HAVE_SYS_INOTIFY_H
560 if (watching_file_p) {
561 if (FD_ISSET(inotify_fd, &rfds)) {
562 ret = handle_file_events(firstjob, inotify_fd);
563 if (ret < 0)
564 break;
565 events += ret;
566 }
567 FD_SET(inotify_fd, &rfds);
568 }
569 #endif
570
571 if (watching_stdin_p) {
572 if (FD_ISSET(stdin_fd, &rfds)) {
573 ret = handle_stdin_events(firstjob, stdin_fd);
574 if (ret < 0) {
575 watching_stdin_p = FALSE;
576 FD_CLR(stdin_fd, &rfds);
577 }
578 events += ret;
579 }
580 if (watching_stdin_p)
581 FD_SET(stdin_fd, &rfds);
582 }
583
584 ret = handle_xlib_events(firstjob, dpy);
585 if (ret < 0)
586 break;
587 events += ret;
588 FD_SET(xlib_fd, &rfds);
589
590 if (events) {
591 for (job = firstjob; job; job = job->next_active)
592 update_display(job, dpy);
593 XFlush(dpy);
594 }
595
596 ret = select(numfds+1, &rfds, NULL, NULL, NULL);
597 if (ret < 0) {
598 fprintf(stderr,"select() failed\n");
599 break;
600 }
601 }
602
603 #ifdef HAVE_SYS_INOTIFY_H
604 if (watching_file_p)
605 ret = inotify_rm_watch(inotify_fd, wd);
606 #endif
607
608 XCloseDisplay(dpy);
609 free(keycodes);
610 firstjob->keycodes = NULL;
611 }
612
613 static gvdevice_features_t device_features_xlib = {
614 GVDEVICE_DOES_TRUECOLOR
615 | GVDEVICE_EVENTS, /* flags */
616 {0.,0.}, /* default margin - points */
617 {0.,0.}, /* default page width, height - points */
618 {96.,96.}, /* dpi */
619 };
620
621 static gvdevice_engine_t device_engine_xlib = {
622 xlib_initialize,
623 NULL, /* xlib_format */
624 xlib_finalize,
625 };
626 #endif
627
628 gvplugin_installed_t gvdevice_types_xlib[] = {
629 #ifdef CAIRO_HAS_XLIB_SURFACE
630 {0, "xlib:cairo", 0, &device_engine_xlib, &device_features_xlib},
631 {0, "x11:cairo", 0, &device_engine_xlib, &device_features_xlib},
632 #endif
633 {0, NULL, 0, NULL, NULL}
634 };
635