#include #include #include #include #include "initcairo.h" #define ERROR(condition, msg, ...) if(condition){fprintf(stderr, msg, ##__VA_ARGS__); return 0;} static Bool alwaysTruePredicate(Display *display, XEvent *event, XPointer arg) { return true; } GraphicsInfo *initCairo(int screen, int mask, int width, int height, const char *name) { GraphicsInfo *info = malloc(sizeof(GraphicsInfo)); DimensionsInfo *dim = malloc(sizeof(DimensionsInfo)); info->dim = dim; mask |= StructureNotifyMask | ExposureMask | KeyPressMask | ButtonPressMask | PointerMotionMask; info->display = XOpenDisplay(NULL); ERROR(!info->display, "Failed to open X display\n"); Visual *visual = XDefaultVisual(info->display, screen); int depth = XDefaultDepth(info->display, screen); Window root = XRootWindow(info->display, screen); XSetWindowAttributes swa; info->cmap = XCreateColormap(info->display, root, visual, AllocNone); swa.colormap = info->cmap; swa.background_pixmap = None; swa.border_pixel = 0; swa.event_mask = mask; info->win = XCreateWindow(info->display, root, 0, 0, width, height, 0, depth, InputOutput, visual, CWBorderPixel|CWColormap|CWEventMask, &swa); ERROR(!info->win, "Failed to create window.\n"); XStoreName(info->display, info->win, name); XMapWindow(info->display, info->win); // create xlib surface info->surface = cairo_xlib_surface_create(info->display, info->win, visual, width, height); cairo_xlib_surface_set_size(info->surface, width, height); info->front_context = cairo_create(info->surface); cairo_set_antialias(info->front_context, CAIRO_ANTIALIAS_NONE); // create backbuffer surface int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); info->buffer = malloc(stride*height); info->buffer_surface = cairo_image_surface_create_for_data(info->buffer, CAIRO_FORMAT_ARGB32, width, height, stride); info->buffer_context = cairo_create(info->buffer_surface); cairo_matrix_init_identity(&info->dim->matrix); info->dim->width = width; info->dim->height = height; updateDimensions(info->dim); info->wm_delete_window = XInternAtom(info->display, "WM_DELETE_WINDOW", 0); info->wm_protocols = XInternAtom(info->display, "WM_PROTOCOLS", 0); XSetWMProtocols(info->display, info->win, &info->wm_delete_window, 1); XFlush(info->display); return info; } void destroyCairo(GraphicsInfo *info) { cairo_destroy(info->buffer_context); cairo_destroy(info->front_context); cairo_surface_destroy(info->surface); cairo_surface_destroy(info->buffer_surface); free(info->buffer); XDestroyWindow(info->display, info->win); XFreeColormap(info->display, info->cmap); XCloseDisplay(info->display); free(info->dim); free(info); } void startTimer(GraphicsInfo *info) { gettimeofday(&info->start_time, NULL); info->frames = 0; } void waitUpdateTimer(GraphicsInfo *info) { struct timeval current_time; double new_elapsed; gettimeofday(¤t_time, NULL); new_elapsed = current_time.tv_sec - info->start_time.tv_sec; new_elapsed += (current_time.tv_usec - info->start_time.tv_usec) * 1e-6; info->frametime = new_elapsed - info->elapsed; if(info->frametime < 0.01) { // frames < 10ms are considered too short; sleep a while and then measure again usleep(10000 - info->frametime*1e6); gettimeofday(¤t_time, NULL); new_elapsed = current_time.tv_sec - info->start_time.tv_sec; new_elapsed += (current_time.tv_usec - info->start_time.tv_usec) * 1e-6; info->frametime = new_elapsed - info->elapsed; } info->elapsed = new_elapsed; info->frames++; } int processStandardEvent(GraphicsInfo *info, XEvent *ev, void (*draw)(void *)) { int state; static int last_x, last_y; int stride; int status = STATUS_NOTHING; switch(ev->type) { case Expose: return STATUS_REDRAW; case ConfigureNotify: printf("ConfigureNotify Event, new dimensions: %d %d %d %d\n", ev->xconfigure.x, ev->xconfigure.y, ev->xconfigure.width, ev->xconfigure.height); info->dim->width = ev->xconfigure.width; info->dim->height = ev->xconfigure.height; updateDimensions(info->dim); cairo_xlib_surface_set_size(info->surface, info->dim->width, info->dim->height); cairo_destroy(info->buffer_context); cairo_surface_destroy(info->buffer_surface); free(info->buffer); stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, info->dim->width); info->buffer = malloc(stride*info->dim->height); info->buffer_surface = cairo_image_surface_create_for_data(info->buffer, CAIRO_FORMAT_ARGB32, info->dim->width, info->dim->height, stride); info->buffer_context = cairo_create(info->buffer_surface); return STATUS_REDRAW; case KeyPress: state = ev->xkey.state & (ShiftMask | LockMask | ControlMask); if(state == 0 && ev->xkey.keycode == 24) return STATUS_QUIT; return STATUS_NOTHING; case ButtonPress: if(ev->xbutton.button == 4) { cairo_matrix_t transform; cairo_matrix_init_identity(&transform); cairo_matrix_translate(&transform, ev->xbutton.x, ev->xbutton.y); cairo_matrix_scale(&transform, 5.0/4.0, 5.0/4.0); cairo_matrix_translate(&transform, -ev->xbutton.x, -ev->xbutton.y); cairo_matrix_multiply(&info->dim->matrix, &info->dim->matrix, &transform); status = STATUS_REDRAW; } else if(ev->xbutton.button == 5) { cairo_matrix_t transform; cairo_matrix_init_identity(&transform); cairo_matrix_translate(&transform, ev->xbutton.x, ev->xbutton.y); cairo_matrix_scale(&transform, 4.0/5.0, 4.0/5.0); cairo_matrix_translate(&transform, -ev->xbutton.x, -ev->xbutton.y); cairo_matrix_multiply(&info->dim->matrix, &info->dim->matrix, &transform); status = STATUS_REDRAW; } last_x = ev->xbutton.x; last_y = ev->xbutton.y; updateDimensions(info->dim); return status; case MotionNotify: if(ev->xmotion.state & Button1Mask && !(ev->xmotion.state & ShiftMask)) { int dx = ev->xmotion.x - last_x; int dy = ev->xmotion.y - last_y; cairo_matrix_t transform; cairo_matrix_init_identity(&transform); cairo_matrix_translate(&transform, dx, dy); cairo_matrix_multiply(&info->dim->matrix, &info->dim->matrix, &transform); status = STATUS_REDRAW; } else if(ev->xmotion.state & Button1Mask && ev->xmotion.state & ShiftMask) { double width = (double) cairo_xlib_surface_get_width(info->surface); double height = (double) cairo_xlib_surface_get_height(info->surface); double angle = atan2((double)ev->xmotion.y - height/2, (double)ev->xmotion.x - width/2)- atan2((double)last_y - height/2, (double)last_x - width/2); cairo_matrix_t transform; cairo_matrix_init_identity(&transform); cairo_matrix_translate(&transform, width/2, height/2); cairo_matrix_rotate(&transform, angle); cairo_matrix_translate(&transform, -width/2, -height/2); cairo_matrix_multiply(&info->dim->matrix, &info->dim->matrix, &transform); status = STATUS_REDRAW; } last_x = ev->xmotion.x; last_y = ev->xmotion.y; return status; case ClientMessage: if((Atom)ev->xclient.message_type == info->wm_protocols && (Atom)ev->xclient.data.l[0] == info->wm_delete_window) { // printf("Window closed\n"); return STATUS_QUIT; } return STATUS_NOTHING; } return STATUS_NOTHING; } int checkEvents(GraphicsInfo *info, int (*process)(GraphicsInfo*, XEvent*), void (*draw)(void *)) // get any events from the queue and the server, process them if neccessary, quit if wanted { XEvent ev; int x11_fd; fd_set fds; int status = STATUS_NOTHING, result; int x11_event = 0; if(XPending(info->display)) { // select() would miss events which are picked up already, so we have to catch them first x11_event = 1; } else { x11_fd = ConnectionNumber(info->display); FD_ZERO(&fds); FD_SET(x11_fd, &fds); select(x11_fd + 1, &fds, NULL, NULL, NULL); if(FD_ISSET(x11_fd, &fds)) x11_event = 1; } if(x11_event) { while(XPending(info->display)) { XNextEvent(info->display, &ev); if(ev.xany.window != info->win) continue; result = processStandardEvent(info, &ev, draw); if(result == STATUS_QUIT) return STATUS_QUIT; else if(result == STATUS_REDRAW) status = STATUS_REDRAW; result = process(info, &ev); if(result == STATUS_QUIT) return STATUS_QUIT; else if(result == STATUS_REDRAW) status = STATUS_REDRAW; } } return status; } // this computes center, radius and scalefactor out of ctx->matrix void updateDimensions(DimensionsInfo *dim) { double det = dim->matrix.xx * dim->matrix.yy - dim->matrix.xy * dim->matrix.yx; double cx = (double)dim->width/2; double cy = (double)dim->height/2; double r = 2*sqrt((cx*cx + cy*cy)/det); // this is not safe anymore if we have non-uniform scaling // don't use cairo_device_to_user() since the matrix might not be our CTM yet cx -= dim->matrix.x0; cy -= dim->matrix.y0; dim->center_x = ( dim->matrix.yy * cx - dim->matrix.xy * cy) / det; dim->center_y = (- dim->matrix.yx * cx + dim->matrix.xx * cy) / det; dim->radius = r; dim->scalefactor = sqrt(det); }