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 void browser_show(
GVJ_t *job) {
114 char *exec_argv[3] = {BROWSER,
NULL,
NULL};
121 fprintf(stderr,
"fork failed: %s\n", strerror(errno));
122 }
else if (pid == 0) {
123 execvp(exec_argv[0], exec_argv);
124 fprintf(stderr,
"error starting %s: %s\n", exec_argv[0], strerror(errno));
128static int handle_xlib_events(
GVJ_t *firstjob, Display *dpy) {
135 while (XPending(dpy)) {
136 XNextEvent(dpy, &xev);
138 for (job = firstjob; job; job = job->
next_active) {
140 if (xev.xany.window == window->win) {
141 switch (xev.xany.type) {
143 pointer.
x = xev.xbutton.x;
144 pointer.
y = xev.xbutton.y;
145 assert(xev.xbutton.button <= INT_MAX &&
146 "Xlib returned invalid button event");
152 pointer.
x = xev.xbutton.x;
153 pointer.
y = xev.xbutton.y;
159 pointer.
x = xev.xbutton.x;
160 pointer.
y = xev.xbutton.y;
161 assert(xev.xbutton.button <= INT_MAX &&
162 "Xlib returned invalid button event");
165 xev.xbutton.button == 1)
170 if (handle_keypress(job, &xev.xkey))
174 case ConfigureNotify:
175 handle_configure_notify(job, &xev.xconfigure);
179 handle_expose(job, &xev.xexpose);
183 handle_client_message(job, &xev.xclient);
196static void update_display(
GVJ_t *job, Display *dpy) {
198 cairo_surface_t *surface;
203 assert(job->
width <= INT_MAX &&
"out of range width");
204 assert(job->
height <= INT_MAX &&
"out of range height");
207 XFreePixmap(dpy, window->pix);
208 assert(window->depth >= 0 &&
"Xlib returned invalid window depth");
209 window->pix = XCreatePixmap(dpy, window->win, job->
width, job->
height,
210 (
unsigned)window->depth);
215 XFillRectangle(dpy, window->pix, window->gc, 0, 0, job->
width, job->
height);
216 surface = cairo_xlib_surface_create(dpy, window->pix, window->visual,
218 job->
context = cairo_create(surface);
221 cairo_surface_destroy(surface);
222 XCopyArea(dpy, window->pix, window->win, window->gc, 0, 0, job->
width,
228static void init_window(
GVJ_t *job, Display *dpy,
int scr) {
229 const char *base =
"";
231 XSetWindowAttributes attributes;
233 XSizeHints *normalhints;
234 XClassHint *classhint;
235 uint64_t attributemask = 0;
239 window =
malloc(
sizeof(window_t));
240 if (window ==
NULL) {
241 fprintf(stderr,
"Failed to malloc window_t\n");
249 zoom_to_fit = fmin((
double)w / job->
width, (
double)h / job->
height);
250 if (zoom_to_fit < 1.0)
251 job->
zoom *= zoom_to_fit;
260 window->cmap = DefaultColormap(dpy, scr);
261 window->visual = DefaultVisual(dpy, scr);
262 attributes.background_pixel = WhitePixel(dpy, scr);
263 attributes.border_pixel = BlackPixel(dpy, scr);
264 attributemask = (CWBackPixel | CWBorderPixel);
265 window->depth = DefaultDepth(dpy, scr);
267 window->win = XCreateWindow(dpy, RootWindow(dpy, scr), 0, 0, job->
width,
268 job->
height, 0, window->depth, InputOutput,
269 window->visual, attributemask, &attributes);
274 normalhints = XAllocSizeHints();
275 normalhints->flags = 0;
278 assert(job->
width <= INT_MAX &&
"out of range width");
279 normalhints->width = (int)job->
width;
280 assert(job->
height <= INT_MAX &&
"out of range height");
281 normalhints->height = (int)job->
height;
283 classhint = XAllocClassHint();
284 classhint->res_name =
"graphviz";
285 classhint->res_class =
"Graphviz";
287 wmhints = XAllocWMHints();
288 wmhints->flags = InputHint;
289 wmhints->input = True;
291 Xutf8SetWMProperties(dpy, window->win,
agxbuse(&name), base, 0, 0,
292 normalhints, wmhints, classhint);
298 assert(window->depth >= 0 &&
"Xlib returned invalid window depth");
299 window->pix = XCreatePixmap(dpy, window->win, job->
width, job->
height,
300 (
unsigned)window->depth);
301 gcv.foreground = WhitePixel(dpy, scr);
302 window->gc = XCreateGC(dpy, window->pix, GCForeground, &gcv);
303 update_display(job, dpy);
306 (ButtonPressMask | ButtonReleaseMask | PointerMotionMask | KeyPressMask |
307 StructureNotifyMask | ExposureMask);
308 XSelectInput(dpy, window->win, (
long)window->event_mask);
309 window->wm_delete_window_atom = XInternAtom(dpy,
"WM_DELETE_WINDOW", False);
310 XSetWMProtocols(dpy, window->win, &window->wm_delete_window_atom, 1);
311 XMapWindow(dpy, window->win);
314static int handle_stdin_events(
GVJ_t *job) {
323#ifdef HAVE_SYS_INOTIFY_H
324static int handle_file_events(
GVJ_t *job,
int inotify_fd) {
325 int avail, ret,
len, rc = 0;
327 struct inotify_event *event;
329 ret = ioctl(inotify_fd, FIONREAD, &avail);
331 fprintf(stderr,
"ioctl() failed\n");
336 assert(avail > 0 &&
"invalid value from FIONREAD");
337 void *buf =
malloc((
size_t)avail);
339 fprintf(stderr,
"out of memory (could not allocate %d bytes)\n", avail);
342 len = (int)
read(inotify_fd, buf, (
size_t)avail);
344 fprintf(stderr,
"avail = %d, len = %d\n", avail,
len);
350 event = (
struct inotify_event *)bf;
351 if (event->mask == IN_MODIFY) {
357 if (strcmp(event->name, p) == 0) {
362 size_t ln =
event->len +
sizeof(
struct inotify_event);
363 assert(ln <= (
size_t)
len);
369 fprintf(stderr,
"length miscalculation, len = %d\n",
len);
377static void xlib_initialize(
GVJ_t *firstjob) {
380 const char *display_name =
NULL;
383 Display *dpy = XOpenDisplay(display_name);
385 fprintf(stderr,
"Failed to open XLIB display: %s\n", XDisplayName(
NULL));
388 scr = DefaultScreen(dpy);
394 fprintf(stderr,
"Failed to malloc %" PRISIZE_T "*KeyCode\n",
399 for (
size_t i = 0; i < firstjob->
numkeys; i++) {
401 if (keysym == NoSymbol)
402 fprintf(stderr,
"ERROR: No keysym for \"%s\"\n",
405 keycodes[i] = XKeysymToKeycode(dpy, keysym);
410 DisplayWidth(dpy, scr) * 25.4 / DisplayWidthMM(dpy, scr);
412 DisplayHeight(dpy, scr) * 25.4 / DisplayHeightMM(dpy, scr);
418static void xlib_finalize(
GVJ_t *firstjob) {
420 Display *dpy = firstjob->
display;
421 int scr = firstjob->
screen;
422 KeyCode *keycodes = firstjob->
keycodes;
423 int numfds, stdin_fd = 0, xlib_fd, ret, events;
425 bool watching_stdin_p =
false;
426#ifdef HAVE_SYS_INOTIFY_H
429 bool watching_file_p =
false;
430 char *p, *cwd =
NULL;
432#ifdef HAVE_INOTIFY_INIT1
434 inotify_fd = inotify_init1(IN_CLOSEXEC);
436 inotify_fd = inotify_init1(IN_CLOEXEC);
439 inotify_fd = inotify_init();
440 if (inotify_fd >= 0) {
441 const int flags = fcntl(inotify_fd, F_GETFD);
442 if (fcntl(inotify_fd, F_SETFD,
flags | FD_CLOEXEC) < 0) {
443 fprintf(stderr,
"setting FD_CLOEXEC failed\n");
448 if (inotify_fd < 0) {
449 fprintf(stderr,
"inotify_init() failed\n");
459 numfds = xlib_fd = XConnectionNumber(dpy);
463#ifdef HAVE_SYS_INOTIFY_H
464 watching_file_p =
true;
468 cwd = getcwd(
NULL, 0);
475 p = strrchr(dirstr,
'/');
478 wd = inotify_add_watch(inotify_fd, dirstr, IN_MODIFY);
481 numfds =
imax(inotify_fd, numfds);
485 watching_stdin_p =
true;
486#ifdef F_DUPFD_CLOEXEC
487 stdin_fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 0);
489 stdin_fd = fcntl(STDIN_FILENO, F_DUPFD, 0);
490 (void)fcntl(stdin_fd, F_SETFD, fcntl(stdin_fd, F_GETFD) | FD_CLOEXEC);
492 numfds =
imax(stdin_fd, numfds);
496 init_window(job, dpy, scr);
503#ifdef HAVE_SYS_INOTIFY_H
504 if (watching_file_p) {
505 if (FD_ISSET(inotify_fd, &rfds)) {
506 ret = handle_file_events(firstjob, inotify_fd);
511 FD_SET(inotify_fd, &rfds);
515 if (watching_stdin_p) {
516 if (FD_ISSET(stdin_fd, &rfds)) {
517 ret = handle_stdin_events(firstjob);
519 watching_stdin_p =
false;
520 FD_CLR(stdin_fd, &rfds);
524 if (watching_stdin_p)
525 FD_SET(stdin_fd, &rfds);
528 ret = handle_xlib_events(firstjob, dpy);
532 FD_SET(xlib_fd, &rfds);
536 update_display(job, dpy);
542 fprintf(stderr,
"select() failed\n");
547#ifdef HAVE_SYS_INOTIFY_H
549 (void)inotify_rm_watch(inotify_fd, wd);
573#ifdef CAIRO_HAS_XLIB_SURFACE
574 {0,
"xlib:cairo", 0, &device_engine_xlib, &device_features_xlib},
575 {0,
"x11:cairo", 0, &device_engine_xlib, &device_features_xlib},
Dynamically expanding string buffers.
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[]
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