#include #include #include #include #include #include #include #include #include #include #include #include #include #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) >= (b) ? (a) : (b)) #define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) struct rgb { double r, g, b; }; struct line { cairo_path_t *path; struct rgb color; }; static struct rgb colors[3]; static size_t color_index; XWindowAttributes scr; static Display *display; static Screen *screen; static Window root, win; static bool grabbed; static bool done; static bool draw; static cairo_surface_t *cs; static cairo_t *cr; static struct line *lines; static size_t lines_cnt, lines_cap; static bool transparent; static void add_line(cairo_path_t *path, struct rgb *color) { if (lines_cnt == lines_cap) { lines_cap *= 2; lines = realloc(lines, lines_cap * sizeof(struct line)); if (!lines) err(1, "realloc"); } lines[lines_cnt].path = path; lines[lines_cnt].color = *color; lines_cnt++; } static void draw_color_mark(void) { struct rgb *c; c = &colors[color_index]; cairo_save(cr); cairo_set_source_rgb(cr, c->r, c->g, c->b); cairo_rectangle(cr, 10, 10, 20, 20); cairo_stroke_preserve(cr); cairo_fill(cr); cairo_restore(cr); } static void update_pixmap(void) { Pixmap pix; GC gc; pix = XCreatePixmap(display, win, (unsigned int) scr.width, (unsigned int) scr.height, (unsigned int) scr.depth); gc = XCreateGC(display, win, 0, 0); XCopyArea(display, win, pix, gc, 0, 0, (unsigned int) scr.width, (unsigned int) scr.height, 0, 0); XSetWindowBackgroundPixmap(display, win, pix); } static void draw_lines(void) { int i; cairo_save(cr); for (i = 0; i < lines_cnt; i++) { cairo_new_path(cr); cairo_set_source_rgb(cr, lines[i].color.r, lines[i].color.g, lines[i].color.b); cairo_append_path(cr, lines[i].path); cairo_stroke(cr); } cairo_restore(cr); } static void grab(void) { Cursor cursor; cursor = XCreateFontCursor(display, XC_pencil); XGrabPointer(display, root, False, ButtonMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, root, cursor, CurrentTime); XGrabKeyboard(display, root, False, GrabModeAsync, GrabModeAsync, CurrentTime); grabbed = true; } static void ungrab(void) { XUngrabKeyboard(display, CurrentTime); XUngrabPointer(display, CurrentTime); XGrabKey(display, XKeysymToKeycode(display, XK_Escape), Mod1Mask, root, False, GrabModeAsync, GrabModeAsync); grabbed = false; } static void redraw(void) { if (transparent) { cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_rectangle(cr, 0, 0, (double) scr.width, (double) scr.height); cairo_fill(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); } else { XClearWindow(display, win); } draw_color_mark(); draw_lines(); XFlush(display); } static void keypress(XEvent ev) { KeySym keysym; keysym = XkbKeycodeToKeysym(display, (KeyCode) ev.xkey.keycode, 0, ev.xkey.state & ShiftMask ? 1 : 0); switch (keysym) { case XK_Escape: if (!ev.xkey.state) { done = true; } else if ((ev.xkey.state & Mod1Mask)) { if (grabbed) { ungrab(); if (!transparent) { XUnmapWindow(display, win); } } else { if (!transparent) { XMapWindow(display, win); update_pixmap(); redraw(); } grab(); } } break; case ' ': color_index = (color_index + 1) % ARRLEN(colors); draw_color_mark(); break; } } static void init(void) { XSetWindowAttributes swa; XVisualInfo vinfo; Visual *visual; int depth; display = XOpenDisplay(NULL); root = DefaultRootWindow(display); screen = XDefaultScreenOfDisplay(display); XGetWindowAttributes(display, root, &scr); if (transparent) { XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, &vinfo); visual = vinfo.visual; depth = vinfo.depth; } else { visual = XDefaultVisualOfScreen(screen); depth = CopyFromParent; } swa.border_pixel = 0; swa.override_redirect = 1; swa.event_mask = ExposureMask; swa.colormap = XCreateColormap(display, root, visual, AllocNone); win = XCreateWindow(display, root, scr.x, scr.y, (unsigned int) scr.width, (unsigned int) scr.height, 0, depth, InputOutput, visual, CWOverrideRedirect | CWBorderPixel | CWColormap | CWEventMask, &swa); if (!win) err(1, "XCreateWindow"); XMapWindow(display, win); if (!transparent) update_pixmap(); cs = cairo_xlib_surface_create(display, win, visual, scr.width, scr.height); cr = cairo_create(cs); cairo_surface_flush(cs); cairo_set_line_width(cr, 2); cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT); colors[0] = (struct rgb) { 1.0, 1.0, 1.0 }; colors[1] = (struct rgb) { 1.0, 0.0, 0.0 }; colors[2] = (struct rgb) { 0.0, 0.0, 0.0 }; color_index = 0; lines_cap = 10; lines = calloc(lines_cap, sizeof(struct line)); if (!lines) exit(1); lines_cnt = 0; grab(); } static void update(void) { struct rgb color; cairo_path_t *path; XEvent ev; done = 0; while (!done) { XNextEvent(display, &ev); switch (ev.type) { case KeyPress: keypress(ev); break; case MotionNotify: if (!draw) break; cairo_set_source_rgb(cr, color.r, color.g, color.b); cairo_line_to(cr, ev.xmotion.x, ev.xmotion.y); cairo_move_to(cr, ev.xmotion.x, ev.xmotion.y); cairo_stroke_preserve(cr); cairo_surface_flush(cs); path = cairo_copy_path(cr); if (path->num_data > 500) { add_line(path, &color); cairo_new_path(cr); cairo_line_to(cr, ev.xmotion.x, ev.xmotion.y); } else { cairo_path_destroy(path); } XFlush(display); break; case ButtonPress: cairo_new_path(cr); cairo_move_to(cr, ev.xmotion.x, ev.xmotion.y); color = colors[color_index]; draw = 1; break; case ButtonRelease: draw = 0; path = cairo_copy_path(cr); add_line(path, &color); redraw(); break; case Expose: redraw(); break; } } } static void deinit(void) { cairo_surface_destroy(cs); XDestroyWindow(display, win); XCloseDisplay(display); free(lines); } int main(int argc, const char **argv) { const char **arg; transparent = false; for (arg = &argv[1]; *arg; arg++) { if (!strcmp(*arg, "-t")) { transparent = true; } else { errx(1, "Unknown arg: %s", *arg); } } init(); update(); deinit(); }