26#ifdef HAVE_SYS_IOCTL_H
29#ifdef HAVE_SYS_TYPES_H
32#ifdef HAVE_SYS_SELECT_H
33#include <sys/select.h>
35#ifdef HAVE_SYS_INOTIFY_H
36#include <sys/inotify.h>
50#ifdef CAIRO_HAS_XLIB_SURFACE
51#include <cairo-xlib.h>
53#include <X11/extensions/Xrender.h>
55typedef struct window_xlib_s {
63 Atom wm_delete_window_atom;
66static void handle_configure_notify(
GVJ_t * job, XConfigureEvent * cev)
74 assert(cev->width >= 0 &&
"Xlib returned an event with negative width");
75 assert(cev->height >= 0 &&
"Xlib returned an event with negative height");
77 job->
zoom *= 1 + fmin(
78 ((
double)cev->width - job->
width) / job->
width,
80 if ((
unsigned)cev->width > job->
width ||
81 (
unsigned)cev->height > job->
height)
83 job->
width = (unsigned)cev->width;
84 job->
height = (unsigned)cev->height;
88static void handle_expose(
GVJ_t * job, XExposeEvent * eev)
93 assert(eev->width >= 0 &&
94 "Xlib returned an expose event with negative width");
95 assert(eev->height >= 0 &&
96 "Xlib returned an expose event with negative height");
97 XCopyArea(eev->display, window->pix, eev->window, window->gc,
98 eev->x, eev->y, (
unsigned)eev->width, (
unsigned)eev->height,
102static void handle_client_message(
GVJ_t * job, XClientMessageEvent * cmev)
107 if (cmev->format == 32
108 && (Atom) cmev->data.l[0] == window->wm_delete_window_atom)
112static bool handle_keypress(
GVJ_t *job, XKeyEvent *kev)
117 for (
size_t i = 0; i < job->
numkeys; i++) {
118 if (kev->keycode == keycodes[i])
124static Visual *find_argb_visual(Display * dpy,
int scr)
127 XVisualInfo
template;
130 XRenderPictFormat *
format;
133 template.screen = scr;
135 template.class = TrueColor;
136 xvi = XGetVisualInfo(dpy,
139 VisualClassMask, &
template, &nvi);
143 for (i = 0; i < nvi; i++) {
144 format = XRenderFindVisualFormat(dpy, xvi[i].visual);
145 if (
format->type == PictTypeDirect &&
format->direct.alphaMask) {
146 visual = xvi[i].visual;
155static void browser_show(
GVJ_t *job)
157#ifdef HAVE_SYS_TYPES_H
158 char *exec_argv[3] = {BROWSER,
NULL,
NULL};
165 fprintf(stderr,
"fork failed: %s\n", strerror(errno));
168 execvp(exec_argv[0], exec_argv);
169 fprintf(stderr,
"error starting %s: %s\n", exec_argv[0], strerror(errno));
176static int handle_xlib_events (
GVJ_t *firstjob, Display *dpy)
184 while (XPending(dpy)) {
185 XNextEvent(dpy, &xev);
187 for (job = firstjob; job; job = job->
next_active) {
189 if (xev.xany.window == window->win) {
190 switch (xev.xany.type) {
192 pointer.
x = (double)xev.xbutton.x;
193 pointer.
y = (double)xev.xbutton.y;
194 assert(xev.xbutton.button <= (
unsigned)INT_MAX &&
195 "Xlib returned invalid button event");
202 pointer.
x = (double)xev.xbutton.x;
203 pointer.
y = (double)xev.xbutton.y;
209 pointer.
x = (double)xev.xbutton.x;
210 pointer.
y = (double)xev.xbutton.y;
211 assert(xev.xbutton.button <= (
unsigned)INT_MAX &&
212 "Xlib returned invalid button event");
220 if (handle_keypress(job, &xev.xkey))
224 case ConfigureNotify:
225 handle_configure_notify(job, &xev.xconfigure);
229 handle_expose(job, &xev.xexpose);
233 handle_client_message(job, &xev.xclient);
246static void update_display(
GVJ_t *job, Display *dpy)
249 cairo_surface_t *surface;
254 assert(job->
width <= (
unsigned)INT_MAX &&
"out of range width");
255 assert(job->
height <= (
unsigned)INT_MAX &&
"out of range height");
258 XFreePixmap(dpy, window->pix);
259 assert(window->depth >= 0 &&
"Xlib returned invalid window depth");
260 window->pix = XCreatePixmap(dpy, window->win,
262 (
unsigned)window->depth);
267 XFillRectangle(dpy, window->pix, window->gc, 0, 0,
269 surface = cairo_xlib_surface_create(dpy,
270 window->pix, window->visual,
272 job->
context = cairo_create(surface);
275 cairo_surface_destroy(surface);
276 XCopyArea(dpy, window->pix, window->win, window->gc,
282static void init_window(
GVJ_t *job, Display *dpy,
int scr)
285 const char *base =
"";
287 XSetWindowAttributes attributes;
289 XSizeHints *normalhints;
290 XClassHint *classhint;
291 uint64_t attributemask = 0;
295 window =
malloc(
sizeof(window_t));
296 if (window ==
NULL) {
297 fprintf(stderr,
"Failed to malloc window_t\n");
304 zoom_to_fit = fmin((
double)w / job->
width, (
double)h / job->
height);
305 if (zoom_to_fit < 1.0)
306 job->
zoom *= zoom_to_fit;
315 if (argb && (window->visual = find_argb_visual(dpy, scr))) {
316 window->cmap = XCreateColormap(dpy, RootWindow(dpy, scr),
317 window->visual, AllocNone);
318 attributes.override_redirect = False;
319 attributes.background_pixel = 0;
320 attributes.border_pixel = 0;
321 attributes.colormap = window->cmap;
322 attributemask = ( CWBackPixel
328 window->cmap = DefaultColormap(dpy, scr);
329 window->visual = DefaultVisual(dpy, scr);
330 attributes.background_pixel = WhitePixel(dpy, scr);
331 attributes.border_pixel = BlackPixel(dpy, scr);
332 attributemask = (CWBackPixel | CWBorderPixel);
333 window->depth = DefaultDepth(dpy, scr);
336 window->win = XCreateWindow(dpy, RootWindow(dpy, scr),
338 InputOutput, window->visual,
339 attributemask, &attributes);
344 normalhints = XAllocSizeHints();
345 normalhints->flags = 0;
348 assert(job->
width <= (
unsigned)INT_MAX &&
"out of range width");
349 normalhints->width = (int)job->
width;
350 assert(job->
height <= (
unsigned)INT_MAX &&
"out of range height");
351 normalhints->height = (int)job->
height;
353 classhint = XAllocClassHint();
354 classhint->res_name =
"graphviz";
355 classhint->res_class =
"Graphviz";
357 wmhints = XAllocWMHints();
358 wmhints->flags = InputHint;
359 wmhints->input = True;
361 Xutf8SetWMProperties(dpy, window->win,
agxbuse(&name), base, 0, 0,
362 normalhints, wmhints, classhint);
368 assert(window->depth >= 0 &&
"Xlib returned invalid window depth");
369 window->pix = XCreatePixmap(dpy, window->win, job->
width, job->
height,
370 (
unsigned)window->depth);
374 gcv.foreground = WhitePixel(dpy, scr);
375 window->gc = XCreateGC(dpy, window->pix, GCForeground, &gcv);
376 update_display(job, dpy);
378 window->event_mask = (
383 | StructureNotifyMask
385 XSelectInput(dpy, window->win, (
long)window->event_mask);
386 window->wm_delete_window_atom =
387 XInternAtom(dpy,
"WM_DELETE_WINDOW", False);
388 XSetWMProtocols(dpy, window->win, &window->wm_delete_window_atom, 1);
389 XMapWindow(dpy, window->win);
392static int handle_stdin_events(
GVJ_t *job)
404#ifdef HAVE_SYS_INOTIFY_H
405static int handle_file_events(
GVJ_t *job,
int inotify_fd)
407 int avail, ret,
len, rc = 0;
409 struct inotify_event *event;
411 ret = ioctl(inotify_fd, FIONREAD, &avail);
413 fprintf(stderr,
"ioctl() failed\n");
418 assert(avail > 0 &&
"invalid value from FIONREAD");
419 void *buf =
malloc((
size_t)avail);
421 fprintf(stderr,
"out of memory (could not allocate %d bytes)\n",
425 len = (int)
read(inotify_fd, buf, (
size_t)avail);
427 fprintf(stderr,
"avail = %d, len = %d\n", avail,
len);
433 event = (
struct inotify_event *)bf;
434 if (event->mask == IN_MODIFY) {
440 if (strcmp(event->name, p) == 0) {
445 size_t ln = event->len +
sizeof(
struct inotify_event);
446 assert(ln <= (
size_t)
len);
452 fprintf(stderr,
"length miscalculation, len = %d\n",
len);
460static bool initialized;
462static void xlib_initialize(
GVJ_t *firstjob)
467 const char *display_name =
NULL;
470 dpy = XOpenDisplay(display_name);
472 fprintf(stderr,
"Failed to open XLIB display: %s\n",
476 scr = DefaultScreen(dpy);
482 if (keycodes ==
NULL) {
483 fprintf(stderr,
"Failed to malloc %" PRISIZE_T "*KeyCode\n",
487 for (
size_t i = 0; i < firstjob->
numkeys; i++) {
489 if (keysym == NoSymbol)
490 fprintf(stderr,
"ERROR: No keysym for \"%s\"\n",
493 keycodes[i] = XKeysymToKeycode(dpy, keysym);
497 firstjob->
device_dpi.
x = DisplayWidth(dpy, scr) * 25.4 / DisplayWidthMM(dpy, scr);
498 firstjob->
device_dpi.
y = DisplayHeight(dpy, scr) * 25.4 / DisplayHeightMM(dpy, scr);
504static void xlib_finalize(
GVJ_t *firstjob)
507 Display *dpy = firstjob->
display;
508 int scr = firstjob->
screen;
509 KeyCode *keycodes= firstjob->
keycodes;
510 int numfds, stdin_fd=0, xlib_fd, ret, events;
512 bool watching_stdin_p =
false;
513#ifdef HAVE_SYS_INOTIFY_H
516 bool watching_file_p =
false;
517 char *p, *cwd =
NULL;
519#ifdef HAVE_INOTIFY_INIT1
521 inotify_fd = inotify_init1(IN_CLOSEXEC);
523 inotify_fd = inotify_init1(IN_CLOEXEC);
526 inotify_fd = inotify_init();
527 if (inotify_fd >= 0) {
528 const int flags = fcntl(inotify_fd, F_GETFD);
529 if (fcntl(inotify_fd, F_SETFD,
flags | FD_CLOEXEC) < 0) {
530 fprintf(stderr,
"setting FD_CLOEXEC failed\n");
535 if (inotify_fd < 0) {
536 fprintf(stderr,
"inotify_init() failed\n");
546 numfds = xlib_fd = XConnectionNumber(dpy);
550#ifdef HAVE_SYS_INOTIFY_H
551 watching_file_p =
true;
555 cwd = getcwd(
NULL, 0);
563 p = strrchr(dirstr,
'/');
566 wd = inotify_add_watch(inotify_fd, dirstr, IN_MODIFY);
569 numfds =
imax(inotify_fd, numfds);
574 watching_stdin_p =
true;
575#ifdef F_DUPFD_CLOEXEC
576 stdin_fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 0);
578 stdin_fd = fcntl(STDIN_FILENO, F_DUPFD, 0);
579 (void)fcntl(stdin_fd, F_SETFD, fcntl(stdin_fd, F_GETFD) | FD_CLOEXEC);
581 numfds =
imax(stdin_fd, numfds);
585 init_window(job, dpy, scr);
592#ifdef HAVE_SYS_INOTIFY_H
593 if (watching_file_p) {
594 if (FD_ISSET(inotify_fd, &rfds)) {
595 ret = handle_file_events(firstjob, inotify_fd);
600 FD_SET(inotify_fd, &rfds);
604 if (watching_stdin_p) {
605 if (FD_ISSET(stdin_fd, &rfds)) {
606 ret = handle_stdin_events(firstjob);
608 watching_stdin_p =
false;
609 FD_CLR(stdin_fd, &rfds);
613 if (watching_stdin_p)
614 FD_SET(stdin_fd, &rfds);
617 ret = handle_xlib_events(firstjob, dpy);
621 FD_SET(xlib_fd, &rfds);
625 update_display(job, dpy);
631 fprintf(stderr,
"select() failed\n");
636#ifdef HAVE_SYS_INOTIFY_H
638 ret = inotify_rm_watch(inotify_fd, wd);
662#ifdef CAIRO_HAS_XLIB_SURFACE
663 {0,
"xlib:cairo", 0, &device_engine_xlib, &device_features_xlib},
664 {0,
"x11:cairo", 0, &device_engine_xlib, &device_features_xlib},
static void agxbfree(agxbuf *xb)
free any malloced resources
static size_t agxbput(agxbuf *xb, const char *s)
append string s into xb
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
static char * agxbuse(agxbuf *xb)
static NORETURN void graphviz_exit(int status)
static double len(glCompPoint p)
Arithmetic helper functions.
static int imax(int a, int b)
maximum of two integers
#define GVDEVICE_DOES_TRUECOLOR
gvplugin_installed_t gvdevice_types_xlib[]
GVIO_API const char * format
#define PRISIZE_T
PRIu64 alike for printing size_t
gvdevice_callbacks_t * callbacks
gvevent_key_binding_t * keybindings
void(* button_release)(GVJ_t *job, int button, pointf pointer)
void(* refresh)(GVJ_t *job)
void(* button_press)(GVJ_t *job, int button, pointf pointer)
void(* motion)(GVJ_t *job, pointf pointer)
void(* read)(GVJ_t *job, const char *filename, const char *layout)
gvevent_key_callback_t callback