26#ifdef HAVE_SYS_IOCTL_H
30#ifdef HAVE_SYS_SELECT_H
31#include <sys/select.h>
33#ifdef HAVE_SYS_INOTIFY_H
34#include <sys/inotify.h>
48#ifdef CAIRO_HAS_XLIB_SURFACE
50#include <X11/extensions/Xrender.h>
51#include <cairo-xlib.h>
53typedef struct window_xlib_s {
61 Atom wm_delete_window_atom;
64static void handle_configure_notify(
GVJ_t *job, XConfigureEvent *cev) {
71 assert(cev->width >= 0 &&
"Xlib returned an event with negative width");
72 assert(cev->height >= 0 &&
"Xlib returned an event with negative height");
74 job->
zoom *= 1 + fmin(((
double)cev->width - job->
width) / job->
width,
76 if ((
unsigned)cev->width > job->
width || (
unsigned)cev->height > job->
height)
78 job->
width = (unsigned)cev->width;
79 job->
height = (unsigned)cev->height;
83static void handle_expose(
GVJ_t *job, XExposeEvent *eev) {
87 assert(eev->width >= 0 &&
88 "Xlib returned an expose event with negative width");
89 assert(eev->height >= 0 &&
90 "Xlib returned an expose event with negative height");
91 XCopyArea(eev->display, window->pix, eev->window, window->gc, eev->x, eev->y,
92 (
unsigned)eev->width, (
unsigned)eev->height, eev->x, eev->y);
95static void handle_client_message(
GVJ_t *job, XClientMessageEvent *cmev) {
99 if (cmev->format == 32 &&
100 (Atom)cmev->data.l[0] == window->wm_delete_window_atom)
104static bool handle_keypress(
GVJ_t *job, XKeyEvent *kev) {
105 const KeyCode *
const keycodes = job->
keycodes;
106 for (
size_t i = 0; i < job->
numkeys; i++) {
107 if (kev->keycode == keycodes[i])
113static Visual *find_argb_visual(Display *dpy,
int scr) {
115 XVisualInfo
template;
118 XRenderPictFormat *
format;
121 template.screen = scr;
123 template.class = TrueColor;
125 XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask,
130 for (i = 0; i < nvi; i++) {
131 format = XRenderFindVisualFormat(dpy, xvi[i].visual);
132 if (
format->type == PictTypeDirect &&
format->direct.alphaMask) {
133 visual = xvi[i].visual;
142static void browser_show(
GVJ_t *job) {
143 char *exec_argv[3] = {BROWSER,
NULL,
NULL};
150 fprintf(stderr,
"fork failed: %s\n", strerror(errno));
151 }
else if (pid == 0) {
152 execvp(exec_argv[0], exec_argv);
153 fprintf(stderr,
"error starting %s: %s\n", exec_argv[0], strerror(errno));
157static int handle_xlib_events(
GVJ_t *firstjob, Display *dpy) {
164 while (XPending(dpy)) {
165 XNextEvent(dpy, &xev);
167 for (job = firstjob; job; job = job->
next_active) {
169 if (xev.xany.window == window->win) {
170 switch (xev.xany.type) {
172 pointer.
x = xev.xbutton.x;
173 pointer.
y = xev.xbutton.y;
174 assert(xev.xbutton.button <= (
unsigned)INT_MAX &&
175 "Xlib returned invalid button event");
181 pointer.
x = xev.xbutton.x;
182 pointer.
y = xev.xbutton.y;
188 pointer.
x = xev.xbutton.x;
189 pointer.
y = xev.xbutton.y;
190 assert(xev.xbutton.button <= (
unsigned)INT_MAX &&
191 "Xlib returned invalid button event");
194 xev.xbutton.button == 1)
199 if (handle_keypress(job, &xev.xkey))
203 case ConfigureNotify:
204 handle_configure_notify(job, &xev.xconfigure);
208 handle_expose(job, &xev.xexpose);
212 handle_client_message(job, &xev.xclient);
225static void update_display(
GVJ_t *job, Display *dpy) {
227 cairo_surface_t *surface;
232 assert(job->
width <= (
unsigned)INT_MAX &&
"out of range width");
233 assert(job->
height <= (
unsigned)INT_MAX &&
"out of range height");
236 XFreePixmap(dpy, window->pix);
237 assert(window->depth >= 0 &&
"Xlib returned invalid window depth");
238 window->pix = XCreatePixmap(dpy, window->win, job->
width, job->
height,
239 (
unsigned)window->depth);
244 XFillRectangle(dpy, window->pix, window->gc, 0, 0, job->
width, job->
height);
245 surface = cairo_xlib_surface_create(dpy, window->pix, window->visual,
247 job->
context = cairo_create(surface);
250 cairo_surface_destroy(surface);
251 XCopyArea(dpy, window->pix, window->win, window->gc, 0, 0, job->
width,
257static void init_window(
GVJ_t *job, Display *dpy,
int scr) {
259 const char *base =
"";
261 XSetWindowAttributes attributes;
263 XSizeHints *normalhints;
264 XClassHint *classhint;
265 uint64_t attributemask = 0;
269 window =
malloc(
sizeof(window_t));
270 if (window ==
NULL) {
271 fprintf(stderr,
"Failed to malloc window_t\n");
279 zoom_to_fit = fmin((
double)w / job->
width, (
double)h / job->
height);
280 if (zoom_to_fit < 1.0)
281 job->
zoom *= zoom_to_fit;
290 if (argb && (window->visual = find_argb_visual(dpy, scr))) {
292 XCreateColormap(dpy, RootWindow(dpy, scr), window->visual, AllocNone);
293 attributes.override_redirect = False;
294 attributes.background_pixel = 0;
295 attributes.border_pixel = 0;
296 attributes.colormap = window->cmap;
298 (CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWColormap);
301 window->cmap = DefaultColormap(dpy, scr);
302 window->visual = DefaultVisual(dpy, scr);
303 attributes.background_pixel = WhitePixel(dpy, scr);
304 attributes.border_pixel = BlackPixel(dpy, scr);
305 attributemask = (CWBackPixel | CWBorderPixel);
306 window->depth = DefaultDepth(dpy, scr);
309 window->win = XCreateWindow(dpy, RootWindow(dpy, scr), 0, 0, job->
width,
310 job->
height, 0, window->depth, InputOutput,
311 window->visual, attributemask, &attributes);
316 normalhints = XAllocSizeHints();
317 normalhints->flags = 0;
320 assert(job->
width <= (
unsigned)INT_MAX &&
"out of range width");
321 normalhints->width = (int)job->
width;
322 assert(job->
height <= (
unsigned)INT_MAX &&
"out of range height");
323 normalhints->height = (int)job->
height;
325 classhint = XAllocClassHint();
326 classhint->res_name =
"graphviz";
327 classhint->res_class =
"Graphviz";
329 wmhints = XAllocWMHints();
330 wmhints->flags = InputHint;
331 wmhints->input = True;
333 Xutf8SetWMProperties(dpy, window->win,
agxbuse(&name), base, 0, 0,
334 normalhints, wmhints, classhint);
340 assert(window->depth >= 0 &&
"Xlib returned invalid window depth");
341 window->pix = XCreatePixmap(dpy, window->win, job->
width, job->
height,
342 (
unsigned)window->depth);
346 gcv.foreground = WhitePixel(dpy, scr);
347 window->gc = XCreateGC(dpy, window->pix, GCForeground, &gcv);
348 update_display(job, dpy);
351 (ButtonPressMask | ButtonReleaseMask | PointerMotionMask | KeyPressMask |
352 StructureNotifyMask | ExposureMask);
353 XSelectInput(dpy, window->win, (
long)window->event_mask);
354 window->wm_delete_window_atom = XInternAtom(dpy,
"WM_DELETE_WINDOW", False);
355 XSetWMProtocols(dpy, window->win, &window->wm_delete_window_atom, 1);
356 XMapWindow(dpy, window->win);
359static int handle_stdin_events(
GVJ_t *job) {
368#ifdef HAVE_SYS_INOTIFY_H
369static int handle_file_events(
GVJ_t *job,
int inotify_fd) {
370 int avail, ret,
len, rc = 0;
372 struct inotify_event *event;
374 ret = ioctl(inotify_fd, FIONREAD, &avail);
376 fprintf(stderr,
"ioctl() failed\n");
381 assert(avail > 0 &&
"invalid value from FIONREAD");
382 void *buf =
malloc((
size_t)avail);
384 fprintf(stderr,
"out of memory (could not allocate %d bytes)\n", avail);
387 len = (int)
read(inotify_fd, buf, (
size_t)avail);
389 fprintf(stderr,
"avail = %d, len = %d\n", avail,
len);
395 event = (
struct inotify_event *)bf;
396 if (event->mask == IN_MODIFY) {
402 if (strcmp(event->name, p) == 0) {
407 size_t ln = event->len +
sizeof(
struct inotify_event);
408 assert(ln <= (
size_t)
len);
414 fprintf(stderr,
"length miscalculation, len = %d\n",
len);
422static void xlib_initialize(
GVJ_t *firstjob) {
425 const char *display_name =
NULL;
428 Display *dpy = XOpenDisplay(display_name);
430 fprintf(stderr,
"Failed to open XLIB display: %s\n", XDisplayName(
NULL));
433 scr = DefaultScreen(dpy);
439 fprintf(stderr,
"Failed to malloc %" PRISIZE_T "*KeyCode\n",
444 for (
size_t i = 0; i < firstjob->
numkeys; i++) {
446 if (keysym == NoSymbol)
447 fprintf(stderr,
"ERROR: No keysym for \"%s\"\n",
450 keycodes[i] = XKeysymToKeycode(dpy, keysym);
455 DisplayWidth(dpy, scr) * 25.4 / DisplayWidthMM(dpy, scr);
457 DisplayHeight(dpy, scr) * 25.4 / DisplayHeightMM(dpy, scr);
463static void xlib_finalize(
GVJ_t *firstjob) {
465 Display *dpy = firstjob->
display;
466 int scr = firstjob->
screen;
467 KeyCode *keycodes = firstjob->
keycodes;
468 int numfds, stdin_fd = 0, xlib_fd, ret, events;
470 bool watching_stdin_p =
false;
471#ifdef HAVE_SYS_INOTIFY_H
474 bool watching_file_p =
false;
475 char *p, *cwd =
NULL;
477#ifdef HAVE_INOTIFY_INIT1
479 inotify_fd = inotify_init1(IN_CLOSEXEC);
481 inotify_fd = inotify_init1(IN_CLOEXEC);
484 inotify_fd = inotify_init();
485 if (inotify_fd >= 0) {
486 const int flags = fcntl(inotify_fd, F_GETFD);
487 if (fcntl(inotify_fd, F_SETFD,
flags | FD_CLOEXEC) < 0) {
488 fprintf(stderr,
"setting FD_CLOEXEC failed\n");
493 if (inotify_fd < 0) {
494 fprintf(stderr,
"inotify_init() failed\n");
504 numfds = xlib_fd = XConnectionNumber(dpy);
508#ifdef HAVE_SYS_INOTIFY_H
509 watching_file_p =
true;
513 cwd = getcwd(
NULL, 0);
520 p = strrchr(dirstr,
'/');
523 wd = inotify_add_watch(inotify_fd, dirstr, IN_MODIFY);
526 numfds =
imax(inotify_fd, numfds);
530 watching_stdin_p =
true;
531#ifdef F_DUPFD_CLOEXEC
532 stdin_fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 0);
534 stdin_fd = fcntl(STDIN_FILENO, F_DUPFD, 0);
535 (void)fcntl(stdin_fd, F_SETFD, fcntl(stdin_fd, F_GETFD) | FD_CLOEXEC);
537 numfds =
imax(stdin_fd, numfds);
541 init_window(job, dpy, scr);
548#ifdef HAVE_SYS_INOTIFY_H
549 if (watching_file_p) {
550 if (FD_ISSET(inotify_fd, &rfds)) {
551 ret = handle_file_events(firstjob, inotify_fd);
556 FD_SET(inotify_fd, &rfds);
560 if (watching_stdin_p) {
561 if (FD_ISSET(stdin_fd, &rfds)) {
562 ret = handle_stdin_events(firstjob);
564 watching_stdin_p =
false;
565 FD_CLR(stdin_fd, &rfds);
569 if (watching_stdin_p)
570 FD_SET(stdin_fd, &rfds);
573 ret = handle_xlib_events(firstjob, dpy);
577 FD_SET(xlib_fd, &rfds);
581 update_display(job, dpy);
587 fprintf(stderr,
"select() failed\n");
592#ifdef HAVE_SYS_INOTIFY_H
594 ret = inotify_rm_watch(inotify_fd, wd);
618#ifdef CAIRO_HAS_XLIB_SURFACE
619 {0,
"xlib:cairo", 0, &device_engine_xlib, &device_features_xlib},
620 {0,
"x11:cairo", 0, &device_engine_xlib, &device_features_xlib},
static void agxbfree(agxbuf *xb)
free any malloced resources
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
static WUR 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