overdraw

X11 drawable overlay
git clone https://git.sinitax.com/sinitax/overdraw
Log | Files | Refs | LICENSE | sfeed.txt

commit 4145c7ed8951400913687c1c3e5acb594f95f9ba
parent cc71e45e451288c483857818ca6fb89bf7160f03
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sun,  2 Jan 2022 20:59:30 +0100

Antialiased line drawing in different colors

Using cairo to allow for more advanced graphics, such as
antialiasing. Paths also allow for the implementation of
undo / redo functionality in the future.

Note, this commit is still missing certain garbage collection
and creates visual artifacts on ButtonRelease, because the
calls to clear_background and draw_lines blit to the screen
automatically instead of waiting until XFlush(display). FIX THIS.

Diffstat:
M.gitignore | 1+
M.gitmodules | 3---
MMakefile | 19+++++++------------
AREADME | 6++++++
Dlibs/xosd | 1-
Aoverdraw.c | 242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/overdraw.c | 39---------------------------------------
7 files changed, 256 insertions(+), 55 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,4 @@ .cache compile_commands.json build +overdraw diff --git a/.gitmodules b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "libs/xosd"] - path = libs/xosd - url = git@sinitax.com:sinitax/xosd-custom diff --git a/Makefile b/Makefile @@ -1,17 +1,12 @@ -CFLAGS = -I libs/xosd/include -LDLIBS = -lX11 -lXext -lpthread +CFLAGS = +LDLIBS = -lX11 -lcairo -all: build/overdraw +.PHONY: all clean -build: - mkdir build +all: overdraw -libs/xosd: - git submodule update --init --recursive +clean: + rm overdraw -build/xosd.o: | build libs/xosd - make -C libs/xosd build/libxosd.o - cp libs/xosd/build/libxosd.o build/xosd.o - -build/overdraw: src/overdraw.c build/xosd.o | build +overdraw: overdraw.c $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) diff --git a/README b/README @@ -0,0 +1,6 @@ +implement initial drawing same way as dsnip, save operations in list and apply to copied pixmap on each frame +after that, draw on root with IncludeInferior that same operation ever expose + +might be a better way with a 32bit depth visual overlayed window, see: +https://stackoverflow.com/questions/21780789/x11-draw-on-overlay-window/57780772#57780772 +https://stackoverflow.com/questions/16400937/click-through-transparent-xlib-windows (for click through) diff --git a/libs/xosd b/libs/xosd @@ -1 +0,0 @@ -Subproject commit 15756aed2e4af2f51133844b9f73221eacafa4c1 diff --git a/overdraw.c b/overdraw.c @@ -0,0 +1,242 @@ +#include <cairo/cairo.h> +#include <cairo/cairo-xlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/keysym.h> +#include <X11/cursorfont.h> + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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 void die(const char *fmtstr, ...); +static void add_line(cairo_path_t *path, struct rgb *color); +static void clear_background(void); +static void draw_color_mark(void); +static void draw_lines(void); + +static struct rgb colors[3]; +static int color_index; + +static XImage *image; + +XWindowAttributes scr; +static Display *display; +static Screen *screen; +static Window root, win; +static GC gc; + +static cairo_surface_t *cs; +static cairo_t *cr; + +static struct line *lines; +static int lines_cnt, lines_cap; + +void +die(const char *fmtstr, ...) +{ + va_list ap; + + va_start(ap, fmtstr); + vfprintf(stderr, fmtstr, ap); + va_end(ap); + + exit(1); +} + +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) exit(1); + } + + lines[lines_cnt].path = path; + lines[lines_cnt].color = *color; + + lines_cnt++; +} + +void +clear_background(void) +{ + XClearWindow(display, win); +} + +void +draw_color_mark(void) +{ + struct rgb *c; + + c = &colors[color_index]; + cairo_save(cr); + cairo_set_source_rgba(cr, c->r, c->g, c->b, 1); + cairo_rectangle(cr, 10, 10, 20, 20); + cairo_stroke_preserve(cr); + cairo_fill(cr); + cairo_restore(cr); +} + +void +draw_lines(void) +{ + int i; + + cairo_save(cr); + for (i = 0; i < lines_cnt; i++) { + cairo_new_path(cr); + cairo_set_source_rgba(cr, lines[i].color.r, + lines[i].color.g, lines[i].color.b, 1); + cairo_append_path(cr, lines[i].path); + cairo_stroke(cr); + } + cairo_restore(cr); +} + +void +init(void) +{ + Cursor cursor; + XSetWindowAttributes swa; + XGCValues gcvals; + Visual *visual; + + display = XOpenDisplay(NULL); + root = DefaultRootWindow(display); + + XGetWindowAttributes(display, root, &scr); + + swa.override_redirect = 1; + win = XCreateWindow(display, root, scr.x, scr.y, scr.width, scr.height, + 0, CopyFromParent, InputOutput, CopyFromParent, + CWOverrideRedirect, &swa); + if (!win) die("Failed to create window\n"); + XMapWindow(display, win); + + image = XGetImage(display, win, scr.x, scr.y, scr.width, scr.height, + AllPlanes, ZPixmap); + if (!image) die("Failed to capture screen\n"); + + gcvals = (XGCValues) { 0 }; + gcvals.subwindow_mode = IncludeInferiors; + gcvals.line_width = 2; + gc = XCreateGC(display, win, GCSubwindowMode | GCLineWidth, &gcvals); + + screen = XDefaultScreenOfDisplay(display); + visual = XDefaultVisualOfScreen(screen); + + Pixmap pix = XCreatePixmap(display, win, scr.width, scr.height, scr.depth); + XCopyArea(display, win, pix, gc, 0, 0, scr.width, scr.height, 0, 0); + XSetWindowBackgroundPixmap(display, win, pix); + + 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; + + cursor = XCreateFontCursor(display, XC_pencil); + + XGrabPointer(display, root, False, + ButtonMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, root, cursor, CurrentTime); + + XGrabKeyboard(display, root, False, GrabModeAsync, + GrabModeAsync, CurrentTime); + + lines_cap = 10; + lines = calloc(lines_cap, sizeof(struct line)); + if (!lines) exit(1); + lines_cnt = 0; + + draw_color_mark(); + XFlush(display); +} + +int +main(int argc, char *argv[]) +{ + int px, py, x, y; + struct rgb color; + cairo_path_t *path; + int done, draw; + int keysym; + XEvent ev; + + init(); + + done = 0; + while (!done) { + if (!XPending(display)) continue; + XNextEvent(display, &ev); + switch (ev.type) { + case KeyPress: + keysym = XkbKeycodeToKeysym(display, ev.xkey.keycode, 0, + ev.xkey.state & ShiftMask ? 1 : 0); + switch (keysym) { + case XK_Escape: + done = 1; + break; + case ' ': + color_index = (color_index + 1) % ARRLEN(colors); + draw_color_mark(); + break; + } + break; + case MotionNotify: + if (!draw) break; + cairo_set_source_rgba(cr, color.r, color.g, color.b, 1); + 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); + 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); + + clear_background(); + draw_color_mark(); + draw_lines(); + XFlush(display); + break; + } + } + + cairo_surface_destroy(cs); + + XDestroyWindow(display, win); + XCloseDisplay(display); +} diff --git a/src/overdraw.c b/src/overdraw.c @@ -1,39 +0,0 @@ -#include <X11/X.h> -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <locale.h> -#include <xosd.h> - -#include <X11/Xlib.h> -#include <time.h> - -int -main (int argc, char *argv[]) -{ - Window root, fromroot, tmpwin; - Display *display; - XGCValues gcvals; - GC gc; - int x, y, prev_x, prev_y, tmp, screen; - uint tmp2; - - display = XOpenDisplay(NULL); - root = DefaultRootWindow(display); - screen = XDefaultScreen(display); - - gcvals.foreground = WhitePixel(display, screen); - gcvals.plane_mask = AllPlanes; - gc = XCreateGC(display, root, GCPlaneMask | GCForeground, &gcvals); - - XQueryPointer(display, root, &fromroot, &tmpwin, &prev_x, &prev_y, &tmp, &tmp, &tmp2); - while(1) { - XQueryPointer(display, root, &fromroot, &tmpwin, &x, &y, &tmp, &tmp, &tmp2); - if (x != prev_x || y != prev_y) { - XDrawLine(display, root, gc, prev_x, prev_y, x, y); - prev_x = x; - prev_y = y; - } - usleep(50000); - } -}