[
  {
    "path": ".gitignore",
    "content": "highlight-pointer\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Sven Willner <sven.willner@yfx.de>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# highlight-pointer\n\nHighlight mouse pointer/cursor using a dot - useful for presentations,\nscreen sharing, ...\n\n(Walyand not supported, unfortunately. This is a small x11-only utility)\n\n## Demo\n\n![](demo.gif)\n\n## Features\n\n- Very lightweight, should work on any Linux/Unix system running an X\n  server\n- Should work with any software capturing/sharing the screen\n  regardless if it shows the cursor (like Zoom) or not (like Skype)\n- Set color for mouse button released and/or pressed state\n- Highlight using a filled or outlined dot\n- Auto-hide highlight and/or cursor after a time when not moving and\n  re-show when moving again\n- Global hotkeys for toggling cursor or highlighter and for toggling\n  auto-hiding\n\n## Installation\n\nDownload the `highlight-pointer` binary from the [releases\npage](https://github.com/swillner/highlight-pointer/releases/latest)\nor see below to build yourself.\n\n### Prerequisites\n\nTo build `highlight-pointer` you need the X11, Xext, Xfixes, and Xi\nlibraries. On Debian/Ubuntu, just install these using\n\n```\nsudo apt-get install libx11-dev libxext-dev libxfixes-dev libxi-dev\n```\n\n### Building\n\nJust build the `highlight-pointer` binary using\n\n```\nmake\n```\n\n## Usage\n\nJust call the `highlight-pointer` binary and include command line\noptions if you want to change color, size, etc. (see below).\n\nTo quit the program press `Ctrl+C` in the terminal where you started\nit, or run `killall highlight-pointer`.\n\n### Options\n\n```\nUsage:\n  highlight-pointer [options]\n\n  -h, --help      show this help message\n\nDISPLAY OPTIONS\n  -c, --released-color COLOR  dot color when mouse button released [default: '#d62728']\n  -p, --pressed-color COLOR   dot color when mouse button pressed [default: '#1f77b4']\n  -o, --outline OUTLINE       line width of outline or 0 for filled dot [default: 0]\n  -r, --radius RADIUS         dot radius in pixels [default: 5]\n      --opacity OPACITY       window opacity (0.0 - 1.0) [default: 1.0]\n      --hide-highlight        start with highlighter hidden\n      --show-cursor           start with cursor shown\n\nTIMEOUT OPTIONS\n      --auto-hide-cursor      hide cursor when not moving after timeout\n      --auto-hide-highlight   hide highlighter when not moving after timeout\n  -t, --hide-timeout TIMEOUT  timeout for hiding when idle, in seconds [default: 3]\n\nHOTKEY OPTIONS\n      --key-quit KEY                        quit\n      --key-toggle-cursor KEY               toggle cursor visibility\n      --key-toggle-highlight KEY            toggle highlight visibility\n      --key-toggle-auto-hide-cursor KEY     toggle auto-hiding cursor when not moving\n      --key-toggle-auto-hide-highlight KEY  toggle auto-hiding highlight when not moving\n\n      Hotkeys are global and can only be used if not set yet by a different process.\n      Keys can be given with modifiers\n        'S' (shift key), 'C' (ctrl key), 'M' (alt/meta key), 'H' (super/\"windows\" key)\n      delimited by a '-'.\n      Keys themselves are parsed by X, so chars like a...z can be set directly,\n      special keys are named as in /usr/include/X11/keysymdef.h\n      or see, e.g. http://xahlee.info/linux/linux_show_keycode_keysym.html\n\n      Examples: 'H-Left', 'C-S-a'\n```\n"
  },
  {
    "path": "highlight-pointer.c",
    "content": "/*\n  highlight-ponter\n\n  Highlight mouse pointer/cursor using a dot - useful for\n  presentations, screen sharing, ...\n\n  MIT License\n\n  Copyright (c) 2020 Sven Willner <sven.willner@yfx.de>\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in all\n  copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n  SOFTWARE.\n*/\n\n#include <X11/Xatom.h>\n#include <X11/Xlib.h>\n#include <X11/Xmd.h>\n#include <X11/extensions/XInput2.h>\n#include <X11/extensions/Xfixes.h>\n#include <X11/extensions/shape.h>\n#include <errno.h>\n#include <getopt.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/select.h>\n#include <unistd.h>\n\n#define TARGET_FPS 0\n\nstatic Display* dpy;\nstatic GC gc = 0;\nstatic Window win;\nstatic Window root;\nstatic int screen;\nstatic int selfpipe[2]; /* for self-pipe trick to cancel select() call */\n\n#define KEY_MODMAP_SIZE 4\nstatic struct {\n    char symbol;\n    unsigned int modifiers;\n} key_modifier_mapping[KEY_MODMAP_SIZE] = {\n    {'S', ShiftMask},   /* shift */\n    {'C', ControlMask}, /* control */\n    {'M', Mod1Mask},    /* alt/meta */\n    {'H', Mod4Mask}     /* super/\"windows\" */\n};\n\n#define KEY_OPTION_OFFSET 1000\n#define KEY_ARRAY_SIZE 5\nstruct {\n    KeySym keysym;\n    unsigned int modifiers;\n} keys[KEY_ARRAY_SIZE] = {\n#define KEY_QUIT 0\n    {NoSymbol, 0},\n#define KEY_TOGGLE_CURSOR 1\n    {NoSymbol, 0},\n#define KEY_TOGGLE_HIGHLIGHT 2\n    {NoSymbol, 0},\n#define KEY_TOGGLE_AUTOHIDE_CURSOR 3\n    {NoSymbol, 0},\n#define KEY_TOGGLE_AUTOHIDE_HIGHLIGHT 4\n    {NoSymbol, 0}};\n\nstatic unsigned int numlockmask = 0;\n\nstatic XColor pressed_color;\nstatic XColor released_color;\nstatic int button_pressed = 0;\nstatic int cursor_visible = 1;\nstatic int highlight_visible = 0;\n\nstatic struct {\n    char* pressed_color_string;\n    char* released_color_string;\n    int auto_hide_cursor;\n    int auto_hide_highlight;\n    int cursor_visible;\n    int hide_timeout;\n    int highlight_visible;\n    int outline;\n    int radius;\n    double opacity;\n} options;\n\nstatic void redraw();\nstatic int get_pointer_position(int* x, int* y);\n\nstatic void show_cursor() {\n    XFixesShowCursor(dpy, root);\n    cursor_visible = 1;\n}\n\nstatic void hide_cursor() {\n    XFixesHideCursor(dpy, root);\n    cursor_visible = 0;\n}\n\nstatic void show_highlight() {\n    int x, y;\n    int total_radius = options.radius + options.outline;\n    get_pointer_position(&x, &y);\n    XMoveWindow(dpy, win, x - total_radius - 1, y - total_radius - 1);\n    XMapWindow(dpy, win);\n    redraw();\n    highlight_visible = 1;\n}\n\nstatic void hide_highlight() {\n    XUnmapWindow(dpy, win);\n    highlight_visible = 0;\n}\n\nstatic int init_events() {\n    XIEventMask events;\n    unsigned char mask[(XI_LASTEVENT + 7) / 8];\n    memset(mask, 0, sizeof(mask));\n\n    XISetMask(mask, XI_RawButtonPress);\n    XISetMask(mask, XI_RawButtonRelease);\n    XISetMask(mask, XI_RawMotion);\n\n    events.deviceid = XIAllMasterDevices;\n    events.mask = mask;\n    events.mask_len = sizeof(mask);\n\n    XISelectEvents(dpy, root, &events, 1);\n\n    return 0;\n}\n\nstatic int get_pointer_position(int* x, int* y) {\n    Window w;\n    int i;\n    unsigned int ui;\n    return XQueryPointer(dpy, root, &w, &w, x, y, &i, &i, &ui);\n}\n\nstatic void set_window_mask() {\n    XGCValues gc_values;\n    int total_radius = options.radius + options.outline;\n    Pixmap mask = XCreatePixmap(dpy, win, 2 * total_radius + 2, 2 * total_radius + 2, 1);\n    GC mask_gc = XCreateGC(dpy, mask, 0, &gc_values);\n    XSetForeground(dpy, mask_gc, 0);\n    XFillRectangle(dpy, mask, mask_gc, options.outline, options.outline, 2 * total_radius + 2, 2 * total_radius + 2);\n\n    XSetForeground(dpy, mask_gc, 1);\n    if (options.outline) {\n        XSetLineAttributes(dpy, mask_gc, options.outline, LineSolid, CapButt, JoinBevel);\n        XDrawArc(dpy, mask, mask_gc, options.outline, options.outline, 2 * options.radius + 1, 2 * options.radius + 1, 0, 360 * 64);\n    } else {\n        XFillArc(dpy, mask, mask_gc, options.outline, options.outline, 2 * options.radius + 1, 2 * options.radius + 1, 0, 360 * 64);\n    }\n\n    XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, mask, ShapeSet);\n\n    XFreeGC(dpy, mask_gc);\n    XFreePixmap(dpy, mask);\n}\n\nstatic int init_window() {\n    int total_radius = options.radius + options.outline;\n    XSetWindowAttributes win_attributes;\n    win_attributes.event_mask = ExposureMask | VisibilityChangeMask;\n    win_attributes.override_redirect = True;\n\n    win = XCreateWindow(dpy, root, options.outline, options.outline, 2 * total_radius + 2, 2 * total_radius + 2, 0, DefaultDepth(dpy, screen), InputOutput,\n                        DefaultVisual(dpy, screen), CWEventMask | CWOverrideRedirect, &win_attributes);\n    if (!win) {\n        fprintf(stderr, \"Can't create highlight window\\n\");\n        return 1;\n    }\n\n    /* Set window opacity */\n    unsigned long opacity_value = (unsigned long)(options.opacity * 0xFFFFFFFF);\n    Atom opacity_atom = XInternAtom(dpy, \"_NET_WM_WINDOW_OPACITY\", False);\n    XChangeProperty(dpy, win, opacity_atom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&opacity_value, 1);\n\n    XClassHint class_hint;\n    XStoreName(dpy, win, \"highlight-pointer\");\n    class_hint.res_name = \"highlight-pointer\";\n    class_hint.res_class = \"HighlightPointer\";\n    XSetClassHint(dpy, win, &class_hint);\n\n    Atom window_type_atom = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE_DND\", False);\n    XChangeProperty(dpy, win, XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE\", False), XA_ATOM, 32, PropModeReplace, (unsigned char*)&window_type_atom, 1);\n\n    /* hide window decorations */\n    /* after https://github.com/akkana/moonroot */\n    Atom motif_wm_hints = XInternAtom(dpy, \"_MOTIF_WM_HINTS\", True);\n    struct {\n        CARD32 flags;\n        CARD32 functions;\n        CARD32 decorations;\n        INT32 input_mode;\n        CARD32 status;\n    } mwmhints;\n    mwmhints.flags = 1L << 1 /* MWM_HINTS_DECORATIONS */;\n    mwmhints.decorations = 0;\n    XChangeProperty(dpy, win, motif_wm_hints, motif_wm_hints, 32, PropModeReplace, (unsigned char*)&mwmhints, 5 /* PROP_MWM_HINTS_ELEMENTS */);\n\n    /* always stay on top */\n    /* after gdk_wmspec_change_state */\n    XClientMessageEvent xclient;\n    memset(&xclient, 0, sizeof(xclient));\n    xclient.type = ClientMessage;\n    xclient.window = win;\n    xclient.message_type = XInternAtom(dpy, \"_NET_WM_STATE\", False);\n    xclient.format = 32;\n    xclient.data.l[0] = 1 /* _NET_WM_STATE_ADD */;\n    xclient.data.l[1] = XInternAtom(dpy, \"_NET_WM_STATE_STAYS_ON_TOP\", False);\n    xclient.data.l[2] = 0;\n    xclient.data.l[3] = 1;\n    xclient.data.l[4] = 0;\n    XSendEvent(dpy, root, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&xclient);\n\n    /* let clicks fall through */\n    /* after https://stackoverflow.com/a/9279747 */\n    XRectangle rect;\n    XserverRegion region = XFixesCreateRegion(dpy, &rect, 1);\n    XFixesSetWindowShapeRegion(dpy, win, ShapeInput, 0, 0, region);\n    XFixesDestroyRegion(dpy, region);\n\n    XGCValues gc_values;\n    gc_values.foreground = WhitePixel(dpy, screen);\n    gc_values.background = BlackPixel(dpy, screen);\n    gc = XCreateGC(dpy, win, GCForeground | GCBackground, &gc_values);\n\n    set_window_mask();\n\n    return 0;\n}\n\nstatic void redraw() {\n    XSetForeground(dpy, gc, button_pressed ? pressed_color.pixel : released_color.pixel);\n    if (options.outline) {\n        XSetLineAttributes(dpy, gc, options.outline, LineSolid, CapButt, JoinBevel);\n        XDrawArc(dpy, win, gc, options.outline, options.outline, 2 * options.radius + 1, 2 * options.radius + 1, 0, 360 * 64);\n    } else {\n        XFillArc(dpy, win, gc, options.outline, options.outline, 2 * options.radius + 1, 2 * options.radius + 1, 0, 360 * 64);\n    }\n}\n\nstatic void quit() { write(selfpipe[1], \"\", 1); }\n\nstatic void handle_key(KeySym keysym, unsigned int modifiers) {\n    modifiers = modifiers & ~(numlockmask | LockMask);\n    int k;\n    for (k = 0; k < KEY_ARRAY_SIZE; ++k) {\n        if (keys[k].keysym == keysym && keys[k].modifiers == modifiers) {\n            break;\n        }\n    }\n    switch (k) {\n        case KEY_QUIT:\n            quit();\n            break;\n\n        case KEY_TOGGLE_CURSOR:\n            options.cursor_visible = 1 - options.cursor_visible;\n            if (options.cursor_visible && !cursor_visible) {\n                show_cursor();\n            } else if (!options.cursor_visible && cursor_visible) {\n                hide_cursor();\n            }\n            break;\n\n        case KEY_TOGGLE_HIGHLIGHT:\n            if (options.highlight_visible) {\n                hide_highlight();\n            } else {\n                show_highlight();\n            }\n            options.highlight_visible = 1 - options.highlight_visible;\n            break;\n\n        case KEY_TOGGLE_AUTOHIDE_CURSOR:\n            if (options.auto_hide_cursor && !cursor_visible) {\n                show_cursor();\n            }\n            options.auto_hide_cursor = 1 - options.auto_hide_cursor;\n            break;\n\n        case KEY_TOGGLE_AUTOHIDE_HIGHLIGHT:\n            if (options.auto_hide_highlight) {\n                show_highlight();\n            }\n            options.auto_hide_highlight = 1 - options.auto_hide_highlight;\n            break;\n    }\n}\n\nstatic void main_loop() {\n    XEvent ev;\n    fd_set fds;\n    int fd = ConnectionNumber(dpy);\n    struct timeval timeout;\n    int x, y, n;\n    int total_radius = options.radius + options.outline;\n    XGenericEventCookie* cookie;\n#if TARGET_FPS > 0\n    Time lasttime = 0;\n#endif\n\n    pipe(selfpipe);\n\n    while (1) {\n        XFlush(dpy);\n        FD_ZERO(&fds);\n        FD_SET(fd, &fds);\n        FD_SET(selfpipe[0], &fds);\n        timeout.tv_usec = 0;\n        timeout.tv_sec = options.hide_timeout;\n        n = select((fd > selfpipe[0] ? fd : selfpipe[0]) + 1, &fds, NULL, NULL, &timeout);\n        if (n < 0) {\n            if (errno != EINTR) {\n                perror(\"select() failed\");\n            }\n            break;\n        }\n        if (n > 0) {\n            if (FD_ISSET(selfpipe[0], &fds)) {\n                break;\n            }\n            while (XPending(dpy)) {\n                XNextEvent(dpy, &ev);\n\n                if (ev.type == GenericEvent) {\n                    cookie = &ev.xcookie;\n#if TARGET_FPS > 0\n                    if (!XGetEventData(dpy, cookie)) {\n                        continue;\n                    }\n                    const XIRawEvent* data = (const XIRawEvent*)cookie->data;\n                    if (data->time - lasttime <= 1000 / TARGET_FPS) {\n                        XFreeEventData(dpy, cookie);\n                        continue;\n                    }\n                    lasttime = data->time;\n                    XFreeEventData(dpy, cookie);\n#endif\n                    if (cookie->evtype == XI_RawMotion) {\n                        if (options.auto_hide_cursor && options.cursor_visible && !cursor_visible) {\n                            show_cursor();\n                        }\n                        if (options.auto_hide_highlight && options.highlight_visible && !highlight_visible) {\n                            show_highlight();\n                        } else if (highlight_visible) {\n                            get_pointer_position(&x, &y);\n                            XMoveWindow(dpy, win, x - total_radius - 1, y - total_radius - 1);\n                            /* unfortunately, this causes increase of the X server's cpu usage */\n                        }\n                        continue;\n                    }\n                    if (cookie->evtype == XI_RawButtonPress) {\n                        button_pressed = 1;\n                        redraw();\n                        continue;\n                    }\n                    if (cookie->evtype == XI_RawButtonRelease) {\n                        button_pressed = 0;\n                        redraw();\n                        continue;\n                    }\n                    continue;\n                }\n\n                if (ev.type == KeyPress) {\n                    KeySym keysym = XLookupKeysym(&ev.xkey, 0);\n                    if (keysym != NoSymbol) {\n                        handle_key(keysym, ev.xkey.state);\n                    }\n                    continue;\n                }\n                if (ev.type == Expose) {\n                    if (ev.xexpose.count < 1) {\n                        redraw();\n                    }\n                    continue;\n                }\n                if (ev.type == VisibilityNotify) {\n                    /* needed to deal with menus, etc. overlapping the hightlight win */\n                    XRaiseWindow(dpy, win);\n                    continue;\n                }\n            }\n        } else {\n            if (options.auto_hide_cursor && cursor_visible) {\n                hide_cursor();\n            }\n            if (options.auto_hide_highlight && highlight_visible) {\n                hide_highlight();\n            }\n        }\n    }\n}\n\nstatic int init_colors() {\n    int res;\n\n    Colormap colormap = DefaultColormap(dpy, screen);\n\n    res = XAllocNamedColor(dpy, colormap, options.pressed_color_string, &pressed_color, &pressed_color);\n    if (!res) {\n        fprintf(stderr, \"Can't allocate color: %s\\n\", options.pressed_color_string);\n        return 1;\n    }\n\n    res = XAllocNamedColor(dpy, colormap, options.released_color_string, &released_color, &released_color);\n    if (!res) {\n        fprintf(stderr, \"Can't allocate color: %s\\n\", options.released_color_string);\n        return 1;\n    }\n\n    return 0;\n}\n\nstatic int grab_keys() {\n    /* after https://git.suckless.org/dwm/file/dwm.c.html */\n    numlockmask = 0;\n    unsigned int numlockkeycode = XKeysymToKeycode(dpy, XK_Num_Lock);\n    if (numlockkeycode) {\n        XModifierKeymap* modmap = XGetModifierMapping(dpy);\n        for (int i = 0; i < 8; ++i) {\n            for (int j = 0; j < modmap->max_keypermod; ++j) {\n                if (modmap->modifiermap[i * modmap->max_keypermod + j] == numlockkeycode) {\n                    numlockmask = (1 << i);\n                }\n            }\n        }\n        XFreeModifiermap(modmap);\n    }\n\n    unsigned int modifiers[] = {0, LockMask, numlockmask, numlockmask | LockMask};\n    for (int i = 0; i < KEY_ARRAY_SIZE; ++i) {\n        if (keys[i].keysym != NoSymbol) {\n            KeyCode c = XKeysymToKeycode(dpy, keys[i].keysym);\n            if (!c) {\n                fprintf(stderr, \"Could not convert key to keycode\\n\");\n                return 1;\n            }\n            for (int j = 0; j < sizeof(modifiers) / sizeof(modifiers[0]); ++j) {\n                XGrabKey(dpy, c, keys[i].modifiers | modifiers[j], root, 1, GrabModeAsync, GrabModeAsync);\n            }\n        }\n    }\n    return 0;\n}\n\nstatic void sig_handler(int sig) {\n    (void)sig;\n    quit();\n}\n\nstatic int parse_key(const char* s, int k) {\n    keys[k].modifiers = 0;\n\n    int i;\n    while (s[0] != '\\0' && s[1] == '-') {\n        for (i = 0; i < KEY_MODMAP_SIZE; ++i) {\n            if (key_modifier_mapping[i].symbol == s[0]) {\n                keys[k].modifiers |= key_modifier_mapping[i].modifiers;\n                break;\n            }\n        }\n        if (i == KEY_MODMAP_SIZE) {\n            return 1;\n        }\n        s += 2;\n    }\n\n    keys[k].keysym = XStringToKeysym(s);\n    if (keys[k].keysym == NoSymbol) {\n        return 1;\n    }\n    return 0;\n}\n\nstatic void print_usage(const char* name) {\n    printf(\n        \"Usage:\\n\"\n        \"  %s [options]\\n\"\n        \"\\n\"\n        \"  -h, --help      show this help message\\n\"\n        \"\\n\"\n        \"DISPLAY OPTIONS\\n\"\n        \"  -c, --released-color COLOR  dot color when mouse button released [default: '#d62728']\\n\"\n        \"  -p, --pressed-color COLOR   dot color when mouse button pressed [default: '#1f77b4']\\n\"\n        \"  -o, --outline OUTLINE       line width of outline or 0 for filled dot [default: 0]\\n\"\n        \"  -r, --radius RADIUS         dot radius in pixels [default: 5]\\n\"\n        \"      --opacity OPACITY       window opacity (0.0 - 1.0) [default: 1.0]\\n\"\n        \"      --hide-highlight        start with highlighter hidden\\n\"\n        \"      --show-cursor           start with cursor shown\\n\"\n        \"\\n\"\n        \"TIMEOUT OPTIONS\\n\"\n        \"      --auto-hide-cursor      hide cursor when not moving after timeout\\n\"\n        \"      --auto-hide-highlight   hide highlighter when not moving after timeout\\n\"\n        \"  -t, --hide-timeout TIMEOUT  timeout for hiding when idle, in seconds [default: 3]\\n\"\n        \"\\n\"\n        \"HOTKEY OPTIONS\\n\"\n        \"      --key-quit KEY                        quit\\n\"\n        \"      --key-toggle-cursor KEY               toggle cursor visibility\\n\"\n        \"      --key-toggle-highlight KEY            toggle highlight visibility\\n\"\n        \"      --key-toggle-auto-hide-cursor KEY     toggle auto-hiding cursor when not moving\\n\"\n        \"      --key-toggle-auto-hide-highlight KEY  toggle auto-hiding highlight when not moving\\n\"\n        \"\\n\"\n        \"      Hotkeys are global and can only be used if not set yet by a different process.\\n\"\n        \"      Keys can be given with modifiers\\n\"\n        \"        'S' (shift key), 'C' (ctrl key), 'M' (alt/meta key), 'H' (super/\\\"windows\\\" key)\\n\"\n        \"      delimited by a '-'.\\n\"\n        \"      Keys themselves are parsed by X, so chars like a...z can be set directly,\\n\"\n        \"      special keys are named as in /usr/include/X11/keysymdef.h\\n\"\n        \"      or see, e.g. http://xahlee.info/linux/linux_show_keycode_keysym.html\\n\"\n        \"\\n\"\n        \"      Examples: 'H-Left', 'C-S-a'\\n\",\n        name);\n}\n\nstatic struct option long_options[] = {{\"auto-hide-cursor\", no_argument, &options.auto_hide_cursor, 1},\n                                       {\"auto-hide-highlight\", no_argument, &options.auto_hide_highlight, 1},\n                                       {\"help\", no_argument, NULL, 'h'},\n                                       {\"hide-highlight\", no_argument, &options.highlight_visible, 0},\n                                       {\"hide-timeout\", required_argument, NULL, 't'},\n                                       {\"outline\", required_argument, NULL, 'o'},\n                                       {\"pressed-color\", required_argument, NULL, 'p'},\n                                       {\"opacity\", required_argument, NULL, 'a'},\n                                       {\"radius\", required_argument, NULL, 'r'},\n                                       {\"released-color\", required_argument, NULL, 'c'},\n                                       {\"show-cursor\", no_argument, &options.cursor_visible, 1},\n                                       {\"key-quit\", required_argument, NULL, KEY_QUIT + KEY_OPTION_OFFSET},\n                                       {\"key-toggle-cursor\", required_argument, NULL, KEY_TOGGLE_CURSOR + KEY_OPTION_OFFSET},\n                                       {\"key-toggle-highlight\", required_argument, NULL, KEY_TOGGLE_HIGHLIGHT + KEY_OPTION_OFFSET},\n                                       {\"key-toggle-auto-hide-cursor\", required_argument, NULL, KEY_TOGGLE_AUTOHIDE_CURSOR + KEY_OPTION_OFFSET},\n                                       {\"key-toggle-auto-hide-highlight\", required_argument, NULL, KEY_TOGGLE_AUTOHIDE_HIGHLIGHT + KEY_OPTION_OFFSET},\n                                       {NULL, 0, NULL, 0}};\n\nstatic int set_options(int argc, char* argv[]) {\n    options.auto_hide_cursor = 0;\n    options.auto_hide_highlight = 0;\n    options.cursor_visible = 0;\n    options.highlight_visible = 1;\n    options.opacity = 1.0;\n    options.radius = 5;\n    options.outline = 0;\n    options.hide_timeout = 3;\n    options.pressed_color_string = \"#1f77b4\";\n    options.released_color_string = \"#d62728\";\n\n    while (1) {\n        int c = getopt_long(argc, argv, \"c:ho:p:r:t:\", long_options, NULL);\n        if (c < 0) {\n            break;\n        }\n        if (c >= KEY_OPTION_OFFSET && c < KEY_OPTION_OFFSET + KEY_ARRAY_SIZE) {\n            int res = parse_key(optarg, c - KEY_OPTION_OFFSET);\n            if (res) {\n                fprintf(stderr, \"Could not parse key value %s\\n\", optarg);\n                return 1;\n            }\n            continue;\n        }\n        switch (c) {\n            case 0:\n                break;\n            case 'a':\n                options.opacity = atof(optarg);\n                break;\n            case 'c':\n                options.released_color_string = optarg;\n                break;\n\n            case 'h':\n                print_usage(argv[0]);\n                return -1;\n\n            case 'o':\n                options.outline = atoi(optarg);\n                if (options.outline < 0) {\n                    fprintf(stderr, \"Invalid outline value %s\\n\", optarg);\n                    return 1;\n                }\n                break;\n\n            case 'p':\n                options.pressed_color_string = optarg;\n                break;\n\n            case 'r':\n                options.radius = atoi(optarg);\n                if (options.radius <= 0) {\n                    fprintf(stderr, \"Invalid radius value %s\\n\", optarg);\n                    return 1;\n                }\n                break;\n\n            case 't':\n                options.hide_timeout = atoi(optarg);\n                if (options.hide_timeout <= 0) {\n                    fprintf(stderr, \"Invalid timeout value %s\\n\", optarg);\n                    return 1;\n                }\n                break;\n\n            default:\n                print_usage(argv[0]);\n                return 1;\n        }\n    }\n    return 0;\n}\n\nstatic int xerror_handler(Display* dpy_p, XErrorEvent* err) {\n    if (err->request_code == 33 /* XGrabKey */ && err->error_code == BadAccess) {\n        fprintf(stderr, \"Key combination already grabbed by a different process\\n\");\n        exit(1);\n    }\n    if (err->error_code == BadAtom) {\n        fprintf(stderr, \"X warning: BadAtom for %d-%d\\n\", err->request_code, err->minor_code);\n        return 0;\n    }\n    char buf[1024];\n    XGetErrorText(dpy_p, err->error_code, buf, 1024);\n    fprintf(stderr, \"X error: %s (in %d-%d)\\n\", buf, err->request_code, err->minor_code);\n    exit(1);\n}\n\nint main(int argc, char* argv[]) {\n    int res;\n\n    res = set_options(argc, argv);\n    if (res < 0) {\n        return 0;\n    } else if (res > 0) {\n        return res;\n    }\n\n    dpy = XOpenDisplay(NULL); /* defaults to DISPLAY env var */\n    if (!dpy) {\n        fprintf(stderr, \"Can't open display\\n\");\n        return 1;\n    }\n    XSetErrorHandler(xerror_handler);\n    screen = DefaultScreen(dpy);\n    root = RootWindow(dpy, screen);\n\n    int event, error, opcode;\n    if (!XShapeQueryExtension(dpy, &event, &error)) {\n        fprintf(stderr, \"XShape extension not supported\\n\");\n        return 1;\n    }\n\n    if (!XQueryExtension(dpy, \"XInputExtension\", &opcode, &event, &error)) {\n        fprintf(stderr, \"XInput extension not supported\\n\");\n        return 1;\n    }\n\n    int major_version = 2;\n    int minor_version = 2;\n    res = XIQueryVersion(dpy, &major_version, &minor_version);\n    if (res == BadRequest) {\n        fprintf(stderr, \"XInput2 extension version 2.2 not supported\\n\");\n        return 1;\n    } else if (res != Success) {\n        fprintf(stderr, \"Can't query XInput version\\n\");\n        return 1;\n    }\n\n    res = init_window();\n    if (res) {\n        return res;\n    }\n\n    res = init_events();\n    if (res) {\n        return res;\n    }\n\n    res = init_colors();\n    if (res) {\n        return res;\n    }\n\n    res = grab_keys();\n    if (res) {\n        return res;\n    }\n\n    XAllowEvents(dpy, SyncBoth, CurrentTime);\n    XSync(dpy, False);\n\n    if (options.highlight_visible) {\n        show_highlight();\n    }\n\n    if (!options.cursor_visible) {\n        hide_cursor();\n    }\n\n    signal(SIGINT, sig_handler);\n    signal(SIGTERM, sig_handler);\n\n    main_loop();\n\n    if (!cursor_visible) {\n        show_cursor();\n    }\n    XUngrabKey(dpy, AnyKey, AnyModifier, root);\n    XUnmapWindow(dpy, win);\n    XFreeGC(dpy, gc);\n    XDestroyWindow(dpy, win);\n    XCloseDisplay(dpy);\n\n    return 0;\n}\n"
  },
  {
    "path": "makefile",
    "content": "highlight-pointer: highlight-pointer.c\n\t$(CC) $^ -o $@ -flto -O3 -Wall -Wextra -Wshadow -std=c99 -lX11 -lXext -lXfixes -lXi\n"
  }
]