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, ¢er_x, ¢er_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, ¢er_x, ¢er_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