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);
188 typedef struct _Position {
190 int start_x, start_y;
193 } PositionRec, *PositionPtr;
195 PositionRec positions[5];
198 UpdatePositions (state, x, y)
203 for (i = 0; i < 5; i++)
205 if (state & (1 << i)) {
206 positions[i].cur_x = x;
207 positions[i].cur_y = y;
213 HandleButtonPress (dpy, win, gc, ev)
219 XButtonEvent *bev = (XButtonEvent *) ev;
221 Undraw (dpy, win, gc, positions);
222 positions[bev->button - 1].seen = 1;
223 positions[bev->button - 1].start_x = bev->x;
224 positions[bev->button - 1].start_y = bev->y;
225 positions[bev->button - 1].cur_x = bev->x;
226 positions[bev->button - 1].cur_y = bev->y;
227 positions[bev->button - 1].end_x = bev->x;
228 positions[bev->button - 1].end_y = bev->y;
229 UpdatePositions (bev->state, bev->x, bev->y);
230 Draw (dpy, win, gc, positions);
234 HandleButtonRelease (dpy, win, gc, ev)
240 XButtonEvent *bev = (XButtonEvent *) ev;
242 Undraw (dpy, win, gc, positions);
243 UpdatePositions (bev->state, bev->x, bev->y);
244 positions[bev->button - 1].end_x = bev->x;
245 positions[bev->button - 1].end_y = bev->y;
246 Draw (dpy, win, gc, positions);
250 HandleMotionNotify (dpy, win, gc, ev)
256 XMotionEvent *mev = (XMotionEvent *) ev;
258 Undraw (dpy, win, gc, positions);
259 UpdatePositions (mev->state, mev->x, mev->y);
260 Draw (dpy, win, gc, positions);
264 DisplayPositions (dpy, win, gc, positions)
268 PositionRec positions[5];
270 static char text[1024];
274 int dir, font_ascent, font_descent;
277 XTextExtents (default_font , text, strlen(text),
278 &dir, &font_ascent, &font_descent, &overall);
280 XClearArea (dpy, win, 0, 0, overall.width, 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);
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 HandleExpose(Display *dpy, Window win, GC gc);
373 HandleKeyPress(Display *dpy, Window win, GC gc, XEvent *ev);
376 HandleKeyRelease(Display *dpy, Window win, GC gc, XEvent *ev);
379 HandleTimeout(Display *dpy, Window win, GC gc);
381 #include <X11/extensions/Xrender.h>
383 XRenderColor renderBlack = { 0, 0, 0, 0xffff };
384 XRenderColor renderWhite = { 0xffff, 0xffff, 0xffff, 0xffff };
385 XRenderColor renderRed = { 0xffff, 0, 0, 0xffff };
386 XRenderColor renderGreen = { 0, 0xffff, 0, 0xffff };
387 XRenderColor renderBlue = { 0, 0, 0xffff, 0xffff };
389 XRenderColor renderClearRed = { 0x8000, 0, 0, 0x8000 };
390 XRenderColor renderClearGreen = { 0, 0x8000, 0, 0x8000 };
391 XRenderColor renderClearBlue = { 0, 0, 0x8000, 0x8000 };
393 static inline Picture
394 GetPicture (Display *dpy, Window w)
399 p = XRenderCreatePicture (dpy, w,
400 XRenderFindVisualFormat (dpy, visual),
405 static inline Picture
406 GetSrc (Display *dpy, XRenderColor *color)
410 static XRenderColor lastColor;
411 XRenderPictFormat *f;
412 XRenderPictureAttributes attr;
414 if (p && !memcmp (color, &lastColor, sizeof (XRenderColor)))
418 f = XRenderFindStandardFormat (dpy, PictStandardARGB32);
419 pix = XCreatePixmap (dpy, RootWindow (dpy, DefaultScreen (dpy)),
420 1, 1, (unsigned) f->depth);
422 p = XRenderCreatePicture (dpy, pix, f, CPRepeat, &attr);
423 XFreePixmap (dpy, pix);
425 XRenderFillRectangle (dpy, PictOpSrc, p, color, 0, 0, 1, 1);
430 static inline XRenderPictFormat *
431 GetMask (Display *dpy)
433 return XRenderFindStandardFormat (dpy, PictStandardA8);
437 main (int argc, char **argv)
442 char **init_argv = argv;
443 XSetWindowAttributes attr;
446 unsigned int width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT;
447 unsigned int border_width = 1u;
448 XSizeHints sizeHints;
452 #ifdef MATCH_ARGUMENT
459 #ifdef COMPRESS_EXPOSE
464 XTextProperty wm_name, icon_name;
465 Atom wm_delete_window;
466 int has_fg_pixel = 0, has_bg_pixel = 0;
467 int has_colormap = 0;
468 unsigned long gc_mask;
469 unsigned long window_mask;
472 rop_name = DEFAULT_ROP;
473 wm_name.value = (unsigned char *) argv[0];
474 wm_name.encoding = XA_STRING;
476 wm_name.nitems = strlen ((const char *) wm_name.value) + 1;
480 if (!strcmp (*argv, "-display"))
482 else if (!strcmp (*argv, "-visual"))
483 vid = strtoul(*++argv, NULL, 0);
484 else if (!strcmp (*argv, "-cmap"))
486 colormap = strtoul(*++argv, NULL, 0);
489 else if (!strcmp (*argv, "-rop"))
491 else if (!strcmp (*argv, "-fg"))
493 else if (!strcmp (*argv, "-bg"))
495 else if (!strcmp (*argv, "-fg_pixel"))
497 fg_pixel = strtoul(*++argv, NULL, 0);
500 else if (!strcmp (*argv, "-bg_pixel"))
502 bg_pixel = strtoul(*++argv, NULL, 0);
505 else if (!strcmp (*argv, "-fn"))
507 else if (!strcmp (*argv, "-pm"))
508 planemask = strtoul(*++argv, NULL, 0);
509 else if (!strcmp (*argv, "-geometry"))
510 XParseGeometry (*++argv, &x, &y, &width, &height);
511 else if (!strcmp (*argv, "-sync"))
513 else if (!strcmp (*argv, "-bw"))
514 border_width = (unsigned int) strtoul(*++argv, NULL, 0);
515 else if (!strcmp (*argv, "-lw"))
516 line_width = strtod(*++argv, NULL);
517 else if (!strcmp (*argv, "-cap"))
519 else if (!strcmp (*argv, "-join"))
521 else if (!strcmp (*argv, "-fill"))
523 else if (!strcmp (*argv, "-linestyle"))
525 else if (!strcmp (*argv, "-bitmap"))
526 bitmap_file = *++argv;
527 else if (!strcmp (*argv, "-dashes"))
531 while (*argv && isdigit (**argv))
532 dashes[ndashes++] = (char) atoi(*argv++);
535 #ifdef MATCH_ARGUMENT
536 else if (i = MatchArgument (argv))
539 else if (!strcmp (*argv, "-root"))
540 root = strtoul (*++argv, NULL, 0);
545 wmHints.flags = InputHint;
546 wmHints.input = True;
547 dpy = XOpenDisplay (dpy_name);
549 Error ("Can't open display", "");
551 XSynchronize (dpy, sync);
552 screen = DefaultScreen (dpy);
554 root = RootWindow (dpy, screen);
555 window_mask = CWBackPixel|CWBorderPixel|CWEventMask;
557 colormap = DefaultColormap (dpy, screen);
560 window_mask |= CWColormap;
561 attr.colormap = colormap;
563 visual = DefaultVisual (dpy, screen);
564 depth = DefaultDepth (dpy, screen);
567 XVisualInfo vi, *vi_ret;
572 vi_ret = XGetVisualInfo (dpy, VisualIDMask|VisualScreenMask,
576 visual = vi_ret->visual;
579 colormap = XCreateColormap (dpy, root, visual, AllocNone);
580 window_mask |= CWColormap;
581 attr.colormap = colormap;
583 depth = vi_ret->depth;
587 fg_pixel = BlackPixel (dpy, screen);
589 bg_pixel = WhitePixel (dpy, screen);
592 if (!XAllocNamedColor (dpy, colormap, fg, &hard, &exact))
593 Error ("Can't allocate fg pixel %s", fg);
594 fg_pixel = hard.pixel;
598 if (!XAllocNamedColor (dpy, colormap, bg, &hard, &exact))
599 Error ("Can't allocate bg pixel %s", bg);
600 bg_pixel = hard.pixel;
602 attr.background_pixel = bg_pixel;
603 attr.border_pixel = fg_pixel;
605 ExposureMask|KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask|MOTION|EXTRA_EVENTS;
606 // attr.override_redirect = True;
607 // window_mask |= CWOverrideRedirect;
608 wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
609 win = XCreateWindow (dpy, root, x, y, width, height, border_width,
617 XSetWMProperties (dpy, win,
618 &wm_name, &icon_name,
620 &sizeHints, &wmHints, 0);
621 XSetWMProtocols (dpy, win, &wm_delete_window, 1);
622 default_rop = RopNameToRop (rop_name);
625 gcv.line_width = (int) line_width;
626 gc_mask |= GCLineWidth;
630 gcv.cap_style = MatchCapStyle (cap_name);
631 gc_mask |= GCCapStyle;
634 gcv.fill_style = MatchFillStyle (fill_name);
635 gc_mask |= GCFillStyle;
638 gcv.line_style = MatchLineStyle (line_name);
639 gc_mask |= GCLineStyle;
642 gcv.join_style = MatchJoinStyle (join_name);
643 gc_mask |= GCJoinStyle;
646 default_font = XLoadQueryFont (dpy, font_name);
648 gcv.font = default_font->fid;
651 gcv.function = default_rop;
652 gcv.foreground = fg_pixel;
653 gcv.background = bg_pixel;
654 gcv.plane_mask = planemask;
655 gc_mask |= GCFunction|GCForeground|GCBackground|GCPlaneMask;
658 unsigned int b_width, b_height;
660 Pixmap bitmap, pixmap;
662 ret = XReadBitmapFile (dpy, win, bitmap_file, &b_width, &b_height,
663 &bitmap, (int *) 0, (int *) 0);
665 case BitmapOpenFailed:
666 Error ("Can't open bitmap file %s", bitmap_file);
668 case BitmapFileInvalid:
669 Error ("Bitmap file %s invalid", bitmap_file);
672 Error ("Out of memory reading bitmap file %s", bitmap_file);
677 switch (gcv.fill_style) {
679 pixmap = XCreatePixmap (dpy, win, b_width, b_height, (unsigned) DefaultDepth (dpy, screen));
680 gc = XCreateGC (dpy, pixmap, GCForeground|GCBackground, &gcv);
681 XCopyPlane (dpy, bitmap, pixmap, gc, 0, 0, b_width, b_height, 0, 0, 1);
683 XFreePixmap (dpy, bitmap);
688 case FillOpaqueStippled:
689 gcv.stipple = bitmap;
690 gc_mask |= GCStipple;
694 gc = XCreateGC (dpy, win, gc_mask, &gcv);
696 default_font = XQueryFont (dpy, XGContextFromGC(gc));
698 XSetDashes (dpy, gc, 0, dashes, ndashes);
699 XMapWindow (dpy, win);
702 while (current_timeout && !XEventsQueued(dpy, QueuedAfterFlush)) {
703 struct pollfd pollfd = {
704 .fd = ConnectionNumber(dpy),
707 int r = poll(&pollfd, 1, current_timeout);
709 HandleTimeout(dpy, win, gc);
712 XNextEvent (dpy, &ev);
714 if (HasMotion && ev.type != MotionNotify) {
716 HandleMotionNotify (dpy, win, gc, &mev);
719 #ifdef COMPRESS_EXPOSE
720 if (HasExpose && ev.type != Expose) {
722 HandleExpose (dpy, eev.xexpose.window, gc);
727 #ifdef COMPRESS_EXPOSE
731 } else if (ev.xexpose.count == 0)
734 HandleExpose (dpy, ev.xexpose.window, gc);
738 HandleKeyPress (dpy, ev.xkey.window, gc, &ev);
741 HandleKeyRelease (dpy, ev.xkey.window, gc, &ev);
745 if (XLookupString ((XKeyEvent *) &ev, quit_string, sizeof (quit_string), 0, 0) == 1) {
746 switch (quit_string[0]) {
750 XClearArea (dpy, ev.xkey.window, 0, 0, 0, 0, True);
758 HandleButtonPress (dpy, ev.xbutton.window, gc, &ev);
761 HandleButtonRelease (dpy, ev.xbutton.window, gc, &ev);
768 HandleMotionNotify (dpy, ev.xmotion.window, gc, &ev);
776 HandleOtherEvent (dpy, win, gc, &ev);