2 * Copyright © 2023 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
25 #include <X11/Xutil.h>
26 #include <X11/Xatom.h>
28 #ifndef DEFAULT_VISUAL
29 #define DEFAULT_VISUAL 0
33 #define DEFAULT_ROP "copy"
37 #define DEFAULT_WIDTH 200
40 #ifndef DEFAULT_HEIGHT
41 #define DEFAULT_HEIGHT 200
45 #define DEFAULT_FONT 0
48 #ifndef DEFAULT_LINE_WIDTH
49 #define DEFAULT_LINE_WIDTH 0
52 #ifndef DEFAULT_LINE_STYLE
53 #define DEFAULT_LINE_STYLE NULL
56 #ifndef DEFAULT_FILL_STYLE
57 #define DEFAULT_FILL_STYLE NULL
60 #ifndef DEFAULT_CAP_STYLE
61 #define DEFAULT_CAP_STYLE NULL
64 #ifndef DEFAULT_JOIN_STYLE
65 #define DEFAULT_JOIN_STYLE NULL
68 #ifndef DEFAULT_DASHES
69 #define DEFAULT_DASHES 0
70 #define DEFAULT_NUM_DASHES 0
74 #define MOTION PointerMotionMask
76 #define MOTION ButtonMotionMask
80 #define EXTRA_EVENTS 0
83 unsigned long fg_pixel;
84 unsigned long bg_pixel;
86 XFontStruct *default_font;
88 typedef struct _RopNames { char *name; int rop; } RopNameRec, *RopNamePtr;
90 RopNameRec ropNames[] = {
91 { "clear", GXclear, }, /* 0 */
92 { "and", GXand, }, /* src AND dst */
93 { "andReverse", GXandReverse, }, /* src AND NOT dst */
94 { "copy", GXcopy, }, /* src */
95 { "andInverted", GXandInverted, }, /* NOT src AND dst */
96 { "noop", GXnoop, }, /* dst */
97 { "xor", GXxor, }, /* src XOR dst */
98 { "or", GXor, }, /* src OR dst */
99 { "nor", GXnor, }, /* NOT src AND NOT dst */
100 { "equiv", GXequiv, }, /* NOT src XOR dst */
101 { "invert", GXinvert, }, /* NOT dst */
102 { "orReverse", GXorReverse, }, /* src OR NOT dst */
103 { "copyInverted", GXcopyInverted, }, /* NOT src */
104 { "orInverted", GXorInverted, }, /* NOT src OR dst */
105 { "nand", GXnand, }, /* NOT src OR NOT dst */
106 { "set", GXset, }, /* 1 */
110 RopNameToRop (char *name)
114 for (rop = 0; rop < 16; rop++) {
115 if (!strcmp (name, ropNames[rop].name)) {
116 return ropNames[rop].rop;
123 Error (char *s, char *a)
125 fprintf (stderr, s, a);
126 fprintf (stderr, "\n");
131 MatchCapStyle (char *s)
133 if (!strcmp (s, "round"))
135 if (!strcmp (s, "projecting"))
136 return CapProjecting;
137 if (!strcmp (s, "butt"))
139 if (!strcmp (s, "notlast"))
141 Error ("Unknown cap style %s", s);
146 MatchJoinStyle (char *s)
148 if (!strcmp (s, "round"))
150 if (!strcmp (s, "miter"))
152 if (!strcmp (s, "bevel"))
154 Error ("Unknown join style %s", s);
159 MatchFillStyle(char *s)
161 if (!strcmp (s, "solid"))
163 if (!strcmp (s, "tiled"))
165 if (!strcmp (s, "stippled"))
167 if (!strcmp (s, "opaquestippled"))
168 return FillOpaqueStippled;
169 Error ("Unknown fill style %s", s);
174 MatchLineStyle(char *s)
176 if (!strcmp (s, "solid"))
178 if (!strcmp (s, "onoff"))
179 return LineOnOffDash;
180 if (!strcmp (s, "double"))
181 return LineDoubleDash;
182 Error ("Unknown line style %s", s);
187 HandleButtonPress(Display *dpy, Window win, GC gc, XEvent *ev);
190 HandleButtonRelease(Display *dpy, Window win, GC gc, XEvent *ev);
193 HandleMotionNotify(Display *dpy, Window win, GC gc, XEvent *ev);
198 typedef struct _Position {
200 int start_x, start_y;
203 } PositionRec, *PositionPtr;
206 Draw(Display *dpy, Window win, GC gc, PositionRec positions[5]);
209 Undraw(Display *dpy, Window win, GC gc, PositionRec positions[5]);
211 PositionRec current_positions[5];
214 UpdatePositions (unsigned state, int x, int y)
219 for (i = 0; i < 5; i++)
221 if (state & (1 << i)) {
222 current_positions[i].cur_x = x;
223 current_positions[i].cur_y = y;
229 HandleButtonPress (Display *dpy, Window win, GC gc, XEvent *ev)
231 XButtonEvent *bev = (XButtonEvent *) ev;
233 Undraw (dpy, win, gc, current_positions);
234 current_positions[bev->button - 1].seen = 1;
235 current_positions[bev->button - 1].start_x = bev->x;
236 current_positions[bev->button - 1].start_y = bev->y;
237 current_positions[bev->button - 1].cur_x = bev->x;
238 current_positions[bev->button - 1].cur_y = bev->y;
239 current_positions[bev->button - 1].end_x = bev->x;
240 current_positions[bev->button - 1].end_y = bev->y;
241 UpdatePositions (bev->state, bev->x, bev->y);
242 Draw (dpy, win, gc, current_positions);
246 HandleButtonRelease (Display *dpy, Window win, GC gc, XEvent *ev)
248 XButtonEvent *bev = (XButtonEvent *) ev;
250 Undraw (dpy, win, gc, current_positions);
251 UpdatePositions (bev->state, bev->x, bev->y);
252 current_positions[bev->button - 1].end_x = bev->x;
253 current_positions[bev->button - 1].end_y = bev->y;
254 Draw (dpy, win, gc, current_positions);
258 HandleMotionNotify (Display *dpy, Window win, GC gc, XEvent *ev)
260 XMotionEvent *mev = (XMotionEvent *) ev;
262 Undraw (dpy, win, gc, current_positions);
263 UpdatePositions (mev->state, mev->x, mev->y);
264 Draw (dpy, win, gc, current_positions);
268 DisplayPositions (Display *dpy, Window win, GC gc, PositionRec positions[5])
270 static char text[1024];
274 int dir, font_ascent, font_descent;
277 XTextExtents (default_font, text, (int) strlen(text),
278 &dir, &font_ascent, &font_descent, &overall);
280 XClearArea (dpy, win, 0, 0, (unsigned) overall.width, (unsigned) (font_ascent + font_descent), False);
282 for (i = 0; i < 5; i++)
284 if (positions[i].seen)
288 sprintf (text + pos, "%1d: (%4d,%4d),(%4d,%4d)",
289 i, positions[i].start_x, positions[i].start_y,
290 positions[i].cur_x, positions[i].cur_y);
291 pos = (int) strlen (text);
294 XDrawString (dpy, win, gc, 0, font_ascent, text, pos);
301 SetToFg (Display *dpy, GC gc)
303 XSetForeground (dpy, gc, fg_pixel);
304 XSetFunction (dpy, gc, default_rop);
308 SetToBg (Display *dpy, GC gc)
310 XSetForeground (dpy, gc, bg_pixel);
311 XSetFunction (dpy, gc, GXcopy);
314 Usage (char *program)
317 fprintf (stderr, "Usage: %s\n", program);
318 fprintf (stderr, "\t-display <display-name>\n");
319 fprintf (stderr, "\t-rop {");
320 for (i = 0; i < 16; i++) {
321 fprintf (stderr, "%s", ropNames[i].name);
323 fprintf (stderr, "}\n");
325 fprintf (stderr, ",\n\t\t");
327 fprintf (stderr, ", ");
329 fprintf (stderr, "\t-fg <fg color name>\n");
330 fprintf (stderr, "\t-bg <bg color name>\n");
331 fprintf (stderr, "\t-fg_pixel <fg pixel value>\n");
332 fprintf (stderr, "\t-bg_pixel <bg pixel value>\n");
333 fprintf (stderr, "\t-fn <font name>\n");
334 fprintf (stderr, "\t-geometry <geometry>\n");
335 fprintf (stderr, "\t-lw <line width>\n");
336 fprintf (stderr, "\t-cap {round, projecting, butt, notlast}\n");
337 fprintf (stderr, "\t-join {round, miter, bevel}\n");
338 fprintf (stderr, "\t-fill {solid, tiled, stippled, opaquestippled}\n");
339 fprintf (stderr, "\t-linestyle {solid, onoff, double}\n");
340 fprintf (stderr, "\t-bitmap <bitmap file name>\n");
341 fprintf (stderr, "\t-dashes [<dash value> ...]\n");
350 char *cap_name = DEFAULT_CAP_STYLE;
351 char *join_name = DEFAULT_JOIN_STYLE;
352 char *fill_name = DEFAULT_FILL_STYLE;
353 char *line_name = DEFAULT_LINE_STYLE;
354 char *font_name = DEFAULT_FONT;
355 double line_width = DEFAULT_LINE_WIDTH;
356 char dashes[256] = { DEFAULT_DASHES };
357 int ndashes = DEFAULT_NUM_DASHES;
358 VisualID vid = DEFAULT_VISUAL;
359 unsigned long planemask = ~0UL;
366 int current_timeout = TIMEOUT;
370 Setup(Display *dpy, Window win);
373 HandleExpose(Display *dpy, Window win, GC gc);
376 HandleKeyPress(Display *dpy, Window win, GC gc, XEvent *ev);
379 HandleKeyRelease(Display *dpy, Window win, GC gc, XEvent *ev);
382 HandleTimeout(Display *dpy, Window win, GC gc);
384 #include <X11/extensions/Xrender.h>
386 XRenderColor renderBlack = { 0, 0, 0, 0xffff };
387 XRenderColor renderWhite = { 0xffff, 0xffff, 0xffff, 0xffff };
388 XRenderColor renderRed = { 0xffff, 0, 0, 0xffff };
389 XRenderColor renderGreen = { 0, 0xffff, 0, 0xffff };
390 XRenderColor renderBlue = { 0, 0, 0xffff, 0xffff };
392 XRenderColor renderClearRed = { 0x8000, 0, 0, 0x8000 };
393 XRenderColor renderClearGreen = { 0, 0x8000, 0, 0x8000 };
394 XRenderColor renderClearBlue = { 0, 0, 0x8000, 0x8000 };
396 static inline Picture
397 GetPicture (Display *dpy, Window w)
402 p = XRenderCreatePicture (dpy, w,
403 XRenderFindVisualFormat (dpy, visual),
408 static inline Picture
409 GetSrc (Display *dpy, XRenderColor *color)
413 static XRenderColor lastColor;
414 XRenderPictFormat *f;
415 XRenderPictureAttributes attr;
417 if (p && !memcmp (color, &lastColor, sizeof (XRenderColor)))
421 f = XRenderFindStandardFormat (dpy, PictStandardARGB32);
422 pix = XCreatePixmap (dpy, RootWindow (dpy, DefaultScreen (dpy)),
423 1, 1, (unsigned) f->depth);
425 p = XRenderCreatePicture (dpy, pix, f, CPRepeat, &attr);
426 XFreePixmap (dpy, pix);
428 XRenderFillRectangle (dpy, PictOpSrc, p, color, 0, 0, 1, 1);
433 static inline XRenderPictFormat *
434 GetMask (Display *dpy)
436 return XRenderFindStandardFormat (dpy, PictStandardA8);
440 main (int argc, char **argv)
445 char **init_argv = argv;
446 XSetWindowAttributes attr;
449 unsigned int width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT;
450 unsigned int border_width = 1u;
451 XSizeHints sizeHints;
455 #ifdef MATCH_ARGUMENT
462 #ifdef COMPRESS_EXPOSE
467 XTextProperty wm_name, icon_name;
468 Atom wm_delete_window;
469 int has_fg_pixel = 0, has_bg_pixel = 0;
470 int has_colormap = 0;
471 unsigned long gc_mask;
473 char quit_string[10];
475 unsigned long window_mask;
478 rop_name = DEFAULT_ROP;
479 wm_name.value = (unsigned char *) argv[0];
480 wm_name.encoding = XA_STRING;
482 wm_name.nitems = strlen ((const char *) wm_name.value) + 1;
486 if (!strcmp (*argv, "-display"))
488 else if (!strcmp (*argv, "-visual"))
489 vid = strtoul(*++argv, NULL, 0);
490 else if (!strcmp (*argv, "-cmap"))
492 colormap = strtoul(*++argv, NULL, 0);
495 else if (!strcmp (*argv, "-rop"))
497 else if (!strcmp (*argv, "-fg"))
499 else if (!strcmp (*argv, "-bg"))
501 else if (!strcmp (*argv, "-fg_pixel"))
503 fg_pixel = strtoul(*++argv, NULL, 0);
506 else if (!strcmp (*argv, "-bg_pixel"))
508 bg_pixel = strtoul(*++argv, NULL, 0);
511 else if (!strcmp (*argv, "-fn"))
513 else if (!strcmp (*argv, "-pm"))
514 planemask = strtoul(*++argv, NULL, 0);
515 else if (!strcmp (*argv, "-geometry"))
516 XParseGeometry (*++argv, &x, &y, &width, &height);
517 else if (!strcmp (*argv, "-sync"))
519 else if (!strcmp (*argv, "-bw"))
520 border_width = (unsigned int) strtoul(*++argv, NULL, 0);
521 else if (!strcmp (*argv, "-lw"))
522 line_width = strtod(*++argv, NULL);
523 else if (!strcmp (*argv, "-cap"))
525 else if (!strcmp (*argv, "-join"))
527 else if (!strcmp (*argv, "-fill"))
529 else if (!strcmp (*argv, "-linestyle"))
531 else if (!strcmp (*argv, "-bitmap"))
532 bitmap_file = *++argv;
533 else if (!strcmp (*argv, "-dashes"))
537 while (*argv && isdigit (**argv))
538 dashes[ndashes++] = (char) atoi(*argv++);
541 #ifdef MATCH_ARGUMENT
542 else if (i = MatchArgument (argv))
545 else if (!strcmp (*argv, "-root"))
546 root = strtoul (*++argv, NULL, 0);
551 wmHints.flags = InputHint;
552 wmHints.input = True;
553 dpy = XOpenDisplay (dpy_name);
555 Error ("Can't open display", "");
557 XSynchronize (dpy, sync);
558 screen = DefaultScreen (dpy);
560 root = RootWindow (dpy, screen);
561 window_mask = CWBackPixel|CWBorderPixel|CWEventMask;
563 colormap = DefaultColormap (dpy, screen);
566 window_mask |= CWColormap;
567 attr.colormap = colormap;
569 visual = DefaultVisual (dpy, screen);
570 depth = DefaultDepth (dpy, screen);
573 XVisualInfo vi, *vi_ret;
578 vi_ret = XGetVisualInfo (dpy, VisualIDMask|VisualScreenMask,
582 visual = vi_ret->visual;
585 colormap = XCreateColormap (dpy, root, visual, AllocNone);
586 window_mask |= CWColormap;
587 attr.colormap = colormap;
589 depth = vi_ret->depth;
593 fg_pixel = BlackPixel (dpy, screen);
595 bg_pixel = WhitePixel (dpy, screen);
598 if (!XAllocNamedColor (dpy, colormap, fg, &hard, &exact))
599 Error ("Can't allocate fg pixel %s", fg);
600 fg_pixel = hard.pixel;
604 if (!XAllocNamedColor (dpy, colormap, bg, &hard, &exact))
605 Error ("Can't allocate bg pixel %s", bg);
606 bg_pixel = hard.pixel;
608 attr.background_pixel = bg_pixel;
609 attr.border_pixel = fg_pixel;
611 ExposureMask|KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask|MOTION|EXTRA_EVENTS;
612 // attr.override_redirect = True;
613 // window_mask |= CWOverrideRedirect;
614 wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
615 win = XCreateWindow (dpy, root, x, y, width, height, border_width,
623 XSetWMProperties (dpy, win,
624 &wm_name, &icon_name,
626 &sizeHints, &wmHints, 0);
627 XSetWMProtocols (dpy, win, &wm_delete_window, 1);
628 default_rop = RopNameToRop (rop_name);
631 gcv.line_width = (int) line_width;
632 gc_mask |= GCLineWidth;
636 gcv.cap_style = MatchCapStyle (cap_name);
637 gc_mask |= GCCapStyle;
640 gcv.fill_style = MatchFillStyle (fill_name);
641 gc_mask |= GCFillStyle;
644 gcv.line_style = MatchLineStyle (line_name);
645 gc_mask |= GCLineStyle;
648 gcv.join_style = MatchJoinStyle (join_name);
649 gc_mask |= GCJoinStyle;
652 default_font = XLoadQueryFont (dpy, font_name);
654 gcv.font = default_font->fid;
657 gcv.function = default_rop;
658 gcv.foreground = fg_pixel;
659 gcv.background = bg_pixel;
660 gcv.plane_mask = planemask;
661 gc_mask |= GCFunction|GCForeground|GCBackground|GCPlaneMask;
664 unsigned int b_width, b_height;
666 Pixmap bitmap, pixmap;
668 ret = XReadBitmapFile (dpy, win, bitmap_file, &b_width, &b_height,
669 &bitmap, (int *) 0, (int *) 0);
671 case BitmapOpenFailed:
672 Error ("Can't open bitmap file %s", bitmap_file);
674 case BitmapFileInvalid:
675 Error ("Bitmap file %s invalid", bitmap_file);
678 Error ("Out of memory reading bitmap file %s", bitmap_file);
683 switch (gcv.fill_style) {
685 pixmap = XCreatePixmap (dpy, win, b_width, b_height, (unsigned) DefaultDepth (dpy, screen));
686 gc = XCreateGC (dpy, pixmap, GCForeground|GCBackground, &gcv);
687 XCopyPlane (dpy, bitmap, pixmap, gc, 0, 0, b_width, b_height, 0, 0, 1);
689 XFreePixmap (dpy, bitmap);
694 case FillOpaqueStippled:
695 gcv.stipple = bitmap;
696 gc_mask |= GCStipple;
700 gc = XCreateGC (dpy, win, gc_mask, &gcv);
702 default_font = XQueryFont (dpy, XGContextFromGC(gc));
704 XSetDashes (dpy, gc, 0, dashes, ndashes);
705 XMapWindow (dpy, win);
708 while (current_timeout && !XEventsQueued(dpy, QueuedAfterFlush)) {
709 struct pollfd pollfd = {
710 .fd = ConnectionNumber(dpy),
713 int r = poll(&pollfd, 1, current_timeout);
715 HandleTimeout(dpy, win, gc);
718 XNextEvent (dpy, &ev);
720 if (HasMotion && ev.type != MotionNotify) {
722 HandleMotionNotify (dpy, win, gc, &mev);
725 #ifdef COMPRESS_EXPOSE
726 if (HasExpose && ev.type != Expose) {
728 HandleExpose (dpy, eev.xexpose.window, gc);
733 #ifdef COMPRESS_EXPOSE
737 } else if (ev.xexpose.count == 0)
740 HandleExpose (dpy, ev.xexpose.window, gc);
744 HandleKeyPress (dpy, ev.xkey.window, gc, &ev);
747 HandleKeyRelease (dpy, ev.xkey.window, gc, &ev);
751 if (XLookupString ((XKeyEvent *) &ev, quit_string, sizeof (quit_string), 0, 0) == 1) {
752 switch (quit_string[0]) {
756 XClearArea (dpy, ev.xkey.window, 0, 0, 0, 0, True);
764 HandleButtonPress (dpy, ev.xbutton.window, gc, &ev);
767 HandleButtonRelease (dpy, ev.xbutton.window, gc, &ev);
774 HandleMotionNotify (dpy, ev.xmotion.window, gc, &ev);
782 HandleOtherEvent (dpy, win, gc, &ev);