1 /*
2  * http://code.arp242.net/find-cursor
3  * Copyright © 2015-2020 Martin Tournoij <martin@arp242.net>
4  * See below for full copyright
5  */
6 
7 #define _XOPEN_SOURCE 500
8 
9 #include <getopt.h>
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 
16 #include <X11/Xlib.h>
17 #include <X11/Xatom.h>
18 #include <X11/Xutil.h>
19 #include <X11/extensions/Xdamage.h>
20 #include <X11/extensions/Xrender.h>
21 #include <X11/extensions/shape.h>
22 
23 void usage(char *name);
24 int parse_num(int ch, char *opt, char *name);
25 void draw(char *name, Display *display, int screen,
26 	int size, int distance, int wait, int line_width, char *color_name,
27 	int follow, int transparent, int grow, int outline, char *ocolor_name,
28 	int repeat);
29 
30 static struct option longopts[] = {
31 	{"help",          no_argument,       NULL, 'h'},
32 	{"size",          required_argument, NULL, 's'},
33 	{"distance",      required_argument, NULL, 'd'},
34 	{"wait",          required_argument, NULL, 'w'},
35 	{"line-width",    required_argument, NULL, 'l'},
36 	{"color",         required_argument, NULL, 'c'},
37 	{"follow",        no_argument,       NULL, 'f'},
38 	{"transparent",   no_argument,       NULL, 't'},
39 	{"grow",          no_argument,       NULL, 'g'},
40 	{"outline",       optional_argument, NULL, 'o'}, // Optional for compat, as previously it was hard-coded to 2px.
41 	{"repeat",        required_argument, NULL, 'r'},
42 	{"outline-color", required_argument, NULL, 'O'},
43 	{NULL, 0, NULL, 0}
44 };
45 
usage(char * name)46 void usage(char *name) {
47 	printf("find-cursor highlights the cursor position by drawing circles around it.\n");
48 	printf("https://github.com/arp242/find-cursor\n");
49 	printf("\n");
50 	printf("Flags:\n");
51 	printf("  -h, --help          Show this help.\n");
52 	printf("\n");
53 	printf("Shape options:\n");
54 	printf("  -s, --size          Maximum size the circle will grow to in pixels.\n");
55 	printf("  -d, --distance      Distance between the circles in pixels.\n");
56 	printf("  -l, --line-width    Width of the lines in pixels.\n");
57 	printf("  -w, --wait          Time to wait before drawing the next circle in\n");
58 	printf("                      tenths of milliseconds.\n");
59 	printf("  -c, --color         Color as X11 color name or RGB (e.g. #ff0000)\n");
60 	printf("  -g, --grow          Grow the animation in size, rather than shrinking it.\n");
61 	printf("\n");
62 	printf("Extra options:\n");
63 	printf("  -f, --follow        Follow the cursor position as the cursor is moving.\n");
64 	printf("  -t, --transparent   Make the window truly 'transparent'. This helps with\n");
65 	printf("                      some display issues when following the cursor position,\n");
66 	printf("                      but it doesn't work well with all WMs, which is why\n");
67 	printf("                      it's disabled by default.\n");
68 	printf("  -o, --outline       Width in pixels of outline; uses 2px if no value is given.\n");
69 	printf("                      Helps visibility on all backgrounds.\n");
70 	printf("  -O, --outline-color Color of outline; if omitted it will automatically use\n");
71 	printf("                      the opposite color. No effect if -o isn't set.\n");
72 	printf("  -r, --repeat        Number of times to repeat the animation; use 0 to repeat\n");
73 	printf("                      indefinitely.\n");
74 	printf("\n");
75 	printf("Examples:\n");
76 	printf("  The defaults:\n");
77 	printf("    %s --size 320 --distance 40 --wait 400 --line-width 4 --color black\n\n", name);
78 	printf("  Draw a solid circle:\n");
79 	printf("    %s --size 100 --distance 1 --wait 20 --line-width 1\n\n", name);
80 	printf("  Always draw a full circle on top of the cursor:\n");
81 	printf("    %s --repeat 0 --follow --distance 1 --wait 1 --line-width 16 --size 16\n", name);
82 	printf("\n");
83 	printf("Launching:\n");
84 	printf("  You will want to map a key in your window manager to run find-cursor.\n");
85 	printf("  You can also use xbindkeys, which should work with any window manager.\n");
86 	printf("\n");
87 	printf("  I run it with xcape:\n");
88 	printf("       xcape -e 'Control_L=Escape;Shift_L=KP_Add'\n");
89 	printf("\n");
90 	printf("  When Left Shift is tapped a Keypad Add is sent; I configured my window\n");
91 	printf("  manager to launch find-cursor with that.\n");
92 	printf("\n");
93 	printf("  I don't have a numpad on my keyboard; you can also use F13 or some\n");
94 	printf("  other unused key.\n");
95 }
96 
97 // Parse number from commandline, or show error and exit if it's not a number.
parse_num(int ch,char * opt,char * name)98 int parse_num(int ch, char *opt, char *name) {
99 	char *end;
100 	long result = strtol(optarg, &end, 10);
101 	if (*end) {
102 		fprintf(stderr, "%s: %d must be a number\n\n", name, ch);
103 		usage(name);
104 		exit(1);
105 	}
106 	return result;
107 }
108 
main(int argc,char * argv[])109 int main(int argc, char* argv[]) {
110 	// Parse options
111 	int size = 320;
112 	int distance = 40;
113 	int wait = 400;
114 	int line_width = 4;
115 	char color_name[64] = "black";
116 	char ocolor_name[64];
117 	int follow = 0;
118 	int transparent = 0;
119 	int grow = 0;
120 	int outline = 0;
121 	int repeat = 0;
122 
123 	extern int optopt;
124 	int ch;
125 	while ((ch = getopt_long(argc, argv, ":hs:d:w:l:c:r:ftgo:O:", longopts, NULL)) != -1)
126 		switch (ch) {
127 		case 's':
128 			size = parse_num(ch, optarg, argv[0]);
129 			break;
130 		case 'd':
131 			distance = parse_num(ch, optarg, argv[0]);
132 			break;
133 		case 'w':
134 			wait = parse_num(ch, optarg, argv[0]);
135 			break;
136 		case 'l':
137 			line_width = parse_num(ch, optarg, argv[0]);
138 			break;
139 		case 'c':
140 			strncpy(color_name, optarg, sizeof(color_name));
141 			break;
142 		case 'h':
143 			usage(argv[0]);
144 			exit(0);
145 		case 'f':
146 			follow = 1;
147 			break;
148 		case 't':
149 			transparent = 1;
150 			break;
151 		case 'g':
152 			grow = 1;
153 			break;
154 		case 'o':
155 			outline = parse_num(ch, optarg, argv[0]);
156 			break;
157 		case 'O':
158 			strncpy(ocolor_name, optarg, sizeof(ocolor_name));
159 			break;
160 		case 'r':
161 			repeat = parse_num(ch, optarg, argv[0]);
162 			if (repeat == 0)
163 				repeat = -1;
164 			break;
165 		case ':':
166 			switch (optopt) {
167 			case 'o':
168 				outline = 2;
169 				break;
170 			default:
171 			    fprintf(stderr, "%s: missing required argument for -%c\n\n", argv[0], optopt);
172 			    usage(argv[0]);
173 			    exit(1);
174 			}
175 			break;
176 		case '?':
177 			fprintf(stderr, "%s: invalid option: -%c\n\n", argv[0], ch);
178 			// fallthrough
179 		default:
180 			usage(argv[0]);
181 			exit(1);
182 		}
183 
184 	// Setup display and such
185 	char *display_name = getenv("DISPLAY");
186 	if (!display_name) {
187 		fprintf(stderr, "%s: DISPLAY not set\n\n", argv[0]);
188 		exit(1);
189 	}
190 
191 	Display *display = XOpenDisplay(display_name);
192 	if (!display) {
193 		fprintf(stderr, "%s: cannot open display '%s'\n\n", argv[0], display_name);
194 		exit(1);
195 	}
196 	int screen = DefaultScreen(display);
197 
198 	int shape_event_base, shape_error_base;
199 	if (!XShapeQueryExtension(display, &shape_event_base, &shape_error_base)) {
200 		fprintf(stderr, "%s: no XShape extension for display '%s'\n\n", argv[0], display_name);
201 		exit(1);
202 	}
203 
204 	// Actually draw.
205 	do
206 		draw(argv[0], display, screen,
207 			size, distance, wait, line_width, color_name,
208 			follow, transparent, grow, outline, ocolor_name,
209 			repeat);
210 	while (repeat == -1 || repeat--);
211 
212 	XCloseDisplay(display);
213 }
214 
215 // Try to get the centre of the cursor.
cursor_center(Display * display,int size,int * x,int * y)216 void cursor_center(Display *display, int size, int *x, int *y) {
217 	XFixesCursorImage *c = XFixesGetCursorImage(display);
218 	*x = c->x - size/2 - c->width/2  + c->xhot;
219 	*y = c->y - size/2 - c->height/2 + c->yhot;
220 	XFree(c);
221 }
222 
draw(char * name,Display * display,int screen,int size,int distance,int wait,int line_width,char * color_name,int follow,int transparent,int grow,int outline,char * ocolor_name,int repeat)223 void draw(char *name, Display *display, int screen,
224 	int size, int distance, int wait, int line_width, char *color_name,
225 	int follow, int transparent, int grow, int outline, char *ocolor_name,
226 	int repeat
227 ) {
228 	// Get the mouse cursor position and size.
229 	int center_x, center_y;
230 	cursor_center(display, size, &center_x, &center_y);
231 
232 	// Create a window at the mouse position
233 	Window window;
234 	XSetWindowAttributes window_attr;
235 	window_attr.override_redirect = 1;
236 
237 	if (transparent) {
238 		XVisualInfo vinfo;
239 		XMatchVisualInfo(display, screen, 32, TrueColor, &vinfo);
240 		window_attr.colormap = XCreateColormap(display, DefaultRootWindow(display), vinfo.visual, AllocNone);
241 		window_attr.background_pixel = 0;
242 		window = XCreateWindow(display, XRootWindow(display, screen),
243 			center_x,                               // x position
244 			center_y,                               // y position
245 			size, size,                             // width, height
246 			4,                                      // border width
247 			vinfo.depth,                            // depth
248 			CopyFromParent,                         // class
249 			vinfo.visual,                           // visual
250 			CWColormap | CWBorderPixel | CWBackPixel | CWOverrideRedirect, // valuemask
251 			&window_attr);                          // attributes
252 	}
253 	else
254 		window = XCreateWindow(display, XRootWindow(display, screen),
255 			center_x,                              // x position
256 			center_y,                              // y position
257 			size, size,                            // width, height
258 			4,                                     // border width
259 			DefaultDepth(display, screen),         // depth
260 			CopyFromParent,                        // class
261 			DefaultVisual(display, screen),        // visual
262 			CWOverrideRedirect,                    // valuemask
263 			&window_attr);                         // attributes
264 
265 	// Make round shaped window.
266 	XGCValues xgcv;
267 	Pixmap shape_mask = XCreatePixmap(display, window, size, size, 1);
268 	GC part_shape = XCreateGC(display, shape_mask, 0, &xgcv);
269 	XSetForeground(display, part_shape, 0);
270 	XFillRectangle(display, shape_mask, part_shape, 0, 0, size, size);
271 	XSetForeground(display, part_shape, 1);
272 	XFillArc(display, shape_mask, part_shape,
273 		0, 0,         // x, y position
274 		size, size,   // Size
275 		0, 360 * 64); // Make it a full circle
276 	XFreeGC(display, part_shape);
277 
278 	XShapeCombineMask(display, window, ShapeBounding, 0, 0, shape_mask, ShapeSet);
279 	XShapeCombineMask(display, window, ShapeClip,     0, 0, shape_mask, ShapeSet);
280 	XFreePixmap(display, shape_mask);
281 
282 	// Input region is small so we can pass input events.
283 	XRectangle rect;
284 	XserverRegion region = XFixesCreateRegion(display, &rect, 1);
285 	XFixesSetWindowShapeRegion(display, window, ShapeInput, 0, 0, region);
286 	XFixesDestroyRegion(display, region);
287 
288 	// Set attrs
289 	XMapWindow(display, window);
290 	XStoreName(display, window, "find-cursor");
291 
292 	XClassHint *class = XAllocClassHint();
293 	class->res_name = "find-cursor";
294 	class->res_class = "find-cursor";
295 	XSetClassHint(display, window, class);
296 	XFree(class);
297 
298 	// Keep the window on top
299 	XEvent e;
300 	memset(&e, 0, sizeof(e));
301 	e.xclient.type = ClientMessage;
302 	e.xclient.message_type = XInternAtom(display, "_NET_WM_STATE", False);
303 	e.xclient.display = display;
304 	e.xclient.window = window;
305 	e.xclient.format = 32;
306 	e.xclient.data.l[0] = 1;
307 	e.xclient.data.l[1] = XInternAtom(display, "_NET_WM_STATE_STAYS_ON_TOP", False);
308 	XSendEvent(display, XRootWindow(display, screen), False, SubstructureRedirectMask, &e);
309 
310 	// Make sure compositing window managers don't apply shadows and such.
311 	// https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html
312 	// https://wiki.archlinux.org/index.php/Compton
313 	Atom type_util = XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
314 	Atom type = XInternAtom(display, "_NET_WM_WINDOW_TYPE_MENU", 0);
315 	XChangeProperty(display, window, type, XA_ATOM, 32, PropModeReplace,
316 		(unsigned char *)&type_util, 1);
317 
318 	XRaiseWindow(display, window);
319 	XFlush(display);
320 
321 	// Prepare to draw on this window
322 	XGCValues values = { .graphics_exposures = False };
323 	unsigned long valuemask = 0;
324 	GC gc = XCreateGC(display, window, valuemask, &values);
325 
326 	// Get colours.
327 	Colormap colormap = DefaultColormap(display, screen);
328 	XColor color;
329 	int err = XAllocNamedColor(display, colormap, color_name, &color, &color);
330 	if (err == 0) {
331 		fprintf(stderr, "%s: invalid color value for -c/--color: '%s'\n\n", name, color_name);
332 		usage(name);
333 		exit(1);
334 	}
335 
336 	XColor color2;
337 	if (outline > 0) {
338 		if (ocolor_name[0] == 0) { // Use opposite colour.
339 			color2.red   = 65535 - color.red;
340 			color2.green = 65535 - color.green;
341 			color2.blue  = 65535 - color.blue;
342 			sprintf(ocolor_name, "#%04X%04X%04X", color2.red, color2.green, color2.blue);
343 		}
344 		int err = XAllocNamedColor(display, colormap, ocolor_name, &color2, &color2);
345 		if (err == 0) {
346 			fprintf(stderr, "%s: invalid color value for -O/--outline-color: '%s'\n\n", name, ocolor_name);
347 			usage(name);
348 			exit(1);
349 		}
350 	}
351 	else {
352 		// Set colour only once if not outline.
353 		XSetLineAttributes(display, gc, line_width, LineSolid, CapButt, JoinBevel);
354 		XSetForeground(display, gc, color.pixel);
355 	}
356 
357 	// Draw the circles
358 	int i = 1;
359 	for (i=1; i<=size; i+=distance) {
360 		if (follow)
361 			XClearWindow(display, window);
362 
363 		int cs = grow ? i : size - i;
364 
365 		if (outline > 0) {
366 			XSetLineAttributes(display, gc, line_width+outline, LineSolid, CapButt, JoinBevel);
367 			XSetForeground(display, gc, color2.pixel);
368 			XDrawArc(display, window, gc,
369 				size/2 - cs/2, size/2 - cs/2, // x, y position
370 				cs, cs,                       // Size
371 				0, 360 * 64);                 // Make it a full circle
372 
373 			// Set color back for the normal circle.
374 			XSetLineAttributes(display, gc, line_width, LineSolid, CapButt, JoinBevel);
375 			XSetForeground(display, gc, color.pixel);
376 		}
377 
378 		XDrawArc(display, window, gc,
379 			size/2 - cs/2, size/2 - cs/2, // x, y position
380 			cs, cs,                       // Size
381 			0, 360 * 64);                 // Make it a full circle
382 
383 		if (follow) {
384 			cursor_center(display, size, &center_x, &center_y);
385 			XMoveWindow(display, window, center_x, center_y);
386 		}
387 
388 		XSync(display, False);
389 		usleep(wait * 100);
390 	}
391 
392 	XFreeGC(display, gc);
393 	XDestroyWindow(display, window);
394 }
395 
396 
397 /*
398  *  The MIT License (MIT)
399  *
400  *  Copyright © 2015-2020 Martin Tournoij
401  *
402  *  Permission is hereby granted, free of charge, to any person obtaining a copy
403  *  of this software and associated documentation files (the "Software"), to
404  *  deal in the Software without restriction, including without limitation the
405  *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
406  *  sell copies of the Software, and to permit persons to whom the Software is
407  *  furnished to do so, subject to the following conditions:
408  *
409  *  The above copyright notice and this permission notice shall be included in
410  *  all copies or substantial portions of the Software.
411  *
412  *  The software is provided "as is", without warranty of any kind, express or
413  *  implied, including but not limited to the warranties of merchantability,
414  *  fitness for a particular purpose and noninfringement. In no event shall the
415  *  authors or copyright holders be liable for any claim, damages or other
416  *  liability, whether in an action of contract, tort or otherwise, arising
417  *  from, out of or in connection with the software or the use or other dealings
418  *  in the software.
419  */
420