123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include "config.h"
- #include <assert.h>
- #include <fcntl.h>
- #include <inttypes.h>
- #include <limits.h>
- #include <math.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #ifdef HAVE_SYS_TIME_H
- #include <sys/time.h>
- #endif
- #ifdef HAVE_SYS_IOCTL_H
- #include <sys/ioctl.h>
- #endif
- #include <sys/types.h>
- #ifdef HAVE_SYS_SELECT_H
- #include <sys/select.h>
- #endif
- #ifdef HAVE_SYS_INOTIFY_H
- #include <sys/inotify.h>
- #ifdef __sun
- #include <sys/filio.h>
- #endif
- #endif
- #include <errno.h>
- #include <cgraph/gv_math.h>
- #include <gvc/gvplugin_device.h>
- #include <util/agxbuf.h>
- #include <util/exit.h>
- #include <util/prisize_t.h>
- #include <cairo.h>
- #ifdef CAIRO_HAS_XLIB_SURFACE
- #include <X11/Xutil.h>
- #include <X11/extensions/Xrender.h>
- #include <cairo-xlib.h>
- typedef struct window_xlib_s {
- Window win;
- uint64_t event_mask;
- Pixmap pix;
- GC gc;
- Visual *visual;
- Colormap cmap;
- int depth;
- Atom wm_delete_window_atom;
- } window_t;
- static void handle_configure_notify(GVJ_t *job, XConfigureEvent *cev) {
- /*FIXME - should allow for margins */
- /* - similar zoom_to_fit code exists in: */
- /* plugin/gtk/callbacks.c */
- /* plugin/xlib/gvdevice_xlib.c */
- /* lib/gvc/gvevent.c */
- assert(cev->width >= 0 && "Xlib returned an event with negative width");
- assert(cev->height >= 0 && "Xlib returned an event with negative height");
- job->zoom *= 1 + fmin(((double)cev->width - job->width) / job->width,
- ((double)cev->height - job->height) / job->height);
- if ((unsigned)cev->width > job->width || (unsigned)cev->height > job->height)
- job->has_grown = true;
- job->width = (unsigned)cev->width;
- job->height = (unsigned)cev->height;
- job->needs_refresh = true;
- }
- static void handle_expose(GVJ_t *job, XExposeEvent *eev) {
- window_t *window;
- window = job->window;
- assert(eev->width >= 0 &&
- "Xlib returned an expose event with negative width");
- assert(eev->height >= 0 &&
- "Xlib returned an expose event with negative height");
- XCopyArea(eev->display, window->pix, eev->window, window->gc, eev->x, eev->y,
- (unsigned)eev->width, (unsigned)eev->height, eev->x, eev->y);
- }
- static void handle_client_message(GVJ_t *job, XClientMessageEvent *cmev) {
- window_t *window;
- window = job->window;
- if (cmev->format == 32 &&
- (Atom)cmev->data.l[0] == window->wm_delete_window_atom)
- graphviz_exit(0);
- }
- static bool handle_keypress(GVJ_t *job, XKeyEvent *kev) {
- const KeyCode *const keycodes = job->keycodes;
- for (size_t i = 0; i < job->numkeys; i++) {
- if (kev->keycode == keycodes[i])
- return job->keybindings[i].callback(job) != 0;
- }
- return false;
- }
- static Visual *find_argb_visual(Display *dpy, int scr) {
- XVisualInfo *xvi;
- XVisualInfo template;
- int nvi;
- int i;
- XRenderPictFormat *format;
- Visual *visual;
- template.screen = scr;
- template.depth = 32;
- template.class = TrueColor;
- xvi =
- XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask,
- &template, &nvi);
- if (!xvi)
- return 0;
- visual = 0;
- for (i = 0; i < nvi; i++) {
- format = XRenderFindVisualFormat(dpy, xvi[i].visual);
- if (format->type == PictTypeDirect && format->direct.alphaMask) {
- visual = xvi[i].visual;
- break;
- }
- }
- XFree(xvi);
- return visual;
- }
- static void browser_show(GVJ_t *job) {
- char *exec_argv[3] = {BROWSER, NULL, NULL};
- pid_t pid;
- exec_argv[1] = job->selected_href;
- pid = fork();
- if (pid == -1) {
- fprintf(stderr, "fork failed: %s\n", strerror(errno));
- } else if (pid == 0) {
- execvp(exec_argv[0], exec_argv);
- fprintf(stderr, "error starting %s: %s\n", exec_argv[0], strerror(errno));
- }
- }
- static int handle_xlib_events(GVJ_t *firstjob, Display *dpy) {
- GVJ_t *job;
- window_t *window;
- XEvent xev;
- pointf pointer;
- int rc = 0;
- while (XPending(dpy)) {
- XNextEvent(dpy, &xev);
- for (job = firstjob; job; job = job->next_active) {
- window = job->window;
- if (xev.xany.window == window->win) {
- switch (xev.xany.type) {
- case ButtonPress:
- pointer.x = xev.xbutton.x;
- pointer.y = xev.xbutton.y;
- assert(xev.xbutton.button <= (unsigned)INT_MAX &&
- "Xlib returned invalid button event");
- job->callbacks->button_press(job, (int)xev.xbutton.button, pointer);
- rc++;
- break;
- case MotionNotify:
- if (job->button) { /* only interested while a button is pressed */
- pointer.x = xev.xbutton.x;
- pointer.y = xev.xbutton.y;
- job->callbacks->motion(job, pointer);
- rc++;
- }
- break;
- case ButtonRelease:
- pointer.x = xev.xbutton.x;
- pointer.y = xev.xbutton.y;
- assert(xev.xbutton.button <= (unsigned)INT_MAX &&
- "Xlib returned invalid button event");
- job->callbacks->button_release(job, (int)xev.xbutton.button, pointer);
- if (job->selected_href && job->selected_href[0] &&
- xev.xbutton.button == 1)
- browser_show(job);
- rc++;
- break;
- case KeyPress:
- if (handle_keypress(job, &xev.xkey))
- return -1; /* exit code */
- rc++;
- break;
- case ConfigureNotify:
- handle_configure_notify(job, &xev.xconfigure);
- rc++;
- break;
- case Expose:
- handle_expose(job, &xev.xexpose);
- rc++;
- break;
- case ClientMessage:
- handle_client_message(job, &xev.xclient);
- rc++;
- break;
- default:
- break;
- }
- break;
- }
- }
- }
- return rc;
- }
- static void update_display(GVJ_t *job, Display *dpy) {
- window_t *window;
- cairo_surface_t *surface;
- window = job->window;
- // window geometry is set to fixed values
- assert(job->width <= (unsigned)INT_MAX && "out of range width");
- assert(job->height <= (unsigned)INT_MAX && "out of range height");
- if (job->has_grown) {
- XFreePixmap(dpy, window->pix);
- assert(window->depth >= 0 && "Xlib returned invalid window depth");
- window->pix = XCreatePixmap(dpy, window->win, job->width, job->height,
- (unsigned)window->depth);
- job->has_grown = false;
- job->needs_refresh = true;
- }
- if (job->needs_refresh) {
- XFillRectangle(dpy, window->pix, window->gc, 0, 0, job->width, job->height);
- surface = cairo_xlib_surface_create(dpy, window->pix, window->visual,
- (int)job->width, (int)job->height);
- job->context = cairo_create(surface);
- job->external_context = true;
- job->callbacks->refresh(job);
- cairo_surface_destroy(surface);
- XCopyArea(dpy, window->pix, window->win, window->gc, 0, 0, job->width,
- job->height, 0, 0);
- job->needs_refresh = false;
- }
- }
- static void init_window(GVJ_t *job, Display *dpy, int scr) {
- int argb = 0;
- const char *base = "";
- XGCValues gcv;
- XSetWindowAttributes attributes;
- XWMHints *wmhints;
- XSizeHints *normalhints;
- XClassHint *classhint;
- uint64_t attributemask = 0;
- window_t *window;
- double zoom_to_fit;
- window = malloc(sizeof(window_t));
- if (window == NULL) {
- fprintf(stderr, "Failed to malloc window_t\n");
- return;
- }
- unsigned w =
- 480; /* FIXME - w,h should be set by a --geometry commandline option */
- unsigned h = 325;
- zoom_to_fit = fmin((double)w / job->width, (double)h / job->height);
- if (zoom_to_fit < 1.0) /* don't make bigger */
- job->zoom *= zoom_to_fit;
- job->width = w; /* use window geometry */
- job->height = h;
- job->window = window;
- job->fit_mode = false;
- job->needs_refresh = true;
- if (argb && (window->visual = find_argb_visual(dpy, scr))) {
- window->cmap =
- XCreateColormap(dpy, RootWindow(dpy, scr), window->visual, AllocNone);
- attributes.override_redirect = False;
- attributes.background_pixel = 0;
- attributes.border_pixel = 0;
- attributes.colormap = window->cmap;
- attributemask =
- (CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWColormap);
- window->depth = 32;
- } else {
- window->cmap = DefaultColormap(dpy, scr);
- window->visual = DefaultVisual(dpy, scr);
- attributes.background_pixel = WhitePixel(dpy, scr);
- attributes.border_pixel = BlackPixel(dpy, scr);
- attributemask = (CWBackPixel | CWBorderPixel);
- window->depth = DefaultDepth(dpy, scr);
- }
- window->win = XCreateWindow(dpy, RootWindow(dpy, scr), 0, 0, job->width,
- job->height, 0, window->depth, InputOutput,
- window->visual, attributemask, &attributes);
- agxbuf name = {0};
- agxbprint(&name, "graphviz: %s", base);
- normalhints = XAllocSizeHints();
- normalhints->flags = 0;
- normalhints->x = 0;
- normalhints->y = 0;
- assert(job->width <= (unsigned)INT_MAX && "out of range width");
- normalhints->width = (int)job->width;
- assert(job->height <= (unsigned)INT_MAX && "out of range height");
- normalhints->height = (int)job->height;
- classhint = XAllocClassHint();
- classhint->res_name = "graphviz";
- classhint->res_class = "Graphviz";
- wmhints = XAllocWMHints();
- wmhints->flags = InputHint;
- wmhints->input = True;
- Xutf8SetWMProperties(dpy, window->win, agxbuse(&name), base, 0, 0,
- normalhints, wmhints, classhint);
- XFree(wmhints);
- XFree(classhint);
- XFree(normalhints);
- agxbfree(&name);
- assert(window->depth >= 0 && "Xlib returned invalid window depth");
- window->pix = XCreatePixmap(dpy, window->win, job->width, job->height,
- (unsigned)window->depth);
- if (argb)
- gcv.foreground = 0;
- else
- gcv.foreground = WhitePixel(dpy, scr);
- window->gc = XCreateGC(dpy, window->pix, GCForeground, &gcv);
- update_display(job, dpy);
- window->event_mask =
- (ButtonPressMask | ButtonReleaseMask | PointerMotionMask | KeyPressMask |
- StructureNotifyMask | ExposureMask);
- XSelectInput(dpy, window->win, (long)window->event_mask);
- window->wm_delete_window_atom = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
- XSetWMProtocols(dpy, window->win, &window->wm_delete_window_atom, 1);
- XMapWindow(dpy, window->win);
- }
- static int handle_stdin_events(GVJ_t *job) {
- if (feof(stdin))
- return -1;
- job->callbacks->read(job, job->input_filename, job->layout_type);
- return 1;
- }
- #ifdef HAVE_SYS_INOTIFY_H
- static int handle_file_events(GVJ_t *job, int inotify_fd) {
- int avail, ret, len, rc = 0;
- char *bf, *p;
- struct inotify_event *event;
- ret = ioctl(inotify_fd, FIONREAD, &avail);
- if (ret < 0) {
- fprintf(stderr, "ioctl() failed\n");
- return -1;
- }
- if (avail) {
- assert(avail > 0 && "invalid value from FIONREAD");
- void *buf = malloc((size_t)avail);
- if (!buf) {
- fprintf(stderr, "out of memory (could not allocate %d bytes)\n", avail);
- return -1;
- }
- len = (int)read(inotify_fd, buf, (size_t)avail);
- if (len != avail) {
- fprintf(stderr, "avail = %d, len = %d\n", avail, len);
- free(buf);
- return -1;
- }
- bf = buf;
- while (len > 0) {
- event = (struct inotify_event *)bf;
- if (event->mask == IN_MODIFY) {
- p = strrchr(job->input_filename, '/');
- if (p)
- p++;
- else
- p = job->input_filename;
- if (strcmp(event->name, p) == 0) {
- job->callbacks->read(job, job->input_filename, job->layout_type);
- rc++;
- }
- }
- size_t ln = event->len + sizeof(struct inotify_event);
- assert(ln <= (size_t)len);
- bf += ln;
- len -= (int)ln;
- }
- free(buf);
- if (len != 0) {
- fprintf(stderr, "length miscalculation, len = %d\n", len);
- return -1;
- }
- }
- return rc;
- }
- #endif
- static void xlib_initialize(GVJ_t *firstjob) {
- KeySym keysym;
- KeyCode *keycodes;
- const char *display_name = NULL;
- int scr;
- Display *dpy = XOpenDisplay(display_name);
- if (dpy == NULL) {
- fprintf(stderr, "Failed to open XLIB display: %s\n", XDisplayName(NULL));
- return;
- }
- scr = DefaultScreen(dpy);
- firstjob->screen = scr;
- keycodes = malloc(firstjob->numkeys * sizeof(KeyCode));
- if (firstjob->numkeys > 0 && keycodes == NULL) {
- fprintf(stderr, "Failed to malloc %" PRISIZE_T "*KeyCode\n",
- firstjob->numkeys);
- XCloseDisplay(dpy);
- return;
- }
- for (size_t i = 0; i < firstjob->numkeys; i++) {
- keysym = XStringToKeysym(firstjob->keybindings[i].keystring);
- if (keysym == NoSymbol)
- fprintf(stderr, "ERROR: No keysym for \"%s\"\n",
- firstjob->keybindings[i].keystring);
- else
- keycodes[i] = XKeysymToKeycode(dpy, keysym);
- }
- firstjob->keycodes = keycodes;
- firstjob->device_dpi.x =
- DisplayWidth(dpy, scr) * 25.4 / DisplayWidthMM(dpy, scr);
- firstjob->device_dpi.y =
- DisplayHeight(dpy, scr) * 25.4 / DisplayHeightMM(dpy, scr);
- firstjob->device_sets_dpi = true;
- firstjob->display = dpy;
- }
- static void xlib_finalize(GVJ_t *firstjob) {
- GVJ_t *job;
- Display *dpy = firstjob->display;
- int scr = firstjob->screen;
- KeyCode *keycodes = firstjob->keycodes;
- int numfds, stdin_fd = 0, xlib_fd, ret, events;
- fd_set rfds;
- bool watching_stdin_p = false;
- #ifdef HAVE_SYS_INOTIFY_H
- int wd = 0;
- int inotify_fd = 0;
- bool watching_file_p = false;
- char *p, *cwd = NULL;
- #ifdef HAVE_INOTIFY_INIT1
- #ifdef IN_CLOSEXEC
- inotify_fd = inotify_init1(IN_CLOSEXEC);
- #else
- inotify_fd = inotify_init1(IN_CLOEXEC);
- #endif
- #else
- inotify_fd = inotify_init();
- if (inotify_fd >= 0) {
- const int flags = fcntl(inotify_fd, F_GETFD);
- if (fcntl(inotify_fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
- fprintf(stderr, "setting FD_CLOEXEC failed\n");
- return;
- }
- }
- #endif
- if (inotify_fd < 0) {
- fprintf(stderr, "inotify_init() failed\n");
- return;
- }
- #endif
- /* skip if initialization previously failed */
- if (dpy == NULL) {
- return;
- }
- numfds = xlib_fd = XConnectionNumber(dpy);
- if (firstjob->input_filename) {
- if (firstjob->graph_index == 0) {
- #ifdef HAVE_SYS_INOTIFY_H
- watching_file_p = true;
- agxbuf dir = {0};
- if (firstjob->input_filename[0] != '/') {
- cwd = getcwd(NULL, 0);
- agxbprint(&dir, "%s/%s", cwd, firstjob->input_filename);
- free(cwd);
- } else {
- agxbput(&dir, firstjob->input_filename);
- }
- char *dirstr = agxbuse(&dir);
- p = strrchr(dirstr, '/');
- *p = '\0';
- wd = inotify_add_watch(inotify_fd, dirstr, IN_MODIFY);
- agxbfree(&dir);
- numfds = imax(inotify_fd, numfds);
- #endif
- }
- } else {
- watching_stdin_p = true;
- #ifdef F_DUPFD_CLOEXEC
- stdin_fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 0);
- #else
- stdin_fd = fcntl(STDIN_FILENO, F_DUPFD, 0);
- (void)fcntl(stdin_fd, F_SETFD, fcntl(stdin_fd, F_GETFD) | FD_CLOEXEC);
- #endif
- numfds = imax(stdin_fd, numfds);
- }
- for (job = firstjob; job; job = job->next_active)
- init_window(job, dpy, scr);
- /* This is the event loop */
- FD_ZERO(&rfds);
- while (1) {
- events = 0;
- #ifdef HAVE_SYS_INOTIFY_H
- if (watching_file_p) {
- if (FD_ISSET(inotify_fd, &rfds)) {
- ret = handle_file_events(firstjob, inotify_fd);
- if (ret < 0)
- break;
- events += ret;
- }
- FD_SET(inotify_fd, &rfds);
- }
- #endif
- if (watching_stdin_p) {
- if (FD_ISSET(stdin_fd, &rfds)) {
- ret = handle_stdin_events(firstjob);
- if (ret < 0) {
- watching_stdin_p = false;
- FD_CLR(stdin_fd, &rfds);
- }
- events += ret;
- }
- if (watching_stdin_p)
- FD_SET(stdin_fd, &rfds);
- }
- ret = handle_xlib_events(firstjob, dpy);
- if (ret < 0)
- break;
- events += ret;
- FD_SET(xlib_fd, &rfds);
- if (events) {
- for (job = firstjob; job; job = job->next_active)
- update_display(job, dpy);
- XFlush(dpy);
- }
- ret = select(numfds + 1, &rfds, NULL, NULL, NULL);
- if (ret < 0) {
- fprintf(stderr, "select() failed\n");
- break;
- }
- }
- #ifdef HAVE_SYS_INOTIFY_H
- if (watching_file_p)
- ret = inotify_rm_watch(inotify_fd, wd);
- #endif
- XCloseDisplay(dpy);
- firstjob->display = NULL;
- free(keycodes);
- firstjob->keycodes = NULL;
- }
- static gvdevice_features_t device_features_xlib = {
- GVDEVICE_DOES_TRUECOLOR | GVDEVICE_EVENTS, /* flags */
- {0., 0.}, /* default margin - points */
- {0., 0.}, /* default page width, height - points */
- {96., 96.}, /* dpi */
- };
- static gvdevice_engine_t device_engine_xlib = {
- xlib_initialize,
- NULL, /* xlib_format */
- xlib_finalize,
- };
- #endif
- gvplugin_installed_t gvdevice_types_xlib[] = {
- #ifdef CAIRO_HAS_XLIB_SURFACE
- {0, "xlib:cairo", 0, &device_engine_xlib, &device_features_xlib},
- {0, "x11:cairo", 0, &device_engine_xlib, &device_features_xlib},
- #endif
- {0, NULL, 0, NULL, NULL}};
|