Graphviz  2.41.20171026.1811
gvdevice_xlib.c
Go to the documentation of this file.
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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
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 
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 
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 = {
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 
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 };
pointf device_dpi
Definition: gvcjob.h:298
#define MAX(a, b)
Definition: agerror.c:17
void(* button_press)(GVJ_t *job, int button, pointf pointer)
Definition: gvcjob.h:158
#define MIN(a, b)
Definition: arith.h:35
void * context
Definition: gvcjob.h:304
void * display
Definition: gvcjob.h:301
void(* refresh)(GVJ_t *job)
Definition: gvcjob.h:157
Definition: geom.h:28
char * input_filename
Definition: gvcjob.h:280
boolean has_grown
Definition: gvcjob.h:345
unsigned int width
Definition: gvcjob.h:336
void * keycodes
Definition: gvcjob.h:371
Definition: gvcjob.h:271
void(* button_release)(GVJ_t *job, int button, pointf pointer)
Definition: gvcjob.h:159
char * selected_href
Definition: gvcjob.h:360
int graph_index
Definition: gvcjob.h:281
int screen
Definition: gvcjob.h:302
void * window
Definition: gvcjob.h:366
double y
Definition: geom.h:28
GVJ_t * next_active
Definition: gvcjob.h:274
#define GVDEVICE_EVENTS
Definition: gvcjob.h:91
if(aagss+aagstacksize-1<=aagssp)
Definition: grammar.c:1332
unsigned char button
Definition: gvcjob.h:351
gvevent_key_binding_t * keybindings
Definition: gvcjob.h:369
const char * layout_type
Definition: gvcjob.h:283
#define NULL
Definition: logic.h:39
boolean device_sets_dpi
Definition: gvcjob.h:299
double x
Definition: geom.h:28
boolean external_context
Definition: gvcjob.h:305
void(* read)(GVJ_t *job, const char *filename, const char *layout)
Definition: gvcjob.h:163
gvevent_key_callback_t callback
Definition: gvcjob.h:172
boolean needs_refresh
Definition: gvcjob.h:345
for(;;)
Definition: grammar.c:1846
int numkeys
Definition: gvcjob.h:370
boolean fit_mode
Definition: gvcjob.h:345
void(* motion)(GVJ_t *job, pointf pointer)
Definition: gvcjob.h:160
gvplugin_installed_t gvdevice_types_xlib[]
double zoom
Definition: gvcjob.h:327
unsigned int height
Definition: gvcjob.h:337
#define FALSE
Definition: cgraph.h:35
gvdevice_callbacks_t * callbacks
Definition: gvcjob.h:297
#define GVDEVICE_DOES_TRUECOLOR
Definition: gvcjob.h:92
Agraph_t * read(FILE *f)
Definition: gv.cpp:73
#define TRUE
Definition: cgraph.h:38