[
  {
    "path": ".gitignore",
    "content": "progman\nprogman_ini.h\n*.o\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright 2020 joshua stein <jcs@jcs.org>\nCopyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "#\n# Copyright 2020 joshua stein <jcs@jcs.org>\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n# OTHER DEALINGS IN THE SOFTWARE.\n#\n\nPREFIX?=\t/usr/local\nX11BASE?=\t/usr/X11R6\n\nPKGLIBS=\tx11 xft xext xpm\n\nCC?=\t\tcc\nCFLAGS+=\t-O2 -Wall -Wunused \\\n\t\t-Wunused -Wmissing-prototypes -Wstrict-prototypes \\\n\t\t-Wpointer-sign \\\n\t\t`pkg-config --cflags ${PKGLIBS}`\nLDFLAGS+=\t`pkg-config --libs ${PKGLIBS}`\n\n# use gdk-pixbuf to rescale icons; optional\nPKGLIBS+=\tgdk-pixbuf-xlib-2.0\nCFLAGS+=\t-DUSE_GDK_PIXBUF\n\n# enable debugging; optional\nCFLAGS+=\t-g\n#CFLAGS+=\t-g -DDEBUG=1\n\nBINDIR=\t\t$(PREFIX)/bin\n\nSRC=\t\tatom.c \\\n\t\tclient.c \\\n\t\tevents.c \\\n\t\tkeyboard.c \\\n\t\tlauncher.c \\\n\t\tmanage.c \\\n\t\tparser.c \\\n\t\tprogman.c \\\n\t\tutil.c\n\nOBJ=\t\t${SRC:.c=.o}\n\nBIN=\t\tprogman\n\nall: $(BIN)\n\n$(OBJ):\t\tprogman.h Makefile\nparser.o:\tprogman.h progman_ini.h\n\nprogman_ini.h: progman.ini\n\txxd -i progman.ini > $@ || (rm -f progman_ini.h; exit 1)\n\nprogman: $(OBJ)\n\t$(CC) -o $@ $(OBJ) $(LDFLAGS)\n\ninstall: all\n\tmkdir -p $(BINDIR) $(MANDIR)\n\tinstall -s $(BIN) $(BINDIR)\n\nclean:\n\trm -f $(BIN) $(OBJ) progman_ini.h\n\n.PHONY: all install clean\n"
  },
  {
    "path": "README.md",
    "content": "## progman\n\nprogman is a simple X11 window manager modeled after the Windows 3 era.\n\n![progman screenshot](https://jcs.org/images/progman-20200810.png)\n\n### License\n\nMIT\n\n### Compiling\n\nRun `make` to compile, and `make install` to install to `/usr/local` by\ndefault.\n\n### Features\n\n- Window minimizing, drawing icons and labels on the root/desktop\n- Window maximizing by double-clicking on a window titlebar, and full-screen\n  support (via `_NET_WM_STATE_FULLSCREEN`)\n- Window shading by right-clicking on a window titlebar\n- Window moving by holding down `Alt` (configurable) and clicking anywhere on a\n  window\n- Built-in keyboard binding support by adding items to the `[keyboard]`\n  section of `~/.config/progman/progman.ini` such as `Win+L = exec xlock`\n- Built-in mouse button binding on the desktop by adding items to the\n  `[desktop]` section of `~/.config/progman/progman.ini` such as\n  `Mouse3 = exec xterm`, with right-click setup by default to show a\n  configurable launcher menu containing programs listed in the `[launcher]`\n  section of `progman.ini`\n- Virtual desktops with keyboard shortcuts for switching between them bound\n  to `Alt+1` through `Alt+0` by default, and using the mouse wheel on the\n  desktop to scroll through virtual desktops\n- Window cycling with `Alt+Tab` and `Shift+Alt+Tab`\n- [Theme support](https://github.com/jcs/progman/tree/master/themes)\n- Optional HiDPI scaling support to magnify icons and buttons\n"
  },
  {
    "path": "atom.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <err.h>\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n#include <X11/Xutil.h>\n#include \"progman.h\"\n\nAtom kde_net_wm_window_type_override;\nAtom net_active_window;\nAtom net_client_list;\nAtom net_client_stack;\nAtom net_close_window;\nAtom net_cur_desk;\nAtom net_num_desks;\nAtom net_supported;\nAtom net_supporting_wm;\nAtom net_wm_desk;\nAtom net_wm_icon;\nAtom net_wm_icon_name;\nAtom net_wm_name;\nAtom net_wm_state;\nAtom net_wm_state_above;\nAtom net_wm_state_add;\nAtom net_wm_state_below;\nAtom net_wm_state_fs;\nAtom net_wm_state_mh;\nAtom net_wm_state_mv;\nAtom net_wm_state_rm;\nAtom net_wm_state_shaded;\nAtom net_wm_state_skipp;\nAtom net_wm_state_skipt;\nAtom net_wm_state_toggle;\nAtom net_wm_strut;\nAtom net_wm_strut_partial;\nAtom net_wm_type_desk;\nAtom net_wm_type_dock;\nAtom net_wm_type_menu;\nAtom net_wm_type_notif;\nAtom net_wm_type_splash;\nAtom net_wm_type_utility;\nAtom net_wm_wintype;\nAtom utf8_string;\nAtom wm_change_state;\nAtom wm_delete;\nAtom wm_protos;\nAtom wm_state;\nAtom xrootpmap_id;\n\nstatic char *get_string_atom(Window, Atom, Atom);\nstatic char *_get_wm_name(Window, int);\n\nvoid\nfind_supported_atoms(void)\n{\n\tnet_supported = XInternAtom(dpy, \"_NET_SUPPORTED\", False);\n\tutf8_string = XInternAtom(dpy, \"UTF8_STRING\", False);\n\twm_change_state = XInternAtom(dpy, \"WM_CHANGE_STATE\", False);\n\twm_delete = XInternAtom(dpy, \"WM_DELETE_WINDOW\", False);\n\twm_protos = XInternAtom(dpy, \"WM_PROTOCOLS\", False);\n\twm_state = XInternAtom(dpy, \"WM_STATE\", False);\n\tnet_wm_state_rm = 0;\n\tnet_wm_state_add = 1;\n\tnet_wm_state_toggle = 2;\n\n\txrootpmap_id = XInternAtom(dpy, \"_XROOTPMAP_ID\", False);\n\n\tkde_net_wm_window_type_override = XInternAtom(dpy,\n\t    \"_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\", False);\n\tappend_atoms(root, net_supported, XA_ATOM,\n\t    &kde_net_wm_window_type_override, 1);\n\n\tnet_active_window = XInternAtom(dpy, \"_NET_ACTIVE_WINDOW\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_active_window, 1);\n\n\tnet_client_list = XInternAtom(dpy, \"_NET_CLIENT_LIST\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_client_list, 1);\n\n\tnet_client_stack = XInternAtom(dpy, \"_NET_CLIENT_LIST_STACKING\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_client_stack, 1);\n\n\tnet_close_window = XInternAtom(dpy, \"_NET_CLOSE_WINDOW\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_close_window, 1);\n\n\tnet_cur_desk = XInternAtom(dpy, \"_NET_CURRENT_DESKTOP\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_cur_desk, 1);\n\n\tnet_num_desks = XInternAtom(dpy, \"_NET_NUMBER_OF_DESKTOPS\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_num_desks, 1);\n\n\tnet_supporting_wm = XInternAtom(dpy, \"_NET_SUPPORTING_WM_CHECK\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_supporting_wm, 1);\n\n\tnet_wm_desk = XInternAtom(dpy, \"_NET_WM_DESKTOP\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_desk, 1);\n\n\tnet_wm_icon = XInternAtom(dpy, \"_NET_WM_ICON\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_icon, 1);\n\n\tnet_wm_icon_name = XInternAtom(dpy, \"_NET_WM_ICON_NAME\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_icon_name, 1);\n\n\tnet_wm_name = XInternAtom(dpy, \"_NET_WM_NAME\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_name, 1);\n\n\tnet_wm_state = XInternAtom(dpy, \"_NET_WM_STATE\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_state, 1);\n\n\tnet_wm_state_above = XInternAtom(dpy, \"_NET_WM_STATE_ABOVE\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_state_above, 1);\n\n\tnet_wm_state_below = XInternAtom(dpy, \"_NET_WM_STATE_BELOW\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_state_below, 1);\n\n\tnet_wm_state_fs = XInternAtom(dpy, \"_NET_WM_STATE_FULLSCREEN\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_state_fs, 1);\n\n\tnet_wm_state_mh = XInternAtom(dpy, \"_NET_WM_STATE_MAXIMIZED_HORZ\",\n\t    False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_state_mh, 1);\n\n\tnet_wm_state_mv = XInternAtom(dpy, \"_NET_WM_STATE_MAXIMIZED_VERT\",\n\t    False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_state_mv, 1);\n\n\tnet_wm_state_shaded = XInternAtom(dpy, \"_NET_WM_STATE_SHADED\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_state_shaded, 1);\n\n\tnet_wm_strut = XInternAtom(dpy, \"_NET_WM_STRUT\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_strut, 1);\n\n\tnet_wm_strut_partial = XInternAtom(dpy, \"_NET_WM_STRUT_PARTIAL\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_strut_partial, 1);\n\n\tnet_wm_type_desk = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE_DESKTOP\",\n\t    False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_type_desk, 1);\n\n\tnet_wm_type_dock = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE_DOCK\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_type_dock, 1);\n\n\tnet_wm_type_menu = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE_MENU\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_type_menu, 1);\n\n\tnet_wm_type_notif = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE_NOTIFICATION\",\n\t    False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_type_utility, 1);\n\n\tnet_wm_type_splash = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE_SPLASH\",\n\t    False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_type_splash, 1);\n\n\tnet_wm_type_utility = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE_UTILITY\",\n\t    False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_type_utility, 1);\n\n\tnet_wm_wintype = XInternAtom(dpy, \"_NET_WM_WINDOW_TYPE\", False);\n\tappend_atoms(root, net_supported, XA_ATOM, &net_wm_wintype, 1);\n}\n\n/*\n * Despite the fact that all these are 32 bits on the wire, libX11 really does\n * stuff an array of longs into *data, so you get 64 bits on 64-bit archs. So\n * we gotta be careful here.\n */\n\nunsigned long\nget_atoms(Window w, Atom a, Atom type, unsigned long off, unsigned long *ret,\n    unsigned long nitems, unsigned long *left)\n{\n\tAtom real_type;\n\tint i, real_format = 0;\n\tunsigned long items_read = 0;\n\tunsigned long bytes_left = 0;\n\tunsigned long *p;\n\tunsigned char *data = NULL;\n\n\tignore_xerrors++;\n\tXGetWindowProperty(dpy, w, a, off, nitems, False, type, &real_type,\n\t    &real_format, &items_read, &bytes_left, &data);\n\tignore_xerrors--;\n\n\tif (real_format == 32 && items_read) {\n\t\tp = (unsigned long *)data;\n\t\tfor (i = 0; i < items_read; i++)\n\t\t\t*ret++ = *p++;\n\t\tif (left)\n\t\t\t*left = bytes_left;\n\t} else {\n\t\titems_read = 0;\n\t\tif (left)\n\t\t\t*left = 0;\n\t}\n\n\tif (data != NULL)\n\t\tXFree(data);\n\n\treturn items_read;\n}\n\nunsigned long\nset_atoms(Window w, Atom a, Atom type, unsigned long *val,\n    unsigned long nitems)\n{\n\treturn (XChangeProperty(dpy, w, a, type, 32, PropModeReplace,\n\t\t(unsigned char *) val, nitems) == Success);\n}\n\nunsigned long\nappend_atoms(Window w, Atom a, Atom type, unsigned long *val,\n    unsigned long nitems)\n{\n\treturn (XChangeProperty(dpy, w, a, type, 32, PropModeAppend,\n\t\t(unsigned char *) val, nitems) == Success);\n}\n\nvoid\nremove_atom(Window w, Atom a, Atom type, unsigned long remove)\n{\n\tunsigned long tmp, read, left, *new;\n\tint i, j = 0;\n\n\tread = get_atoms(w, a, type, 0, &tmp, 1, &left);\n\tif (!read)\n\t\treturn;\n\n\tnew = malloc((read + left) * sizeof(*new));\n\tif (new == NULL)\n\t\terr(1, \"malloc\");\n\tif (read && tmp != remove)\n\t\tnew[j++] = tmp;\n\n\tfor (i = 1, read = left = 1; read && left; i += read) {\n\t\tread = get_atoms(w, a, type, i, &tmp, 1, &left);\n\t\tif (!read)\n\t\t\tbreak;\n\t\tif (tmp != remove)\n\t\t\tnew[j++] = tmp;\n\t}\n\n\tif (j)\n\t\tXChangeProperty(dpy, w, a, type, 32, PropModeReplace,\n\t\t    (unsigned char *)new, j);\n\telse\n\t\tXDeleteProperty(dpy, w, a);\n\n\tfree(new);\n}\n\n/*\n * Get the window-manager name (aka human-readable \"title\") for a given window.\n * There are two ways a client can set this:\n *\n * 1. _NET_WM_STRING, which has type UTF8_STRING.\n * This is preferred and is always used if available.\n *\n * 2. WM_NAME, which has type COMPOUND_STRING or STRING.\n * This is the old ICCCM way, which we fall back to in the absence of\n * _NET_WM_STRING. In this case, we ask X to convert the value of the property\n * to UTF-8 for us. N.b.: STRING is Latin-1 whatever the locale.\n * COMPOUND_STRING is the most hideous abomination ever created. Thankfully we\n * do not have to worry about any of this.\n *\n * If UTF-8 conversion is not available (XFree86 < 4.0.2, or any older X\n * implementation), only WM_NAME will be checked, and, at least for XFree86 and\n * X.Org, it will only be returned if it has type STRING. This is due to an\n * inherent limitation in their implementation of XFetchName. If you have a\n * different X vendor, YMMV.\n *\n * In all cases, this function asks X to allocate the returned string, so it\n * must be freed with XFree.\n */\nchar *\n_get_wm_name(Window w, int icon)\n{\n\tchar *name;\n\tXTextProperty name_prop;\n\tXTextProperty name_prop_converted;\n\tchar **name_list;\n\tint nitems;\n\n\tif (icon) {\n\t\tif ((name = get_string_atom(w, net_wm_icon_name, utf8_string)))\n\t\t\treturn name;\n\t\tif (!XGetWMIconName(dpy, w, &name_prop))\n\t\t\treturn NULL;\n\t} else {\n\t\tif ((name = get_string_atom(w, net_wm_name, utf8_string)))\n\t\t\treturn name;\n\t\tif (!XGetWMName(dpy, w, &name_prop))\n\t\t\treturn NULL;\n\t}\n\n\tif (Xutf8TextPropertyToTextList(dpy, &name_prop, &name_list,\n\t    &nitems) == Success && nitems >= 1) {\n\t\t/*\n\t\t * Now we've got a freshly allocated XTextList. Since\n\t\t * it might have multiple items that need to be joined,\n\t\t * and we need to return something that can be freed by\n\t\t * XFree, we roll it back up into an XTextProperty.\n\t\t */\n\t\tif (Xutf8TextListToTextProperty(dpy, name_list, nitems,\n\t\t\tXUTF8StringStyle, &name_prop_converted) == Success) {\n\t\t\tXFreeStringList(name_list);\n\t\t\treturn (char *)name_prop_converted.value;\n\t\t}\n\n\t\t/*\n\t\t * Not much we can do here. This should never\n\t\t * happen anyway. Famous last words.\n\t\t */\n\t\tXFreeStringList(name_list);\n\t\treturn NULL;\n\t}\n\n\treturn (char *)name_prop.value;\n}\n\nchar *\nget_wm_name(Window w)\n{\n\treturn _get_wm_name(w, 0);\n}\n\nchar *\nget_wm_icon_name(Window w)\n{\n\treturn _get_wm_name(w, 1);\n}\n\n/*\n * I give up on trying to do this the right way. We'll just request as many\n * elements as possible. If that's not the entire string, we're fucked. In\n * reality this should never happen. (That's the second time I get to say \"this\n * should never happen\" in this file!)\n *\n * Standard gripe about casting nonsense applies.\n */\nstatic char *\nget_string_atom(Window w, Atom a, Atom type)\n{\n\tAtom real_type;\n\tint real_format = 0;\n\tunsigned long items_read = 0;\n\tunsigned long bytes_left = 0;\n\tunsigned char *data;\n\n\tXGetWindowProperty(dpy, w, a, 0, LONG_MAX, False, type,\n\t    &real_type, &real_format, &items_read, &bytes_left, &data);\n\n\t/* XXX: should check bytes_left here and bail if nonzero, in case\n\t * someone wants to store a >4gb string on the server */\n\n\tif (real_format == 8 && items_read >= 1)\n\t\treturn (char *)data;\n\n\treturn NULL;\n}\n\nvoid\nset_string_atom(Window w, Atom a, unsigned char *str, unsigned long len)\n{\n\tXChangeProperty(dpy, w, a, utf8_string, 8, PropModeReplace, str, len);\n}\n\n/*\n * Reads the _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT hint into the args, if it\n * exists. In the case of _NET_WM_STRUT_PARTIAL we cheat and only take the\n * first 4 values, because that's all we care about. This means we can use the\n * same code for both (_NET_WM_STRUT only specifies 4 elements). Each number is\n * the margin in pixels on that side of the display where we don't want to\n * place clients. If there is no hint, we act as if it was all zeros (no\n * margin).\n */\nint\nget_strut(Window w, strut_t *s)\n{\n\tAtom real_type;\n\tint real_format = 0;\n\tunsigned long items_read = 0;\n\tunsigned long bytes_left = 0;\n\tunsigned char *data;\n\tunsigned long *strut_data;\n\n\tXGetWindowProperty(dpy, w, net_wm_strut_partial, 0, 12, False,\n\t    XA_CARDINAL, &real_type, &real_format, &items_read, &bytes_left,\n\t    &data);\n\n\tif (!(real_format == 32 && items_read >= 12))\n\t\tXGetWindowProperty(dpy, w, net_wm_strut, 0, 4, False,\n\t\t    XA_CARDINAL, &real_type, &real_format, &items_read,\n\t\t    &bytes_left, &data);\n\n\tif (real_format == 32 && items_read >= 4) {\n\t\tstrut_data = (unsigned long *) data;\n\t\ts->left = strut_data[0];\n\t\ts->right = strut_data[1];\n\t\ts->top = strut_data[2];\n\t\ts->bottom = strut_data[3];\n\t\tXFree(data);\n\t\treturn 1;\n\t}\n\n\ts->left = 0;\n\ts->right = 0;\n\ts->top = 0;\n\ts->bottom = 0;\n\n\treturn 0;\n}\n\nunsigned long\nget_wm_state(Window w)\n{\n\tunsigned long state;\n\n\tif (get_atoms(w, wm_state, wm_state, 0, &state, 1, NULL))\n\t\treturn state;\n\n\treturn WithdrawnState;\n}\n"
  },
  {
    "path": "atom.h",
    "content": "/*\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef PROGMAN_ATOM_H\n#define PROGMAN_ATOM_H\n\n#include <X11/Xlib.h>\n#include <X11/Xatom.h>\n\nstruct strut {\n\tlong left;\n\tlong right;\n\tlong top;\n\tlong bottom;\n};\n\ntypedef struct strut strut_t;\n\nextern Atom kde_net_wm_window_type_override;\nextern Atom net_active_window;\nextern Atom net_client_list;\nextern Atom net_client_stack;\nextern Atom net_close_window;\nextern Atom net_cur_desk;\nextern Atom net_num_desks;\nextern Atom net_supported;\nextern Atom net_supporting_wm;\nextern Atom net_wm_desk;\nextern Atom net_wm_icon;\nextern Atom net_wm_icon_name;\nextern Atom net_wm_name;\nextern Atom net_wm_state;\nextern Atom net_wm_state_above;\nextern Atom net_wm_state_add;\nextern Atom net_wm_state_below;\nextern Atom net_wm_state_fs;\nextern Atom net_wm_state_mh;\nextern Atom net_wm_state_mv;\nextern Atom net_wm_state_rm;\nextern Atom net_wm_state_shaded;\nextern Atom net_wm_state_skipp;\nextern Atom net_wm_state_skipt;\nextern Atom net_wm_state_toggle;\nextern Atom net_wm_strut;\nextern Atom net_wm_strut_partial;\nextern Atom net_wm_type_desk;\nextern Atom net_wm_type_dock;\nextern Atom net_wm_type_menu;\nextern Atom net_wm_type_notif;\nextern Atom net_wm_type_splash;\nextern Atom net_wm_type_utility;\nextern Atom net_wm_wintype;\nextern Atom utf8_string;\nextern Atom wm_change_state;\nextern Atom wm_delete;\nextern Atom wm_protos;\nextern Atom wm_state;\nextern Atom xrootpmap_id;\n\nextern void find_supported_atoms(void);\nextern unsigned long get_atoms(Window, Atom, Atom, unsigned long,\n    unsigned long *, unsigned long, unsigned long *);\nextern unsigned long set_atoms(Window, Atom, Atom, unsigned long *,\n    unsigned long);\nextern unsigned long append_atoms(Window, Atom, Atom, unsigned long *,\n    unsigned long);\nextern void remove_atom(Window, Atom, Atom, unsigned long);\nextern char *get_wm_name(Window);\nextern char *get_wm_icon_name(Window);\nextern void set_string_atom(Window, Atom, unsigned char *, unsigned long);\nextern int get_strut(Window, strut_t *);\nextern unsigned long get_wm_state(Window);\n\n#endif\t/* PROGMAN_ATOM_H */\n"
  },
  {
    "path": "client.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <stdlib.h>\n#ifdef DEBUG\n#include <stdio.h>\n#endif\n#include <string.h>\n#include <err.h>\n#include <X11/Xatom.h>\n#include <X11/extensions/shape.h>\n#ifdef USE_GDK_PIXBUF\n#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>\n#endif\n#include \"progman.h\"\n#include \"atom.h\"\n\nstatic void init_geom(client_t *, strut_t *);\nstatic void reparent(client_t *, strut_t *);\nstatic void bevel(Window, geom_t, int);\nstatic void *word_wrap_xft(char *, char, XftFont *, int, int *);\n\n/*\n * Set up a client structure for the new (not-yet-mapped) window. We have to\n * ignore two unmap events if the client was already mapped but has IconicState\n * set (for instance, when we are the second window manager in a session).\n * That's because there's one for the reparent (which happens on all viewable\n * windows) and then another for the unmapping itself.\n */\nclient_t *\nnew_client(Window w)\n{\n\tclient_t *c;\n\tXWindowAttributes attr;\n\n\tc = malloc(sizeof *c);\n\tmemset(c, 0, sizeof(*c));\n\n\tc->name = get_wm_name(w);\n\tc->icon_name = get_wm_icon_name(w);\n\tc->win = w;\n\n\tupdate_size_hints(c);\n\tXGetTransientForHint(dpy, c->win, &c->trans);\n\n\tignore_xerrors++;\n\tXGetWindowAttributes(dpy, c->win, &attr);\n\tignore_xerrors--;\n\tc->geom.x = attr.x;\n\tc->geom.y = attr.y;\n\tc->geom.w = attr.width;\n\tc->geom.h = attr.height;\n\tc->cmap = attr.colormap;\n\tc->old_bw = attr.border_width;\n\n\tif (get_atoms(c->win, net_wm_desk, XA_CARDINAL, 0, &c->desk, 1, NULL)) {\n\t\tif (c->desk == -1)\n\t\t\tc->desk = DESK_ALL;\t/* FIXME */\n\t\tif (c->desk >= ndesks && c->desk != DESK_ALL)\n\t\t\tc->desk = cur_desk;\n\t} else {\n\t\tset_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk, 1);\n\t\tc->desk = cur_desk;\n\t}\n\n\t/*\n\t * We are not actually keeping the stack one in order. However, every\n\t * fancy panel uses it and nothing else, no matter what the spec says.\n\t * (I'm not sure why, as rearranging the list every time the stacking\n\t * changes would be distracting. GNOME's window list applet doesn't.)\n\t */\n\tappend_atoms(root, net_client_list, XA_WINDOW, &c->win, 1);\n\tappend_atoms(root, net_client_stack, XA_WINDOW, &c->win, 1);\n\n\tif (opt_drag_button)\n\t\t/* setup for mod+click dragging */\n\t\tXGrabButton(dpy, opt_drag_button, opt_drag_mod, c->win, True,\n\t\t    ButtonPressMask | ButtonReleaseMask, GrabModeAsync,\n\t\t    GrabModeAsync, None, move_curs);\n\n\tcheck_states(c);\n\n\tif (c->wm_hints)\n\t\tXFree(c->wm_hints);\n\tc->wm_hints = XGetWMHints(dpy, c->win);\n\tif (c->wm_hints && (c->wm_hints->flags & StateHint) &&\n\t    c->wm_hints->initial_state == IconicState)\n\t\tc->state = STATE_ICONIFIED;\n\n#ifdef DEBUG\n\tdump_name(c, __func__, \"\", c->name);\n\tdump_geom(c, c->geom, \"XGetWindowAttributes\");\n\tdump_info(c);\n#endif\n\n\treturn c;\n}\n\nclient_t *\nfind_client(Window w, int mode)\n{\n\tclient_t *c;\n\n\tfor (c = focused; c; c = c->next) {\n\t\tswitch (mode) {\n\t\tcase MATCH_ANY:\n\t\t\tif (w == c->frame || w == c->win || w == c->resize_nw ||\n\t\t\t    w == c->resize_w || w == c->resize_sw ||\n\t\t\t    w == c->resize_s || w == c->resize_se ||\n\t\t\t    w == c->resize_e || w == c->resize_ne ||\n\t\t\t    w == c->resize_n || w == c->titlebar ||\n\t\t\t    w == c->close || w == c->iconify || w == c->zoom ||\n\t\t\t    w == c->icon || w == c->icon_label)\n\t\t\t\treturn c;\n\t\t\tbreak;\n\t\tcase MATCH_FRAME:\n\t\t\tif (w == c->frame || w == c->resize_nw ||\n\t\t\t    w == c->resize_w || w == c->resize_sw ||\n\t\t\t    w == c->resize_s || w == c->resize_se ||\n\t\t\t    w == c->resize_e || w == c->resize_ne ||\n\t\t\t    w == c->resize_n || w == c->titlebar ||\n\t\t\t    w == c->close || w == c->iconify || w == c->zoom ||\n\t\t\t    w == c->icon || w == c->icon_label)\n\t\t\t\treturn c;\n\t\t\tbreak;\n\t\tcase MATCH_WINDOW:\n\t\t\tif (w == c->win)\n\t\t\t\treturn c;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nclient_t *\nfind_client_at_coords(Window w, int x, int y)\n{\n\tunsigned int nwins, i;\n\tWindow qroot, qparent, *wins;\n\tXWindowAttributes attr;\n\tclient_t *c, *foundc = NULL;\n\n\tXQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);\n\tfor (i = nwins - 1; i > 0; i--) {\n\t\tignore_xerrors++;\n\t\tXGetWindowAttributes(dpy, wins[i], &attr);\n\t\tignore_xerrors--;\n\t\tif (!(c = find_client(wins[i], MATCH_ANY)))\n\t\t\tcontinue;\n\n\t\tif (c->state & STATE_ICONIFIED) {\n\t\t\tif (x >= c->icon_geom.x &&\n\t\t\t    x <= c->icon_geom.x + c->icon_geom.w &&\n\t\t\t    y >= c->icon_geom.y &&\n\t\t\t    y <= c->icon_geom.y + c->icon_geom.h) {\n\t\t\t\tfoundc = c;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (x >= c->icon_label_geom.x &&\n\t\t\t    x <= c->icon_label_geom.x + c->icon_label_geom.w &&\n\t\t\t    y >= c->icon_label_geom.y &&\n\t\t\t    y <= c->icon_label_geom.y + c->icon_label_geom.h) {\n\t\t\t\tfoundc = c;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tif (x >= c->frame_geom.x &&\n\t\t\t    x <= c->frame_geom.x + c->frame_geom.w &&\n\t\t\t    y >= c->frame_geom.y &&\n\t\t\t    y <= c->frame_geom.y + c->frame_geom.h) {\n\t\t\t\tfoundc = c;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tXFree(wins);\n\n\treturn foundc;\n}\n\nclient_t *\ntop_client(void)\n{\n\tunsigned int nwins, i;\n\tWindow qroot, qparent, *wins;\n\tXWindowAttributes attr;\n\tclient_t *c, *foundc = NULL;\n\n\tXQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);\n\tfor (i = nwins - 1; i > 0; i--) {\n\t\tignore_xerrors++;\n\t\tXGetWindowAttributes(dpy, wins[i], &attr);\n\t\tignore_xerrors--;\n\t\tif ((c = find_client(wins[i], MATCH_FRAME)) &&\n\t\t    !(c->state & STATE_ICONIFIED)) {\n\t\t    \tfoundc = c;\n\t\t\tbreak;\n\t\t}\n\t}\n\tXFree(wins);\n\n\treturn foundc;\n}\n\nvoid\nmap_client(client_t *c)\n{\n\tstrut_t s = { 0 };\n\tint want_raise = 0;\n\n\tXGrabServer(dpy);\n\n\tcollect_struts(c, &s);\n\tinit_geom(c, &s);\n\n\t/* this also builds (but does not map) the frame windows */\n\treparent(c, &s);\n\n\tconstrain_frame(c);\n\n\tif (shape_support)\n\t\tset_shape(c);\n\n\tif (c->state & STATE_ICONIFIED) {\n\t\tc->ignore_unmap++;\n\t\tset_wm_state(c, IconicState);\n\t\tXUnmapWindow(dpy, c->win);\n\t\ticonify_client(c);\n\t} else {\n\t\t/* we're not allowing WithdrawnState */\n\t\tset_wm_state(c, NormalState);\n\t\tif (!((c->state & STATE_DOCK) ||\n\t\t    has_win_type(c, net_wm_type_notif)))\n\t\t\twant_raise = 1;\n\t}\n\n\tif (c->name)\n\t\tXFree(c->name);\n\tc->name = get_wm_name(c->win);\n\n\tif (c->icon_name)\n\t\tXFree(c->icon_name);\n\tc->icon_name = get_wm_icon_name(c->win);\n\n\tif (c->state & STATE_ICONIFIED) {\n\t\tXResizeWindow(dpy, c->win, c->geom.w, c->geom.h);\n\t\tsend_config(c);\n\t\tadjust_client_order(c, ORDER_ICONIFIED_TOP);\n\t} else {\n\t\t/* we haven't drawn anything yet, setup at the right place */\n\t\trecalc_frame(c);\n\t\tXMoveResizeWindow(dpy, c->frame,\n\t\t    c->frame_geom.x, c->frame_geom.y,\n\t\t    c->frame_geom.w, c->frame_geom.h);\n\t\tXMoveResizeWindow(dpy, c->win,\n\t\t    c->geom.x - c->frame_geom.x, c->geom.y - c->frame_geom.y,\n\t\t    c->geom.w, c->geom.h);\n\n\t\tif (want_raise) {\n\t\t\tXMapWindow(dpy, c->frame);\n\t\t\tXMapWindow(dpy, c->win);\n\t\t\tfocus_client(c, FOCUS_FORCE);\n\t\t} else {\n\t\t\tXMapWindow(dpy, c->frame);\n\t\t\tXMapWindow(dpy, c->win);\n\t\t\tadjust_client_order(c, ORDER_BOTTOM);\n\t\t\tredraw_frame(c, None);\n\t\t}\n\n\t\tsend_config(c);\n\t\tflush_expose_client(c);\n\t}\n\n\tXSync(dpy, False);\n\tXUngrabServer(dpy);\n}\n\nvoid\nupdate_size_hints(client_t *c)\n{\n\tlong supplied;\n\n\tXGetWMNormalHints(dpy, c->win, &c->size_hints, &supplied);\n\n\t/* Discard bogus hints */\n\tif ((c->size_hints.flags & PAspect) &&\n\t    (c->size_hints.min_aspect.x < 1 || c->size_hints.min_aspect.y < 1 ||\n\t    c->size_hints.max_aspect.x < 1 || c->size_hints.max_aspect.y < 1))\n\t\tc->size_hints.flags &= ~PAspect;\n\tif ((c->size_hints.flags & PMaxSize) && c->size_hints.max_width < 1)\n\t\tc->size_hints.flags &= ~PMaxSize;\n\tif ((c->size_hints.flags & PMinSize) && c->size_hints.min_width < 1)\n\t\tc->size_hints.flags &= ~PMinSize;\n\tif ((c->size_hints.flags & PResizeInc) &&\n\t    (c->size_hints.width_inc < 1 || c->size_hints.height_inc < 1))\n\t\tc->size_hints.flags &= ~PResizeInc;\n\tif ((c->size_hints.flags & (USSize | PSize)) &&\n\t    (c->size_hints.width < 1 || c->size_hints.height < 1))\n\t\tc->size_hints.flags &= ~(USSize|PSize);\n}\n\n/*\n * When we're ready to map, we have two things to consider: the literal\n * geometry of the window (what the client passed to XCreateWindow), and the\n * size hints (what they set with XSetWMSizeHints, if anything). Generally, the\n * client doesn't care, and leaves the literal geometry at +0+0. If the client\n * wants to be mapped in a particular place, though, they either set this\n * geometry to something different or set a size hint. The size hint is the\n * recommended method, and takes precedence. If there is already something in\n * c->geom, though, we just leave it.\n */\nstatic void\ninit_geom(client_t *c, strut_t *s)\n{\n#ifdef DEBUG\n\tgeom_t size_flags = { 0 };\n#endif\n\tunsigned long win_type, read, left;\n\tint screen_x = DisplayWidth(dpy, screen);\n\tint screen_y = DisplayHeight(dpy, screen);\n\tint wmax = screen_x - s->left - s->right;\n\tint hmax = screen_y - s->top - s->bottom;\n\tint mouse_x, mouse_y;\n\tint i;\n\n\tif (c->state & (STATE_ZOOMED | STATE_FULLSCREEN)) {\n\t\t/*\n\t\t * For zoomed windows, we'll adjust later to accommodate the\n\t\t * titlebar.\n\t\t */\n\t\tc->geom.x = s->top;\n\t\tc->geom.y = s->left;\n\t\tc->geom.w = wmax;\n\t\tc->geom.h = hmax;\n#ifdef DEBUG\n\t\tdump_geom(c, c->geom, \"init_geom zoom/fs\");\n#endif\n\t\treturn;\n\t}\n\n\t/*\n\t * If size/position hints are zero but the initial XGetWindowAttributes\n\t * reported non-zero, ignore these hint values\n\t */\n\tif (c->size_hints.width == 0 && c->geom.w != 0 &&\n\t    c->size_hints.height == 0 && c->geom.h != 0)\n\t\tc->size_hints.flags &= ~(USSize|PSize);\n\tif (c->size_hints.x == 0 && c->geom.x != 0 &&\n\t    c->size_hints.y == 0 && c->geom.y != 0)\n\t\tc->size_hints.flags &= ~(USPosition|PPosition);\n\n\t/*\n\t * Here, we merely set the values; they're in the same place regardless\n\t * of whether the user or the program specified them. We'll distinguish\n\t * between the two cases later, if we need to.\n\t */\n\tif (c->size_hints.flags & (USSize|PSize)) {\n\t\tif (c->size_hints.width >= 0)\n\t\t\tc->geom.w = c->size_hints.width;\n\t\tif (c->size_hints.height > 0)\n\t\t\tc->geom.h = c->size_hints.height;\n\n#ifdef DEBUG\n\t\tsize_flags.w = c->size_hints.width;\n\t\tsize_flags.h = c->size_hints.height;\n\t\tdump_geom(c, c->geom, \"init_geom size_hints w/h\");\n#endif\n\t}\n\n\tif (c->size_hints.flags & (USPosition | PPosition)) {\n\t\tif (c->size_hints.x >= 0)\n\t\t\tc->geom.x = c->size_hints.x;\n\t\tif (c->size_hints.y >= 0)\n\t\t\tc->geom.y = c->size_hints.y;\n#ifdef DEBUG\n\t\tsize_flags.x = c->size_hints.x;\n\t\tsize_flags.y = c->size_hints.y;\n\t\tdump_geom(c, c->geom, \"init_geom size_hints x/y\");\n#endif\n\t}\n\n#ifdef DEBUG\n\tif (c->size_hints.flags & (USSize | PSize | USPosition | PPosition))\n\t\tdump_geom(c, size_flags, \"init_geom size flags\");\n#endif\n\n\t/*\n\t * Several types of windows can put themselves wherever they want, but\n\t * we need to read the size hints to get that position before\n\t * returning.\n\t */\n\tfor (i = 0, left = 1; left; i += read) {\n\t\tread = get_atoms(c->win, net_wm_wintype, XA_ATOM, i, &win_type,\n\t\t    1, &left);\n\t\tif (!read)\n\t\t\tbreak;\n\t\tif (CAN_PLACE_SELF(win_type))\n\t\t\treturn;\n\t}\n\n\tif (!c->placed) {\n\t\tif (c->geom.x <= 0 && c->geom.y <= 0) {\n\t\t\t/* Place the window near the cursor */\n\t\t\tget_pointer(&mouse_x, &mouse_y);\n\t\t\trecalc_map(c, c->geom, mouse_x, mouse_y, mouse_x,\n\t\t\t    mouse_y, s, NULL);\n\t\t} else {\n\t\t\t/*\n\t\t\t * Place the window's frame where the window requested\n\t\t\t * to be\n\t\t\t */\n\t\t\trecalc_frame(c);\n\t\t\tc->geom.x += c->border_width;\n\t\t\tc->geom.y += c->border_width + c->titlebar_geom.h;\n\t\t}\n\t}\n\n\t/*\n\t * In any case, if we got this far, we need to do a further sanity\n\t * check and make sure that the window isn't overlapping any struts --\n\t * except for transients, because they might be a panel-type client\n\t * popping up a notification window over themselves.\n\t */\n\tif (c->geom.x + c->geom.w > screen_x - s->right)\n\t\tc->geom.x = screen_x - s->right - c->geom.w;\n\tif (c->geom.y + c->geom.h > screen_y - s->bottom)\n\t\tc->geom.y = screen_y - s->bottom - c->geom.h;\n\tif (c->geom.x < s->left || c->geom.w > wmax)\n\t\tc->geom.x = s->left;\n\tif (c->geom.y < s->top || c->geom.h > hmax)\n\t\tc->geom.y = s->top;\n\n\trecalc_frame(c);\n\n\t/* only move already-placed windows if they're off-screen */\n\tif (c->placed &&\n\t    (c->frame_geom.x < s->left || c->geom.y <= s->top)) {\n\t\tc->geom.x += (c->geom.x - c->frame_geom.x);\n\t\tc->geom.y += (c->geom.y - c->frame_geom.y);\n\t\trecalc_frame(c);\n\t}\n\n#ifdef DEBUG\n\tdump_geom(c, c->geom, __func__);\n#endif\n}\n\n/*\n * The frame window is not created until we actually do the reparenting here,\n * and thus the Xft surface cannot exist until this runs. Anything that has to\n * manipulate the client before we are called must make sure not to attempt to\n * use either.\n */\nstatic void\nreparent(client_t *c, strut_t *s)\n{\n\tXSetWindowAttributes pattr;\n\n\trecalc_frame(c);\n\n\tpattr.override_redirect = True;\n\tpattr.background_pixel = border_bg.pixel;\n\tpattr.event_mask = SubMask | ButtonPressMask | ButtonReleaseMask |\n\t    ExposureMask | EnterWindowMask;\n\tc->frame = XCreateWindow(dpy, root,\n\t    c->frame_geom.x, c->frame_geom.y,\n\t    c->frame_geom.w, c->frame_geom.h,\n\t    0,\n\t    DefaultDepth(dpy, screen), CopyFromParent,\n\t    DefaultVisual(dpy, screen),\n\t    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);\n\n\t/*\n\t * Init all windows to 1x1+1+1 because a width/height of 0 causes a\n\t * BadValue error.  redraw_frame moves them to the right size and\n\t * position anyway, and some of these never even get mapped/shown.\n\t */\n\n#define _(x,y,z) x##y##z\n#define CREATE_RESIZE_WIN(DIR) \\\n\tpattr.background_pixel = BlackPixel(dpy, screen); \\\n\tpattr.cursor = _(resize_,DIR,_curs); \\\n\t_(c->resize_,DIR,) = XCreateWindow(dpy, c->frame, 1, 1, 1, 1, \\\n\t    0, CopyFromParent, InputOutput, CopyFromParent, \\\n\t    CWOverrideRedirect | CWBackPixel | CWEventMask | CWCursor, \\\n\t    &pattr); \\\n\tXReparentWindow(dpy, _(c->resize_,DIR,), c->frame, \\\n\t    _(c->resize_,DIR,_geom.x), _(c->resize_,DIR,_geom.y));\n\n\tCREATE_RESIZE_WIN(nw);\n\tCREATE_RESIZE_WIN(n);\n\tCREATE_RESIZE_WIN(ne);\n\tCREATE_RESIZE_WIN(e);\n\tCREATE_RESIZE_WIN(se);\n\tCREATE_RESIZE_WIN(s);\n\tCREATE_RESIZE_WIN(sw);\n\tCREATE_RESIZE_WIN(w);\n#undef _\n#undef CREATE_RESIZE_WIN\n\n\t/* no CWCursor for these */\n\tc->close = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,\n\t    0, CopyFromParent, InputOutput, CopyFromParent,\n\t    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);\n\tXReparentWindow(dpy, c->close, c->frame, c->close_geom.x,\n\t    c->close_geom.y);\n\n\tc->titlebar = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,\n\t    0, CopyFromParent, InputOutput, CopyFromParent,\n\t    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);\n\tXReparentWindow(dpy, c->titlebar, c->frame, c->titlebar_geom.x,\n\t    c->titlebar_geom.y);\n\n\tc->iconify = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,\n\t    0, CopyFromParent, InputOutput, CopyFromParent,\n\t    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);\n\tXReparentWindow(dpy, c->iconify, c->frame, c->iconify_geom.x,\n\t    c->iconify_geom.y);\n\n\tc->zoom = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,\n\t    0, CopyFromParent, InputOutput, CopyFromParent,\n\t    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);\n\tXReparentWindow(dpy, c->zoom, c->frame, c->zoom_geom.x,\n\t    c->zoom_geom.y);\n\n\tc->xftdraw = XftDrawCreate(dpy, (Drawable)c->titlebar,\n\t    DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));\n\n\tif (shape_support)\n\t\tXShapeSelectInput(dpy, c->win, ShapeNotifyMask);\n\n\tXAddToSaveSet(dpy, c->win);\n\tXSelectInput(dpy, c->win, ColormapChangeMask | PropertyChangeMask);\n\tXSetWindowBorderWidth(dpy, c->win, 0);\n\tXReparentWindow(dpy, c->win, c->frame, c->resize_w_geom.w,\n\t    c->titlebar_geom.y + c->titlebar_geom.h + 1);\n}\n\nint\nhas_win_type(client_t *c, Atom type)\n{\n\tint i;\n\n\tfor (i = 0; i < (sizeof(c->win_type) / sizeof(c->win_type[0])); i++) {\n\t\tif (c->win_type[i] == type)\n\t\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nvoid\nrecalc_frame(client_t *c)\n{\n\tint buts = font->ascent + font->descent + (2 * opt_pad) + 2;\n\n\tif (buts < close_pm_attrs.width)\n\t    buts = close_pm_attrs.width;\n\n\tif (has_win_type(c, net_wm_type_dock) ||\n\t    has_win_type(c, net_wm_type_menu) ||\n\t    has_win_type(c, net_wm_type_splash) ||\n\t    has_win_type(c, net_wm_type_desk) ||\n\t    has_win_type(c, kde_net_wm_window_type_override))\n\t\tc->frame_style = FRAME_NONE;\n\telse if (has_win_type(c, net_wm_type_notif))\n\t\tc->frame_style = FRAME_BORDER;\n\telse if (has_win_type(c, net_wm_type_utility))\n\t\tc->frame_style = (FRAME_BORDER | FRAME_RESIZABLE |\n\t\t    FRAME_CLOSE | FRAME_TITLEBAR);\n\telse if (c->state & (STATE_DOCK | STATE_FULLSCREEN))\n\t\tc->frame_style = FRAME_NONE;\n\telse if (c->state & STATE_ZOOMED)\n\t\tc->frame_style = FRAME_ALL & ~(FRAME_BORDER | FRAME_RESIZABLE);\n\telse\n\t\tc->frame_style = FRAME_ALL;\n\n\tif ((c->size_hints.flags & PMinSize) &&\n\t    (c->size_hints.flags & PMaxSize) &&\n\t    c->size_hints.min_width == c->size_hints.max_width &&\n\t    c->size_hints.min_height == c->size_hints.max_height)\n\t\tc->frame_style &= ~(FRAME_RESIZABLE | FRAME_ZOOM |\n\t\t    FRAME_ICONIFY);\n\n\tif (c->frame_style & FRAME_BORDER)\n\t\tc->border_width = opt_bw + 2;\n\telse\n\t\tc->border_width = 0;\n\n\tif (has_win_type(c, net_wm_type_utility)) {\n\t\t/* use tiny titlebar with no window title */\n\t\tbuts = (2 * opt_pad) + 2;\n\t\tif (buts < utility_close_pm_attrs.width)\n\t\t    buts = utility_close_pm_attrs.width;\n\t\tif (c->frame_style & FRAME_RESIZABLE)\n\t\t\tc->border_width = (opt_bw / 2) + 2;\n\t}\n\n\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\tc->resize_nw_geom.x = 0;\n\t\tc->resize_nw_geom.y = 0;\n\t\tc->resize_nw_geom.w = c->border_width + buts;\n\t\tc->resize_nw_geom.h = c->border_width + buts;\n\t} else\n\t\tmemset(&c->resize_nw_geom, 0, sizeof(geom_t));\n\n\tif (c->frame_style & FRAME_CLOSE) {\n\t\tc->close_geom.x = c->border_width - 1;\n\t\tc->close_geom.y = c->border_width - 1;\n\t\tc->close_geom.w = buts + 1;\n\t\tc->close_geom.h = buts + 1;\n\t\tif (!(c->frame_style & FRAME_RESIZABLE)) {\n\t\t\tc->close_geom.x++;\n\t\t\tc->close_geom.y++;\n\t\t\tc->close_geom.h--;\n\t\t\tc->close_geom.w--;\n\t\t}\n\t} else\n\t\tmemset(&c->close_geom, 0, sizeof(geom_t));\n\n\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\tc->resize_n_geom.x = c->border_width + buts;\n\t\tc->resize_n_geom.y = 0;\n\t\tc->resize_n_geom.w = c->geom.w - buts - buts;\n\t\tc->resize_n_geom.h = c->border_width;\n\t} else\n\t\tmemset(&c->resize_n_geom, 0, sizeof(geom_t));\n\n\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\tc->resize_ne_geom.x = c->border_width + c->geom.w - buts;\n\t\tc->resize_ne_geom.y = 0;\n\t\tc->resize_ne_geom.w = c->border_width + buts;\n\t\tc->resize_ne_geom.h = c->border_width + buts;\n\t} else\n\t\tmemset(&c->resize_ne_geom, 0, sizeof(geom_t));\n\n\tif (c->frame_style & FRAME_ZOOM) {\n\t\tc->zoom_geom.x = c->border_width + c->geom.w - buts;\n\t\tc->zoom_geom.y = c->border_width - 1;\n\t\tc->zoom_geom.w = buts + 1;\n\t\tc->zoom_geom.h = buts + 1;\n\t} else\n\t\tmemset(&c->zoom_geom, 0, sizeof(geom_t));\n\n\tif (c->frame_style & FRAME_ICONIFY) {\n\t\tc->iconify_geom.x = c->border_width + c->geom.w - buts;\n\t\tc->iconify_geom.y = c->border_width - 1;\n\t\tc->iconify_geom.w = buts + 1;\n\t\tc->iconify_geom.h = buts + 1;\n\t\tif (c->frame_style & FRAME_ZOOM)\n\t\t\tc->iconify_geom.x -= c->zoom_geom.w - 1;\n\t} else\n\t\tmemset(&c->iconify_geom, 0, sizeof(geom_t));\n\n\tif (c->frame_style & FRAME_TITLEBAR) {\n\t\tc->titlebar_geom.x = c->border_width + c->close_geom.w;\n\t\tif (c->frame_style & FRAME_CLOSE)\n\t\t\tc->titlebar_geom.x--;\n\t\tc->titlebar_geom.y = c->border_width;\n\t\tc->titlebar_geom.w = c->geom.w;\n\t\tif (c->frame_style & FRAME_CLOSE)\n\t\t\tc->titlebar_geom.w -= c->close_geom.w - 1;\n\t\tif (c->frame_style & FRAME_ICONIFY)\n\t\t\tc->titlebar_geom.w -= c->iconify_geom.w - 2;\n\t\tif (c->frame_style & FRAME_ZOOM)\n\t\t\tc->titlebar_geom.w -= c->zoom_geom.w - 2;\n\t\tif ((c->frame_style & FRAME_ZOOM) &&\n\t\t    (c->frame_style & FRAME_ICONIFY))\n\t\t\tc->titlebar_geom.w++;\n\t\tc->titlebar_geom.h = buts;\n\t} else\n\t\tmemset(&c->titlebar_geom, 0, sizeof(geom_t));\n\n\tif ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) {\n\t\tc->resize_e_geom.x = c->border_width + c->geom.w;\n\t\tc->resize_e_geom.y = c->border_width + buts;\n\t\tc->resize_e_geom.w = c->border_width;\n\t\tif (c->frame_style & FRAME_TITLEBAR)\n\t\t\tc->resize_e_geom.h = c->geom.h - buts;\n\t\telse\n\t\t\tc->resize_e_geom.h = c->geom.h - buts - buts;\n\t} else\n\t\tmemset(&c->resize_e_geom, 0, sizeof(geom_t));\n\n\tif ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) {\n\t\tc->resize_se_geom.x = c->resize_ne_geom.x;\n\t\tc->resize_se_geom.y = c->resize_e_geom.y + c->resize_e_geom.h;\n\t\tc->resize_se_geom.w = c->border_width + buts;\n\t\tc->resize_se_geom.h = c->border_width + buts;\n\t} else\n\t\tmemset(&c->resize_se_geom, 0, sizeof(geom_t));\n\n\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\tif (c->state & STATE_SHADED) {\n\t\t\tc->resize_s_geom.x = 0;\n\t\t\tc->resize_s_geom.y = c->border_width + buts - 1;\n\t\t\tc->resize_s_geom.w = c->border_width + c->geom.w +\n\t\t\t    c->border_width;\n\t\t\tc->resize_s_geom.h = c->border_width;\n\t\t} else {\n\t\t\tc->resize_s_geom.x = c->resize_n_geom.x;\n\t\t\tc->resize_s_geom.y = c->resize_se_geom.y + buts;\n\t\t\tc->resize_s_geom.w = c->resize_n_geom.w;\n\t\t\tc->resize_s_geom.h = c->border_width;\n\t\t}\n\t} else\n\t\tmemset(&c->resize_s_geom, 0, sizeof(geom_t));\n\n\tif ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) {\n\t\tc->resize_sw_geom.x = 0;\n\t\tc->resize_sw_geom.y = c->resize_se_geom.y;\n\t\tc->resize_sw_geom.w = c->resize_se_geom.w;\n\t\tc->resize_sw_geom.h = c->resize_se_geom.h;\n\t} else\n\t\tmemset(&c->resize_sw_geom, 0, sizeof(geom_t));\n\n\tc->resize_w_geom.x = 0;\n\tc->resize_w_geom.y = c->resize_e_geom.y;\n\tc->resize_w_geom.w = c->resize_e_geom.w;\n\tc->resize_w_geom.h = c->resize_e_geom.h;\n\n\tc->frame_geom.x = c->geom.x - c->border_width;\n\tc->frame_geom.y = c->geom.y - c->border_width -\n\t    ((c->frame_style & FRAME_TITLEBAR) ? buts : 0);\n\tc->frame_geom.w = c->geom.w + c->border_width + c->border_width;\n\tif (c->state & STATE_SHADED)\n\t\tc->frame_geom.h = c->border_width + buts + c->border_width - 1;\n\telse\n\t\tc->frame_geom.h = c->geom.h + c->border_width +\n\t\t    ((c->frame_style & FRAME_TITLEBAR) ? buts : 0) +\n\t\t    c->border_width;\n}\n\nint\nset_wm_state(client_t *c, unsigned long state)\n{\n\treturn set_atoms(c->win, wm_state, wm_state, &state, 1);\n}\n\nvoid\ncheck_states(client_t *c)\n{\n\tAtom state;\n\tunsigned long read, left;\n\tint i;\n\n\t/* XXX: c->win is unmapped, we can't talk to it */\n\tif (c->state & STATE_ICONIFIED)\n\t\treturn;\n\n\tc->state = STATE_NORMAL;\n\tc->frame_style = FRAME_ALL;\n\n\tfor (i = 0; i < MAX_WIN_TYPE_ATOMS; i++) {\n\t\tif (get_atoms(c->win, net_wm_wintype, XA_ATOM, i,\n\t\t    &c->win_type[i], 1, &left)) {\n#ifdef DEBUG\n\t\t\tdump_name(c, __func__, \"wm_wintype\", XGetAtomName(dpy,\n\t\t\t    c->win_type[i]));\n#endif\n\t\t\tif (c->win_type[i] == net_wm_type_dock)\n\t\t\t\tc->state |= STATE_DOCK;\n\t\t}\n\n\t\tif (!left)\n\t\t\tbreak;\n\n\t\tif (left && i == MAX_WIN_TYPE_ATOMS - 1)\n\t\t\twarnx(\"client has too many _NET_WM_WINDOW_TYPE atoms\");\n\t}\n\n\tif (get_wm_state(c->win) == IconicState) {\n#ifdef DEBUG\n\t\tdump_name(c, __func__, \"wm_state\", \"IconicState\");\n#endif\n\t\tc->state |= STATE_ICONIFIED;\n\t\treturn;\n\t}\n\n\tfor (i = 0, left = 1; left; i += read) {\n\t\tread = get_atoms(c->win, net_wm_state, XA_ATOM, i, &state, 1,\n\t\t    &left);\n\t\tif (!read)\n\t\t\tbreak;\n#ifdef DEBUG\n\t\tdump_name(c, __func__, \"net_wm_state\", XGetAtomName(dpy,\n\t\t    state));\n#endif\n\t\tif (state == net_wm_state_shaded)\n\t\t\tc->state |= STATE_SHADED;\n\t\telse if (state == net_wm_state_mh || state == net_wm_state_mv)\n\t\t\tc->state |= STATE_ZOOMED;\n\t\telse if (state == net_wm_state_fs)\n\t\t\tc->state |= STATE_FULLSCREEN;\n\t\telse if (state == net_wm_state_above)\n\t\t\tc->state |= STATE_ABOVE;\n\t\telse if (state == net_wm_state_below)\n\t\t\tc->state |= STATE_BELOW;\n\t}\n}\n\n/* If we frob the geom for some reason, we need to inform the client. */\nvoid\nsend_config(client_t *c)\n{\n\tXConfigureEvent ce;\n\n\tce.type = ConfigureNotify;\n\tce.event = c->win;\n\tce.window = c->win;\n\tce.x = c->geom.x;\n\tce.y = c->geom.y;\n\tce.width = c->geom.w;\n\tce.height = c->geom.h;\n\tce.border_width = 0;\n\tce.above = None;\n\tce.override_redirect = 0;\n\n\tXSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);\n}\n\nvoid\nredraw_frame(client_t *c, Window only)\n{\n\tXftColor *txft;\n\tXGlyphInfo extents;\n\tPixmap *pm, *pm_mask;\n\tXpmAttributes *pm_attrs;\n\tint x, y, tw;\n\n\tif (!c || (c->frame_style == FRAME_NONE) || !c->frame)\n\t\treturn;\n\n\tif (!IS_ON_CUR_DESK(c))\n\t\treturn;\n\n\tif (c->state & STATE_ICONIFIED) {\n\t\tredraw_icon(c, only);\n\t\treturn;\n\t}\n\n#ifdef DEBUG\n\tdump_name(c, __func__, frame_name(c, only), c->name);\n#endif\n\n\trecalc_frame(c);\n\n\tif (only == None) {\n\t\tif ((c->frame_style & FRAME_BORDER) &&\n\t\t    !(c->frame_style & FRAME_RESIZABLE)) {\n\t\t\tif (c == focused)\n\t\t\t\tXSetWindowBackground(dpy, c->frame, bg.pixel);\n\t\t\telse\n\t\t\t\tXSetWindowBackground(dpy, c->frame,\n\t\t\t\t    unfocused_bg.pixel);\n\t\t\tXClearWindow(dpy, c->frame);\n\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    WhitePixel(dpy, screen));\n\t\t\tXDrawLine(dpy, c->frame, DefaultGC(dpy, screen),\n\t\t\t    c->border_width, c->border_width,\n\t\t\t    c->frame_geom.w - c->border_width,\n\t\t\t    c->border_width);\n\t\t\tXDrawLine(dpy, c->frame, DefaultGC(dpy, screen),\n\t\t\t    c->frame_geom.w - c->border_width - 1,\n\t\t\t    c->border_width,\n\t\t\t    c->frame_geom.w - c->border_width - 1,\n\t\t\t    c->border_width + c->titlebar_geom.h);\n\t\t} else\n\t\t\tXSetWindowBackground(dpy, c->frame,\n\t\t\t    BlackPixel(dpy, screen));\n\n\t\tXMoveResizeWindow(dpy, c->frame,\n\t\t    c->frame_geom.x, c->frame_geom.y,\n\t\t    c->frame_geom.w, c->frame_geom.h);\n\n\t\tif (c->state & STATE_SHADED)\n\t\t\t/* keep win just below our shaded frame */\n\t\t\tXMoveResizeWindow(dpy, c->win,\n\t\t\t    c->geom.x - c->frame_geom.x,\n\t\t\t    c->geom.y - c->frame_geom.y + c->border_width + 1,\n\t\t\t    c->geom.w, c->geom.h);\n\t\telse\n\t\t\tXMoveResizeWindow(dpy, c->win,\n\t\t\t    c->geom.x - c->frame_geom.x,\n\t\t\t    c->geom.y - c->frame_geom.y,\n\t\t\t    c->geom.w, c->geom.h);\n\n\t\tXSetForeground(dpy, DefaultGC(dpy, screen), border_fg.pixel);\n\t\tXDrawRectangle(dpy, c->frame, DefaultGC(dpy, screen),\n\t\t    0, 0, c->frame_geom.w - 1, c->frame_geom.h - 1);\n\t}\n\n\tif (only == None || only == c->titlebar) {\n\t\tif (c->frame_style & FRAME_TITLEBAR) {\n\t\t\tif (c == focused) {\n\t\t\t\ttxft = &xft_fg;\n\t\t\t\tXSetWindowBackground(dpy, c->titlebar,\n\t\t\t\t    bg.pixel);\n\t\t\t} else {\n\t\t\t\ttxft = &xft_fg_unfocused;\n\t\t\t\tXSetWindowBackground(dpy, c->titlebar,\n\t\t\t\t    unfocused_bg.pixel);\n\t\t\t}\n\t\t\tXMoveResizeWindow(dpy, c->titlebar,\n\t\t\t    c->titlebar_geom.x, c->titlebar_geom.y,\n\t\t\t    c->titlebar_geom.w, c->titlebar_geom.h);\n\t\t\tXMapWindow(dpy, c->titlebar);\n\t\t\tXClearWindow(dpy, c->titlebar);\n\n\t\t\tif (c->name && !has_win_type(c, net_wm_type_utility)) {\n\t\t\t\tXftTextExtentsUtf8(dpy, font,\n\t\t\t\t    (FcChar8 *)c->name, strlen(c->name),\n\t\t\t\t    &extents);\n\t\t\t\ttw = extents.xOff;\n\t\t\t\tx = opt_pad * 2;\n\n\t\t\t\tif (tw < (c->titlebar_geom.w - (opt_pad * 2)))\n\t\t\t\t\t/* center title */\n\t\t\t\t\tx = (c->titlebar_geom.w / 2) - (tw / 2);\n\n\t\t\t\ty = opt_pad + font->ascent;\n\n\t\t\t\tXftDrawStringUtf8(c->xftdraw, txft, font, x,\n\t\t\t\t    y, (unsigned char *)c->name,\n\t\t\t\t    strlen(c->name));\n\t\t\t}\n\t\t\tif (!(c->frame_style & FRAME_RESIZABLE) &&\n\t\t\t    (c->state & STATE_SHADED))\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    WhitePixel(dpy, screen));\n\t\t\telse\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    border_fg.pixel);\n\t\t\tXDrawLine(dpy, c->titlebar, DefaultGC(dpy, screen),\n\t\t\t    0, c->titlebar_geom.h - 1, c->titlebar_geom.w + 1,\n\t\t\t    c->titlebar_geom.h - 1);\n\n\t\t\tif ((c->frame_style & FRAME_BORDER) &&\n\t\t\t    !(c->frame_style & FRAME_RESIZABLE)) {\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    WhitePixel(dpy, screen));\n\t\t\t\tXDrawLine(dpy, c->titlebar,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    0, 0, c->titlebar_geom.w, 0);\n\t\t\t\tXDrawLine(dpy, c->titlebar,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    c->titlebar_geom.w - 1, 0,\n\t\t\t\t    c->titlebar_geom.w - 1,\n\t\t\t\t    c->titlebar_geom.h - 1);\n\t\t\t}\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->titlebar);\n\t}\n\n\tif (only == None || only == c->close) {\n\t\tif (c->frame_style & FRAME_CLOSE) {\n\t\t\tXSetWindowBackground(dpy, c->close, button_bg.pixel);\n\t\t\tXClearWindow(dpy, c->close);\n\t\t\tXMoveResizeWindow(dpy, c->close,\n\t\t\t    c->close_geom.x, c->close_geom.y, c->close_geom.w,\n\t\t\t    c->close_geom.h);\n\t\t\tXMapWindow(dpy, c->close);\n\n\t\t\tif (has_win_type(c, net_wm_type_utility)) {\n\t\t\t\tpm = &utility_close_pm;\n\t\t\t\tpm_mask = &utility_close_pm_mask;\n\t\t\t\tpm_attrs = &utility_close_pm_attrs;\n\t\t\t} else {\n\t\t\t\tpm = &close_pm;\n\t\t\t\tpm_mask = &close_pm_mask;\n\t\t\t\tpm_attrs = &close_pm_attrs;\n\t\t\t}\n\n\t\t\tx = (c->close_geom.w / 2) - (pm_attrs->width / 2);\n\t\t\ty = (c->close_geom.h / 2) - (pm_attrs->height / 2);\n\t\t\tXSetClipMask(dpy, pixmap_gc, *pm_mask);\n\t\t\tXSetClipOrigin(dpy, pixmap_gc, x, y);\n\t\t\tXCopyArea(dpy, *pm, c->close, pixmap_gc, 0, 0,\n\t\t\t    pm_attrs->width, pm_attrs->height, x, y);\n\t\t\tif (c->close_pressed)\n\t\t\t\tXCopyArea(dpy, c->close, c->close, invert_gc,\n\t\t\t\t    0, 0, c->close_geom.w, c->close_geom.h, 0,\n\t\t\t\t    0);\n\t\t\telse\n\t\t\t\tXCopyArea(dpy, c->close, c->close, pixmap_gc,\n\t\t\t\t    0, 0, c->close_geom.w, c->close_geom.h, 0,\n\t\t\t\t    0);\n\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->close,\n\t\t\t    DefaultGC(dpy, screen),\n\t\t\t    0, 0, c->close_geom.w - 1,\n\t\t\t    c->close_geom.h - 1);\n\n\t\t\tif ((c->frame_style & FRAME_BORDER) &&\n\t\t\t    !(c->frame_style & FRAME_RESIZABLE)) {\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    WhitePixel(dpy, screen));\n\t\t\t\tXDrawLine(dpy, c->close,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    0, 0, c->close_geom.w, 0);\n\t\t\t\tXDrawLine(dpy, c->close,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    0, 0, 0, c->close_geom.h - 1);\n\t\t\t}\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->close);\n\t}\n\n\tif (only == None || only == c->iconify) {\n\t\tif (c->frame_style & FRAME_ICONIFY) {\n\t\t\tXSetWindowBackground(dpy, c->iconify, button_bg.pixel);\n\t\t\tXClearWindow(dpy, c->iconify);\n\t\t\tXMoveResizeWindow(dpy, c->iconify,\n\t\t\t    c->iconify_geom.x, c->iconify_geom.y,\n\t\t\t    c->iconify_geom.w,\n\t\t\t    c->iconify_geom.h);\n\t\t\tXMapWindow(dpy, c->iconify);\n\t\t\tx = (c->iconify_geom.w / 2) -\n\t\t\t    (iconify_pm_attrs.width / 2) - (opt_bevel / 2);\n\t\t\ty = (c->iconify_geom.h / 2) -\n\t\t\t    (iconify_pm_attrs.height / 2) - (opt_bevel / 2);\n\t\t\tif (c->iconify_pressed) {\n\t\t\t\tx += 2;\n\t\t\t\ty += 2;\n\t\t\t}\n\t\t\tXSetClipMask(dpy, pixmap_gc, iconify_pm_mask);\n\t\t\tXSetClipOrigin(dpy, pixmap_gc, x, y);\n\t\t\tXCopyArea(dpy, iconify_pm, c->iconify, pixmap_gc, 0, 0,\n\t\t\t    iconify_pm_attrs.width, iconify_pm_attrs.height, x,\n\t\t\t    y);\n\t\t\tbevel(c->iconify, c->iconify_geom, c->iconify_pressed);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->iconify, DefaultGC(dpy, screen),\n\t\t\t    0, 0, c->iconify_geom.w - 1, c->iconify_geom.h - 1);\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->iconify);\n\t}\n\n\tif (only == None || only == c->zoom) {\n\t\tif (c->frame_style & FRAME_ZOOM) {\n\t\t\tXMoveResizeWindow(dpy, c->zoom,\n\t\t\t    c->zoom_geom.x, c->zoom_geom.y, c->zoom_geom.w,\n\t\t\t    c->zoom_geom.h);\n\t\t\tXSetWindowBackground(dpy, c->zoom, button_bg.pixel);\n\t\t\tXClearWindow(dpy, c->zoom);\n\t\t\tXMapWindow(dpy, c->zoom);\n\n\t\t\tif (c->state & STATE_ZOOMED) {\n\t\t\t\tpm = &unzoom_pm;\n\t\t\t\tpm_mask = &unzoom_pm_mask;\n\t\t\t\tpm_attrs = &unzoom_pm_attrs;\n\t\t\t} else {\n\t\t\t\tpm = &zoom_pm;\n\t\t\t\tpm_mask = &zoom_pm_mask;\n\t\t\t\tpm_attrs = &zoom_pm_attrs;\n\t\t\t}\n\n\t\t\tx = (c->zoom_geom.w / 2) - (pm_attrs->width / 2) -\n\t\t\t    (opt_bevel / 2);\n\t\t\ty = (c->zoom_geom.h / 2) - (pm_attrs->height / 2) -\n\t\t\t    (opt_bevel / 2);\n\t\t\tif (c->zoom_pressed) {\n\t\t\t\tx += 2;\n\t\t\t\ty += 2;\n\t\t\t}\n\t\t\tXSetClipMask(dpy, pixmap_gc, *pm_mask);\n\t\t\tXSetClipOrigin(dpy, pixmap_gc, x, y);\n\t\t\tXCopyArea(dpy, *pm, c->zoom, pixmap_gc, 0, 0,\n\t\t\t    pm_attrs->width, pm_attrs->height, x, y);\n\t\t\tbevel(c->zoom, c->zoom_geom, c->zoom_pressed);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->zoom, DefaultGC(dpy, screen),\n\t\t\t    0, 0, c->zoom_geom.w - 1, c->zoom_geom.h - 1);\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->zoom);\n\t}\n\n\tif (only == None || only == c->resize_nw) {\n\t\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\t\tXSetWindowBackground(dpy, c->resize_nw,\n\t\t\t    border_bg.pixel);\n\t\t\tXClearWindow(dpy, c->resize_nw);\n\t\t\tXMoveResizeWindow(dpy, c->resize_nw,\n\t\t\t    c->resize_nw_geom.x, c->resize_nw_geom.y,\n\t\t\t    c->resize_nw_geom.w, c->resize_nw_geom.h);\n\t\t\tXMapWindow(dpy, c->resize_nw);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_nw,\n\t\t\t    DefaultGC(dpy, screen), 0, 0,\n\t\t\t    c->resize_nw_geom.w - 1, c->resize_nw_geom.h - 1);\n\n\t\t\tif (!(c->frame_style & FRAME_CLOSE)) {\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    border_fg.pixel);\n\t\t\t\tXDrawRectangle(dpy, c->resize_nw,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    c->resize_n_geom.h - 1,\n\t\t\t\t    c->resize_n_geom.h - 1,\n\t\t\t\t    c->resize_nw_geom.w - 1,\n\t\t\t\t    c->resize_nw_geom.h - 1);\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    BlackPixel(dpy, screen));\n\t\t\t\tXFillRectangle(dpy, c->resize_nw,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    c->resize_n_geom.h,\n\t\t\t\t    c->resize_n_geom.h,\n\t\t\t\t    c->resize_nw_geom.h - 2,\n\t\t\t\t    c->resize_nw_geom.h - 2);\n\t\t\t}\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_nw);\n\t}\n\n\tif (only == None || only == c->resize_n) {\n\t\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\t\tXSetWindowBackground(dpy, c->resize_n, border_bg.pixel);\n\t\t\tXClearWindow(dpy, c->resize_n);\n\t\t\tXMoveResizeWindow(dpy, c->resize_n,\n\t\t\t    c->resize_n_geom.x, c->resize_n_geom.y,\n\t\t\t    c->resize_n_geom.w, c->resize_n_geom.h);\n\t\t\tXMapWindow(dpy, c->resize_n);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_n, DefaultGC(dpy, screen),\n\t\t\t    -1, 0, c->resize_n_geom.w + 1,\n\t\t\t    c->resize_n_geom.h - 1);\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_n);\n\t}\n\n\tif (only == None || only == c->resize_ne) {\n\t\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\t\tXSetWindowBackground(dpy, c->resize_ne,\n\t\t\t    border_bg.pixel);\n\t\t\tXMapWindow(dpy, c->resize_ne);\n\t\t\tXClearWindow(dpy, c->resize_ne);\n\t\t\tXMoveResizeWindow(dpy, c->resize_ne,\n\t\t\t    c->resize_ne_geom.x, c->resize_ne_geom.y,\n\t\t\t    c->resize_ne_geom.w, c->resize_ne_geom.h);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_ne,\n\t\t\t    DefaultGC(dpy, screen), 0, 0,\n\t\t\t    c->resize_ne_geom.w - 1, c->resize_ne_geom.h - 1);\n\t\t\tif (!(c->frame_style & (FRAME_ICONIFY | FRAME_ZOOM))) {\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    border_fg.pixel);\n\t\t\t\tXDrawRectangle(dpy, c->resize_ne,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    0, c->resize_n_geom.h - 1,\n\t\t\t\t    c->resize_ne_geom.w - c->resize_n_geom.h,\n\t\t\t\t    c->resize_ne_geom.h - 1);\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    BlackPixel(dpy, screen));\n\t\t\t\tXFillRectangle(dpy, c->resize_ne,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    0, c->resize_n_geom.h,\n\t\t\t\t    c->resize_ne_geom.w - c->resize_n_geom.h,\n\t\t\t\t    c->resize_ne_geom.h - 2);\n\t\t\t}\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_ne);\n\t}\n\n\tif (only == None || only == c->resize_e) {\n\t\tif ((c->frame_style & FRAME_RESIZABLE) &&\n\t\t    !(c->state & STATE_SHADED)) {\n\t\t\tXSetWindowBackground(dpy, c->resize_e, border_bg.pixel);\n\t\t\tXMapWindow(dpy, c->resize_e);\n\t\t\tXClearWindow(dpy, c->resize_e);\n\t\t\tXMoveResizeWindow(dpy, c->resize_e,\n\t\t\t    c->resize_e_geom.x, c->resize_e_geom.y,\n\t\t\t    c->resize_e_geom.w, c->resize_e_geom.h);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_e, DefaultGC(dpy, screen),\n\t\t\t    0, -1, c->resize_e_geom.w - 1,\n\t\t\t    c->resize_e_geom.h + 1);\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_e);\n\t}\n\n\tif (only == None || only == c->resize_se) {\n\t\tif ((c->frame_style & FRAME_RESIZABLE) &&\n\t\t    !(c->state & STATE_SHADED)) {\n\t\t\tXSetWindowBackground(dpy, c->resize_se,\n\t\t\t    border_bg.pixel);\n\t\t\tXClearWindow(dpy, c->resize_se);\n\t\t\tXMoveResizeWindow(dpy, c->resize_se,\n\t\t\t    c->resize_se_geom.x, c->resize_se_geom.y,\n\t\t\t    c->resize_se_geom.w, c->resize_se_geom.h);\n\t\t\tXMapWindow(dpy, c->resize_se);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_se,\n\t\t\t    DefaultGC(dpy, screen), 0, 0,\n\t\t\t    c->resize_se_geom.w - 1,\n\t\t\t    c->resize_se_geom.h - 1);\n\t\t\tXDrawRectangle(dpy, c->resize_se,\n\t\t\t    DefaultGC(dpy, screen), 0, 0,\n\t\t\t    c->resize_se_geom.w - c->resize_s_geom.h,\n\t\t\t    c->resize_se_geom.h - c->resize_s_geom.h);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    BlackPixel(dpy, screen));\n\t\t\tXFillRectangle(dpy, c->resize_se,\n\t\t\t    DefaultGC(dpy, screen), 0, 0,\n\t\t\t    c->resize_se_geom.w - c->resize_s_geom.h,\n\t\t\t    c->resize_se_geom.h - c->resize_s_geom.h);\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_se);\n\t}\n\n\tif (only == None || only == c->resize_s) {\n\t\tif (c->frame_style & FRAME_RESIZABLE) {\n\t\t\tXSetWindowBackground(dpy, c->resize_s, border_bg.pixel);\n\t\t\tXClearWindow(dpy, c->resize_s);\n\t\t\tXMoveResizeWindow(dpy, c->resize_s,\n\t\t\t    c->resize_s_geom.x, c->resize_s_geom.y,\n\t\t\t    c->resize_s_geom.w, c->resize_s_geom.h);\n\t\t\tXMapWindow(dpy, c->resize_s);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_s, DefaultGC(dpy, screen),\n\t\t\t    0, 0, c->resize_s_geom.w,\n\t\t\t    c->resize_s_geom.h - 1);\n\n\t\t\tif (c->state & STATE_SHADED) {\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    border_bg.pixel);\n\t\t\t\tXDrawLine(dpy, c->resize_s,\n\t\t\t\t    DefaultGC(dpy, screen), 1, 0,\n\t\t\t\t    c->resize_s_geom.h - 1, 0);\n\t\t\t\tXDrawLine(dpy, c->resize_s,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    c->resize_s_geom.w - c->resize_s_geom.h + 1,\n\t\t\t\t    0, c->resize_s_geom.w - 1, 0);\n\n\t\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t\t    border_fg.pixel);\n\t\t\t\tXDrawLine(dpy, c->resize_s,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    c->resize_sw_geom.w, 0,\n\t\t\t\t    c->resize_sw_geom.w, c->resize_s_geom.h);\n\t\t\t\tXDrawLine(dpy, c->resize_s,\n\t\t\t\t    DefaultGC(dpy, screen),\n\t\t\t\t    c->resize_s_geom.w - c->resize_sw_geom.w -\n\t\t\t\t    1, 0,\n\t\t\t\t    c->resize_s_geom.w - c->resize_sw_geom.w -\n\t\t\t\t    1, c->resize_s_geom.h);\n\t\t\t}\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_s);\n\t}\n\n\tif (only == None || only == c->resize_sw) {\n\t\tif ((c->frame_style & FRAME_RESIZABLE) &&\n\t\t    !(c->state & STATE_SHADED)) {\n\t\t\tXSetWindowBackground(dpy, c->resize_sw,\n\t\t\t    border_bg.pixel);\n\t\t\tXClearWindow(dpy, c->resize_sw);\n\t\t\tXMoveResizeWindow(dpy, c->resize_sw,\n\t\t\t    c->resize_sw_geom.x, c->resize_sw_geom.y,\n\t\t\t    c->resize_sw_geom.w, c->resize_sw_geom.h);\n\t\t\tXMapWindow(dpy, c->resize_sw);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_sw,\n\t\t\t    DefaultGC(dpy, screen), 0, 0, c->resize_sw_geom.w,\n\t\t\t    c->resize_sw_geom.h - 1);\n\t\t\tXDrawRectangle(dpy, c->resize_sw,\n\t\t\t    DefaultGC(dpy, screen),\n\t\t\t    c->resize_w_geom.w - 1, 0,\n\t\t\t    c->resize_sw_geom.w,\n\t\t\t    c->resize_sw_geom.h - c->resize_s_geom.h);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    BlackPixel(dpy, screen));\n\t\t\tXFillRectangle(dpy, c->resize_sw,\n\t\t\t    DefaultGC(dpy, screen), c->resize_w_geom.w, 0,\n\t\t\t    c->resize_sw_geom.w,\n\t\t\t    c->resize_sw_geom.h - c->resize_s_geom.h);\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_sw);\n\t}\n\n\tif (only == None || only == c->resize_w) {\n\t\tif ((c->frame_style & FRAME_RESIZABLE) &&\n\t\t    !(c->state & STATE_SHADED)) {\n\t\t\tXSetWindowBackground(dpy, c->resize_w, border_bg.pixel);\n\t\t\tXClearWindow(dpy, c->resize_w);\n\t\t\tXMoveResizeWindow(dpy, c->resize_w,\n\t\t\t    c->resize_w_geom.x, c->resize_w_geom.y,\n\t\t\t    c->resize_w_geom.w, c->resize_w_geom.h);\n\t\t\tXMapWindow(dpy, c->resize_w);\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    border_fg.pixel);\n\t\t\tXDrawRectangle(dpy, c->resize_w, DefaultGC(dpy, screen),\n\t\t\t    0, -1, c->resize_w_geom.w - 1,\n\t\t\t    c->resize_w_geom.h + 1);\n\t\t} else\n\t\t\tXUnmapWindow(dpy, c->resize_w);\n\t}\n}\n\nstatic void\nbevel(Window win, geom_t geom, int pressed)\n{\n\tint x;\n\n\tXSetForeground(dpy, DefaultGC(dpy, screen), bevel_dark.pixel);\n\n\tif (pressed) {\n\t\tfor (x = 0; x < opt_bevel - 1; x++) {\n\t\t\tXDrawLine(dpy, win, DefaultGC(dpy, screen),\n\t\t\t    x, x, geom.w - x, x);\n\t\t\tXDrawLine(dpy, win, DefaultGC(dpy, screen),\n\t\t\t    x, x, x, geom.h - x);\n\t\t}\n\t} else {\n\t\tfor (x = 1; x <= opt_bevel; x++) {\n\t\t\tXDrawLine(dpy, win, DefaultGC(dpy, screen),\n\t\t\t    geom.w - 1 - x, x,\n\t\t\t    geom.w - 1 - x, geom.h - 1);\n\t\t\tXDrawLine(dpy, win, DefaultGC(dpy, screen),\n\t\t\t    x, geom.h - 1 - x,\n\t\t\t    geom.w - 1, geom.h - x - 1);\n\t\t}\n\n\t\tXSetForeground(dpy, DefaultGC(dpy, screen), bevel_light.pixel);\n\t\tfor (x = 1; x <= opt_bevel - 1; x++) {\n\t\t\tXDrawLine(dpy, win, DefaultGC(dpy, screen),\n\t\t\t    1, x,\n\t\t\t    geom.w - 1 - x, x);\n\t\t\tXDrawLine(dpy, win, DefaultGC(dpy, screen),\n\t\t\t    x, 1,\n\t\t\t    x, geom.h - 1 - x);\n\t\t}\n\t}\n}\n\nvoid\nget_client_icon(client_t *c)\n{\n\tWindow junkw;\n\tunsigned long w, h;\n\tunsigned int depth;\n\tint junki;\n\n\t/* try through atom */\n\tif (get_atoms(c->win, net_wm_icon, XA_CARDINAL, 0, &w, 1, NULL) &&\n\t    get_atoms(c->win, net_wm_icon, XA_CARDINAL, 1, &h, 1, NULL)) {\n\t    \t/* TODO */\n\t}\n\n\tif (c->icon_managed) {\n\t\tif (c->icon_pixmap)\n\t\t\tXFreePixmap(dpy, c->icon_pixmap);\n\t\tif (c->icon_mask)\n\t\t\tXFreePixmap(dpy, c->icon_mask);\n\t\tc->icon_managed = 0;\n\t}\n\n\t/* fallback to WMHints */\n\tif (c->wm_hints)\n\t\tXFree(c->wm_hints);\n\tc->wm_hints = XGetWMHints(dpy, c->win);\n\tif (!c->wm_hints || !(c->wm_hints->flags & IconPixmapHint)) {\n\t\tc->icon_pixmap = default_icon_pm;\n\t\tc->icon_depth = DefaultDepth(dpy, screen);\n\t\tc->icon_mask = default_icon_pm_mask;\n\t\treturn;\n\t}\n\n\tXGetGeometry(dpy, c->wm_hints->icon_pixmap, &junkw, &junki, &junki,\n\t    (unsigned int *)&c->icon_geom.w, (unsigned int *)&c->icon_geom.h,\n\t    (unsigned int *)&junki, &depth);\n\tc->icon_pixmap = c->wm_hints->icon_pixmap;\n\tc->icon_depth = depth;\n\n\tif (c->wm_hints->flags & IconMaskHint)\n\t\tc->icon_mask = c->wm_hints->icon_mask;\n\telse\n\t\tc->icon_mask = None;\n\n#ifdef USE_GDK_PIXBUF\n\tif (c->icon_geom.w > icon_size || c->icon_geom.h > icon_size) {\n\t\tGdkPixbuf *gp, *mask, *scaled;\n\t\tint sh, sw;\n\n\t\tif (c->icon_geom.w > c->icon_geom.h) {\n\t\t\tsw = icon_size;\n\t\t\tsh = (icon_size / (double)c->icon_geom.w) *\n\t\t\t    c->icon_geom.h;\n\t\t} else {\n\t\t\tsh = icon_size;\n\t\t\tsw = (icon_size / (double)c->icon_geom.h) *\n\t\t\t    c->icon_geom.w;\n\t\t}\n\n\t\tgp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,\n\t\t    c->icon_geom.w, c->icon_geom.h);\n\n\t\tif (!gdk_pixbuf_xlib_get_from_drawable(gp, c->icon_pixmap,\n\t\t    c->cmap, DefaultVisual(dpy, screen), 0, 0, 0, 0,\n\t\t    c->icon_geom.w, c->icon_geom.h)) {\n\t\t\twarnx(\"failed to load pixmap with gdk pixbuf\");\n\t\t\tg_object_unref(gp);\n\t\t\treturn;\n\t\t}\n\n\t\t/* manually mask image, ugh */\n\t\tif (c->icon_mask != None) {\n\t\t\tguchar *px, *pxm;\n\t\t\tint rs, rsm, dm, ch, x, y;\n\n\t\t\tmask = gdk_pixbuf_xlib_get_from_drawable(NULL,\n\t\t\t    c->icon_mask, c->cmap, DefaultVisual(dpy, screen),\n\t\t\t    0, 0, 0, 0, c->icon_geom.w, c->icon_geom.h);\n\t\t\tif (!mask) {\n\t\t\t\twarnx(\"failed to load mask with gdk pixbuf\");\n\t\t\t\tg_object_unref(gp);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tpx = gdk_pixbuf_get_pixels(gp);\n\t\t\tpxm = gdk_pixbuf_get_pixels(mask);\n\t\t\trs = gdk_pixbuf_get_rowstride(gp);\n\t\t\trsm = gdk_pixbuf_get_rowstride(mask);\n\t\t\tdm = gdk_pixbuf_get_bits_per_sample(mask);\n\t\t\tch = gdk_pixbuf_get_n_channels(mask);\n\n\t\t\tfor (y = 0; y < c->icon_geom.h; y++) {\n\t\t\t\tguchar *tr = px + (y * rs);\n\t\t\t\tguchar *trm = pxm + (y * rsm);\n\t\t\t\tfor (x = 0; x < c->icon_geom.w; x++) {\n\t\t\t\t\tguchar al = 0xff;\n\t\t\t\t\tswitch (dm) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tal = trm[x * ch / 8];\n\t\t\t\t\t\tal >>= (x % 8);\n\t\t\t\t\t\tal = al ? 0xff : 0;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 8:\n\t\t\t\t\t\tal = (trm[(x * ch) + 2]) ? 0xff\n\t\t\t\t\t\t    : 0;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\ttr[(x * 4) + 3] = al;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tg_object_unref(mask);\n\t\t}\n\n\t\tscaled = gdk_pixbuf_scale_simple(gp, sw, sh,\n\t\t    GDK_INTERP_BILINEAR);\n\t\tif (!scaled) {\n\t\t\twarnx(\"failed to scale icon with gdk pixbuf\");\n\t\t\tg_object_unref(gp);\n\t\t\treturn;\n\t\t}\n\n\t\tif (c->icon_managed) {\n\t\t\tif (c->icon_pixmap)\n\t\t\t\tXFreePixmap(dpy, c->icon_pixmap);\n\t\t\tif (c->icon_mask)\n\t\t\t\tXFreePixmap(dpy, c->icon_mask);\n\t\t}\n\n\t\tgdk_pixbuf_xlib_render_pixmap_and_mask(scaled,\n\t\t    &c->icon_pixmap, &c->icon_mask, 1);\n\t\tc->icon_geom.w = sw;\n\t\tc->icon_geom.h = sh;\n\t\tc->icon_managed = 1;\n\n\t\tg_object_unref(scaled);\n\t\tg_object_unref(gp);\n\t}\n#endif\n}\n\nvoid\nredraw_icon(client_t *c, Window only)\n{\n\tXftColor *txft;\n\tvoid *xft_lines;\n\tint label_pad = 2 * opt_scale;\n\tint nlines, x;\n\n#ifdef DEBUG\n\tdump_name(c, __func__, frame_name(c, only), c->name);\n#endif\n\n\tif (only == None || only == c->icon) {\n\t\tXClearWindow(dpy, c->icon);\n\t\tXSetWindowBackground(dpy, c->icon, WhitePixel(dpy, screen));\n\t\tXMoveResizeWindow(dpy, c->icon,\n\t\t    c->icon_geom.x + ((icon_size - c->icon_geom.w) / 2),\n\t\t    c->icon_geom.y + ((icon_size - c->icon_geom.h) / 2),\n\t\t    c->icon_geom.w, c->icon_geom.h);\n\t\tif (c->icon_mask) {\n\t\t\tXShapeCombineMask(dpy, c->icon, ShapeBounding, 0, 0,\n\t\t\t    c->icon_mask, ShapeSet);\n\t\t\tXSetClipMask(dpy, c->icon_gc, c->icon_mask);\n\t\t\tXSetClipOrigin(dpy, c->icon_gc, 0, 0);\n\t\t}\n\t\tif (c->icon_depth == DefaultDepth(dpy, screen))\n\t\t\tXCopyArea(dpy, c->icon_pixmap, c->icon, c->icon_gc,\n\t\t\t    0, 0, c->icon_geom.w, c->icon_geom.h, 0, 0);\n\t\telse {\n\t\t\tXSetBackground(dpy, c->icon_gc,\n\t\t\t    BlackPixel(dpy, screen));\n\t\t\tXSetForeground(dpy, c->icon_gc,\n\t\t\t    WhitePixel(dpy, screen));\n\t\t\tXCopyPlane(dpy, c->icon_pixmap, c->icon, c->icon_gc,\n\t\t\t    0, 0, c->icon_geom.w, c->icon_geom.h, 0, 0, 1);\n\t\t}\n\t}\n\n\tif (only != None && only != c->icon_label)\n\t\treturn;\n\n\tif (c == focused) {\n\t\ttxft = &xft_fg;\n\t\tXSetWindowBackground(dpy, c->icon_label, bg.pixel);\n\t\tXSetForeground(dpy, DefaultGC(dpy, screen), fg.pixel);\n\t} else {\n\t\ttxft = &xft_fg_unfocused;\n\t\tXSetWindowBackground(dpy, c->icon_label, unfocused_bg.pixel);\n\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t    unfocused_fg.pixel);\n\t}\n\n\tXClearWindow(dpy, c->icon_label);\n\n\tif (!c->icon_name)\n\t\tc->icon_name = strdup(\"(Unknown)\");\n\n\txft_lines = word_wrap_xft(c->icon_name, ' ', iconfont,\n\t    (icon_size * 2) - (label_pad * 2), &nlines);\n\n\tc->icon_label_geom.y = c->icon_geom.y + icon_size + 10;\n\tc->icon_label_geom.h = label_pad;\n\tc->icon_label_geom.w = label_pad;\n\n\tfor (x = 0; x < nlines; x++) {\n\t\tstruct xft_line_t *line = xft_lines +\n\t\t    (sizeof(struct xft_line_t) * x);\n\t\tint w = label_pad + line->xft_width + label_pad;\n\t\tif (w > c->icon_label_geom.w)\n\t\t\tc->icon_label_geom.w = w;\n\t\tc->icon_label_geom.h += iconfont->ascent + iconfont->descent;\n\t}\n\n\tc->icon_label_geom.h += label_pad;\n\tc->icon_label_geom.x = c->icon_geom.x -\n\t    ((c->icon_label_geom.w - icon_size) / 2);\n\n\tXMoveResizeWindow(dpy, c->icon_label,\n\t    c->icon_label_geom.x, c->icon_label_geom.y,\n\t    c->icon_label_geom.w, c->icon_label_geom.h);\n\n\tint ly = label_pad;\n\tfor (x = 0; x < nlines; x++) {\n\t\tstruct xft_line_t *line = xft_lines +\n\t\t    (sizeof(struct xft_line_t) * x);\n\t\tint lx = ((c->icon_label_geom.w - line->xft_width) / 2);\n\n\t\tly += iconfont->ascent;\n\t\tXftDrawStringUtf8(c->icon_xftdraw, txft, iconfont, lx, ly,\n\t\t    (FcChar8 *)line->str, line->len);\n\t\tly += iconfont->descent;\n\t}\n\n\tfree(xft_lines);\n}\n\nvoid\ncollect_struts(client_t *c, strut_t *s)\n{\n\tclient_t *p;\n\tXWindowAttributes attr;\n\tstrut_t temp;\n\n\tfor (p = focused; p; p = p->next) {\n\t\tif (!IS_ON_CUR_DESK(p) || p == c)\n\t\t\tcontinue;\n\n\t\tignore_xerrors++;\n\t\tXGetWindowAttributes(dpy, p->win, &attr);\n\t\tignore_xerrors--;\n\t\tif (attr.map_state == IsViewable && get_strut(p->win, &temp)) {\n\t\t\tif (temp.left > s->left)\n\t\t\t\ts->left = temp.left;\n\t\t\tif (temp.right > s->right)\n\t\t\t\ts->right = temp.right;\n\t\t\tif (temp.top > s->top)\n\t\t\t\ts->top = temp.top;\n\t\t\tif (temp.bottom > s->bottom)\n\t\t\t\ts->bottom = temp.bottom;\n\t\t}\n\t}\n}\n\n/*\n * Well, the man pages for the shape extension say nothing, but I was able to\n * find a shape.PS.Z on the x.org FTP site. What we want to do here is make the\n * window shape be a boolean OR (or union) of the client's shape and our bar\n * for the name. The bar requires both a bound and a clip because it has a\n * border; the server will paint the border in the region between the two.\n */\nvoid\nset_shape(client_t *c)\n{\n\tint n, order;\n\tXRectangle temp, *rects;\n\n\trects = XShapeGetRectangles(dpy, c->win, ShapeBounding, &n, &order);\n\n\tif (n > 1) {\n\t\t/* window contents */\n\t\tXShapeCombineShape(dpy, c->frame, ShapeBounding,\n\t\t    c->resize_w_geom.w,\n\t\t    c->resize_n_geom.h + c->titlebar_geom.h,\n\t\t    c->win, ShapeBounding, ShapeSet);\n\n\t\t/* titlebar */\n\t\ttemp.x = 0;\n\t\ttemp.y = 0;\n\t\ttemp.width = c->frame_geom.w;\n\t\ttemp.height = c->resize_n_geom.h + c->titlebar_geom.h;\n\t\tXShapeCombineRectangles(dpy, c->frame, ShapeBounding,\n\t\t    0, 0, &temp, 1, ShapeUnion, YXBanded);\n\n\t\t/* bottom border */\n\t\ttemp.height = c->resize_s_geom.h;\n\t\tXShapeCombineRectangles(dpy, c->frame, ShapeBounding,\n\t\t    0, c->frame_geom.h - c->resize_s_geom.h, &temp, 1,\n\t\t    ShapeUnion, YXBanded);\n\n\t\t/* left border */\n\t\ttemp.width = c->resize_w_geom.w;\n\t\ttemp.height = c->frame_geom.h;\n\t\tXShapeCombineRectangles(dpy, c->frame, ShapeBounding,\n\t\t    0, 0, &temp, 1, ShapeUnion, YXBanded);\n\t\t/* right border */\n\t\tXShapeCombineRectangles(dpy, c->frame, ShapeBounding,\n\t\t    c->frame_geom.w - c->resize_e_geom.w, 0, &temp, 1,\n\t\t    ShapeUnion, YXBanded);\n\n\t\tc->shaped = 1;\n\t} else\n\t\tc->shaped = 0;\n\n\tXFree(rects);\n}\n\n/*\n * I've decided to carefully ignore any errors raised by this function, rather\n * that attempt to determine asychronously if a window is \"valid\". Xlib calls\n * should only fail here if that a window has removed itself completely before\n * the Unmap and Destroy events get through the queue to us. It's not pretty.\n *\n * The mode argument specifes if the client is actually destroying itself or\n * being destroyed by us, or if we are merely cleaning up its data structures\n * when we exit mid-session.\n */\nvoid\ndel_client(client_t *c, int mode)\n{\n\tclient_t *next;\n\n\tXSync(dpy, False);\n\tXGrabServer(dpy);\n\tignore_xerrors++;\n\n#ifdef DEBUG\n\tdump_name(c, __func__, mode == DEL_WITHDRAW ? \"withdraw\" : \"\", c->name);\n\tdump_removal(c, mode);\n#endif\n\n\tif (mode == DEL_WITHDRAW) {\n\t\tset_wm_state(c, WithdrawnState);\n\t\tXUnmapWindow(dpy, c->frame);\n\t} else {\n\t\tif (c->state & STATE_ZOOMED) {\n\t\t\tc->geom.x = c->save.x;\n\t\t\tc->geom.y = c->save.y;\n\t\t\tc->geom.w = c->save.w;\n\t\t\tc->geom.h = c->save.h;\n\t\t\tXResizeWindow(dpy, c->win, c->geom.w, c->geom.h);\n\t\t}\n\t\tXMapWindow(dpy, c->win);\n\t\tXSetWindowBorderWidth(dpy, c->win, c->old_bw);\n\t}\n\n\tremove_atom(root, net_client_list, XA_WINDOW, c->win);\n\tremove_atom(root, net_client_stack, XA_WINDOW, c->win);\n\n\tif (c->xftdraw)\n\t\tXftDrawDestroy(c->xftdraw);\n\n\tXReparentWindow(dpy, c->win, root, c->geom.x, c->geom.y);\n\tXRemoveFromSaveSet(dpy, c->win);\n\tXDestroyWindow(dpy, c->frame);\n\n\tif (c->icon_xftdraw)\n\t\tXftDrawDestroy(c->icon_xftdraw);\n\tif (c->icon) {\n\t\tXDestroyWindow(dpy, c->icon);\n\t\tif (c->icon_label)\n\t\t\tXDestroyWindow(dpy, c->icon_label);\n\t}\n\tif (c->icon_gc)\n\t\tXFreeGC(dpy, c->icon_gc);\n\tif (c->icon_managed) {\n\t\tif (c->icon_pixmap)\n\t\t\tXFreePixmap(dpy, c->icon_pixmap);\n\t\tif (c->icon_mask)\n\t\t\tXFreePixmap(dpy, c->icon_mask);\n\t\tc->icon_managed = 0;\n\t}\n\n\tif (c->name)\n\t\tXFree(c->name);\n\tif (c->icon_name)\n\t\tXFree(c->icon_name);\n\n\tif (focused == c) {\n\t\tnext = next_client_for_focus(focused);\n\t\tif (!next)\n\t\t\tnext = focused->next;\n\t\tadjust_client_order(c, ORDER_OUT);\n\t\tif (next)\n\t\t\tfocus_client(next, FOCUS_FORCE);\n\t\telse\n\t\t\tfocused = NULL;\n\t} else\n\t\tadjust_client_order(c, ORDER_OUT);\n\n\tfree(c);\n\n\tXSync(dpy, False);\n\tignore_xerrors--;\n\tXUngrabServer(dpy);\n}\n\nvoid *\nword_wrap_xft(char *str, char delim, XftFont *font, int width, int *nlines)\n{\n\tXGlyphInfo extents;\n\tstruct xft_line_t *lines = NULL;\n\tchar *curstr;\n\tint x, lastdelim;\n\tint alloced = 10;\n\tint nline;\n\n\tlines = realloc(lines, alloced * sizeof(struct xft_line_t));\n\tif (lines == NULL)\n\t\terr(1, \"realloc\");\n\nstart_wrap:\n\tnline = 0;\n\tlastdelim = -1;\n\tcurstr = str;\n\n\tfor (x = 0; ; x++) {\n\t\tstruct xft_line_t *line = &lines[nline];\n\t\tint tx;\n\n\t\tif (curstr[x] != delim && curstr[x] != '\\n' &&\n\t\t    curstr[x] != '\\0')\n\t\t\tcontinue;\n\n\t\tXftTextExtentsUtf8(dpy, font, (FcChar8 *)curstr, x, &extents);\n\n\t\tif (curstr[x] == delim && extents.xOff < width) {\n\t\t\t/* keep eating words */\n\t\t\tlastdelim = x;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (extents.xOff > width) {\n\t\t\tif (lastdelim == -1) {\n\t\t\t\t/*\n\t\t\t\t * We can't break this long line, make\n\t\t\t\t * our label width this wide and start\n\t\t\t\t * over, since it may affect previous\n\t\t\t\t * wrapping\n\t\t\t\t */\n\t\t\t\twidth = extents.xOff;\n\t\t\t\tgoto start_wrap;\n\t\t\t}\n\t\t\tx = lastdelim;\n\t\t}\n\n\t\t/* trim leading and trailing spaces */\n\t\ttx = x;\n\t\twhile (curstr[tx - 1] == ' ')\n\t\t\ttx--;\n\t\twhile (curstr[0] == ' ') {\n\t\t\tcurstr++;\n\t\t\ttx--;\n\t\t}\n\t\tXftTextExtentsUtf8(dpy, font, (FcChar8 *)curstr, tx, &extents);\n\n\t\tline->str = curstr;\n\t\tline->len = tx;\n\t\tline->xft_width = extents.xOff;\n\n\t\tif (curstr[x] == '\\0')\n\t\t\tbreak;\n\n\t\tcurstr = curstr + x + 1;\n\t\tx = 0;\n\t\tlastdelim = -1;\n\t\tnline++;\n\n\t\tif (nline == alloced) {\n\t\t\talloced += 10;\n\t\t\tlines = realloc(lines,\n\t\t\t    alloced * sizeof(struct xft_line_t));\n\t\t\tif (lines == NULL)\n\t\t\t\terr(1, \"realloc\");\n\t\t}\n\t}\n\n\t*nlines = nline + 1;\n\treturn lines;\n}\n"
  },
  {
    "path": "events.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <poll.h>\n#include <X11/Xatom.h>\n#include <X11/extensions/shape.h>\n#include \"progman.h\"\n#include \"atom.h\"\n\n#ifndef INFTIM\n#define INFTIM (-1)\n#endif\n\nstatic void handle_button_press(XButtonEvent *);\nstatic void handle_button_release(XButtonEvent *);\nstatic void handle_configure_request(XConfigureRequestEvent *);\nstatic void handle_circulate_request(XCirculateRequestEvent *);\nstatic void handle_map_request(XMapRequestEvent *);\nstatic void handle_destroy_event(XDestroyWindowEvent *);\nstatic void handle_client_message(XClientMessageEvent *);\nstatic void handle_property_change(XPropertyEvent *);\nstatic void handle_enter_event(XCrossingEvent *);\nstatic void handle_cmap_change(XColormapEvent *);\nstatic void handle_expose_event(XExposeEvent *);\nstatic void handle_shape_change(XShapeEvent *);\n\nstatic XEvent ev;\n\nvoid\nevent_loop(void)\n{\n\tstruct pollfd pfd[2];\n\n\tmemset(&pfd, 0, sizeof(pfd));\n\tpfd[0].fd = ConnectionNumber(dpy);\n\tpfd[0].events = POLLIN;\n\tpfd[1].fd = exitmsg[0];\n\tpfd[1].events = POLLIN;\n\n\tfor (;;) {\n\t\tif (!XPending(dpy)) {\n\t\t\tpoll(pfd, 2, INFTIM);\n\t\t\tif (pfd[1].revents)\n\t\t\t\t/* exitmsg */\n\t\t\t\tbreak;\n\n\t\t\tif (!XPending(dpy))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tXNextEvent(dpy, &ev);\n#ifdef DEBUG\n\t\tshow_event(ev);\n#endif\n\t\tswitch (ev.type) {\n\t\tcase ButtonPress:\n\t\t\thandle_button_press(&ev.xbutton);\n\t\t\tbreak;\n\t\tcase ButtonRelease:\n\t\t\thandle_button_release(&ev.xbutton);\n\t\t\tbreak;\n\t\tcase ConfigureRequest:\n\t\t\thandle_configure_request(&ev.xconfigurerequest);\n\t\t\tbreak;\n\t\tcase CirculateRequest:\n\t\t\thandle_circulate_request(&ev.xcirculaterequest);\n\t\t\tbreak;\n\t\tcase MapRequest:\n\t\t\thandle_map_request(&ev.xmaprequest);\n\t\t\tbreak;\n\t\tcase UnmapNotify:\n\t\t\thandle_unmap_event(&ev.xunmap);\n\t\t\tbreak;\n\t\tcase DestroyNotify:\n\t\t\thandle_destroy_event(&ev.xdestroywindow);\n\t\t\tbreak;\n\t\tcase ClientMessage:\n\t\t\thandle_client_message(&ev.xclient);\n\t\t\tbreak;\n\t\tcase ColormapNotify:\n\t\t\thandle_cmap_change(&ev.xcolormap);\n\t\t\tbreak;\n\t\tcase PropertyNotify:\n\t\t\thandle_property_change(&ev.xproperty);\n\t\t\tbreak;\n\t\tcase EnterNotify:\n\t\t\thandle_enter_event(&ev.xcrossing);\n\t\t\tbreak;\n\t\tcase Expose:\n\t\t\thandle_expose_event(&ev.xexpose);\n\t\t\tbreak;\n\t\tcase KeyPress:\n\t\tcase KeyRelease:\n\t\t\thandle_key_event(&ev.xkey);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (shape_support && ev.type == shape_event)\n\t\t\t\thandle_shape_change((XShapeEvent *)&ev);\n\t\t}\n\t}\n}\n\n/*\n * Someone clicked a button. If they clicked on a window, we want the button\n * press, but if they clicked on the root, we're only interested in the button\n * release. Thus, two functions.\n *\n * If it was on the root, we get the click by default. If it's on a window\n * frame, we get it as well.\n *\n * If it's on a client window, it may still fall through to us if the client\n * doesn't select for mouse-click events. The upshot of this is that you should\n * be able to click on the blank part of a GTK window with Button2 to move\n * it.\n */\nstatic void\nhandle_button_press(XButtonEvent *e)\n{\n\tclient_t *c = find_client(e->window, MATCH_ANY);\n\tint i;\n\n\tif (e->window == root) {\n\t\tclient_t *fc;\n\t\t/*\n\t\t * Clicking inside transparent icons may fall through to the\n\t\t * root, so check for an iconified client here\n\t\t */\n\t\tif ((fc = find_client_at_coords(e->window, e->x, e->y)) &&\n\t\t    (fc->state & STATE_ICONIFIED)) {\n\t\t\tc = fc;\n\t\t\te->window = c->icon;\n\t\t}\n\t}\n\n\tif (c && (c->state & STATE_DOCK)) {\n\t\t/* pass button event through */\n\t\tXAllowEvents(dpy, ReplayPointer, CurrentTime);\n\t} else if (c) {\n\t\tif (opt_drag_button && e->button == opt_drag_button &&\n\t\t    (e->state & opt_drag_mod) &&\n\t\t    !(c->state & (STATE_FULLSCREEN | STATE_ZOOMED |\n\t\t    STATE_ICONIFIED))) {\n\t\t\t/* alt+click, begin moving */\n\t\t\tfocus_client(c, FOCUS_NORMAL);\n\t\t\tmove_client(c);\n\t\t} else if (find_client(e->window, MATCH_FRAME)) {\n\t\t\t/* raising our frame will also raise the window */\n\t\t\tfocus_client(c, FOCUS_NORMAL);\n\t\t\tuser_action(c, e->window, e->x, e->y, e->button, 1);\n\t\t} else {\n\t\t\tif (e->button == 1)\n\t\t\t\tfocus_client(c, FOCUS_NORMAL);\n\n\t\t\t/* pass button event through */\n\t\t\tXAllowEvents(dpy, ReplayPointer, CurrentTime);\n\t\t}\n\t} else if (e->window == root) {\n\t\tfor (i = 0; i < nkey_actions; i++) {\n\t\t\tif (key_actions[i].type == BINDING_TYPE_DESKTOP &&\n\t\t\t    key_actions[i].mod == e->state &&\n\t\t\t    key_actions[i].button == e->button) {\n\t\t\t\ttake_action(&key_actions[i]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void\nhandle_button_release(XButtonEvent *e)\n{\n\tclient_t *c = find_client(e->window, MATCH_ANY);\n\n\tif (e->window == root) {\n\t\tclient_t *fc;\n\t\tif ((fc = find_client_at_coords(e->window, e->x, e->y)) &&\n\t\t    (fc->state & STATE_ICONIFIED)) {\n\t\t\tc = fc;\n\t\t\te->window = c->icon;\n\t\t}\n\t}\n\n\tif (c) {\n\t\tif (find_client(e->window, MATCH_FRAME))\n\t\t\tuser_action(c, e->window, e->x, e->y, e->button, 0);\n\n\t\tXAllowEvents(dpy, ReplayPointer, CurrentTime);\n\t}\n}\n\n/*\n * Because we are redirecting the root window, we get ConfigureRequest events\n * from both clients we're handling and ones that we aren't. For clients we\n * manage, we need to adjust the frame and the client window, and for unmanaged\n * windows we have to pass along everything unchanged.\n *\n * Most of the assignments here are going to be garbage, but only the ones that\n * are masked in by e->value_mask will be looked at by the X server.\n */\nstatic void\nhandle_configure_request(XConfigureRequestEvent *e)\n{\n\tclient_t *c = NULL;\n\tXWindowChanges wc;\n\n\tif ((c = find_client(e->window, MATCH_WINDOW))) {\n\t\trecalc_frame(c);\n\n\t\tif (e->value_mask & CWX)\n\t\t\tc->geom.x = e->x + (c->geom.x - c->frame_geom.x);\n\t\tif (e->value_mask & CWY)\n\t\t\tc->geom.y = e->y + (c->geom.y - c->frame_geom.y);\n\t\tif (e->value_mask & CWWidth)\n\t\t\tc->geom.w = e->width;\n\t\tif (e->value_mask & CWHeight)\n\t\t\tc->geom.h = e->height;\n\n\t\tconstrain_frame(c);\n\n\t\twc.x = c->frame_geom.x;\n\t\twc.y = c->frame_geom.y;\n\t\twc.width = c->frame_geom.w;\n\t\twc.height = c->frame_geom.h;\n\t\twc.border_width = 0;\n\t\twc.sibling = e->above;\n\t\twc.stack_mode = e->detail;\n#ifdef DEBUG\n\t\tdump_geom(c, c->frame_geom, \"moving frame to\");\n#endif\n\t\tXConfigureWindow(dpy, c->frame, e->value_mask, &wc);\n\t\tif (e->value_mask & (CWWidth | CWHeight))\n\t\t\tset_shape(c);\n\t\tif ((c->state & STATE_ZOOMED) &&\n\t\t    (e->value_mask & (CWX | CWY | CWWidth | CWHeight))) {\n#ifdef DEBUG\n\t\t\tdump_name(c, __func__, NULL,\n\t\t\t    \"unzooming from XConfigureRequest\");\n#endif\n\t\t\tunzoom_client(c);\n\t\t} else {\n\t\t\tredraw_frame(c, None);\n\t\t\tsend_config(c);\n\t\t}\n\t}\n\n\tif (c) {\n\t\twc.x = c->geom.x - c->frame_geom.x;\n\t\twc.y = c->geom.y - c->frame_geom.y;\n\t\twc.width = c->geom.w;\n\t\twc.height = c->geom.h;\n\t} else {\n\t\twc.x = e->x;\n\t\twc.y = e->y;\n\t\twc.width = e->width;\n\t\twc.height = e->height;\n\t}\n\twc.sibling = e->above;\n\twc.stack_mode = e->detail;\n\tXConfigureWindow(dpy, e->window, e->value_mask, &wc);\n\n\t/* top client may not be the focused one now */\n\tif ((c = top_client()) && IS_ON_CUR_DESK(c))\n\t\tfocus_client(c, FOCUS_FORCE);\n}\n\n/*\n * The only window that we will circulate children for is the root (because\n * nothing else would make sense). After a client requests that the root's\n * children be circulated, the server will determine which window needs to be\n * raised or lowered, and so all we have to do is make it so.\n */\nstatic void\nhandle_circulate_request(XCirculateRequestEvent *e)\n{\n\tclient_t *c;\n\n\tif (e->parent == root) {\n\t\tc = find_client(e->window, MATCH_ANY);\n\n\t\tif (e->place == PlaceOnBottom) {\n\t\t\tif (c) {\n\t\t\t\tadjust_client_order(c, ORDER_BOTTOM);\n\t\t\t\tif (focused)\n\t\t\t\t\tfocus_client(focused, FOCUS_FORCE);\n\t\t\t} else\n\t\t\t\tXLowerWindow(dpy, e->window);\n\t\t} else {\n\t\t\tif (c && IS_ON_CUR_DESK(c))\n\t\t\t\tfocus_client(c, FOCUS_FORCE);\n\t\t\telse if (c)\n\t\t\t\tadjust_client_order(c, ORDER_TOP);\n\t\t\telse\n\t\t\t\tXRaiseWindow(dpy, e->window);\n\t\t}\n\t}\n}\n\n/*\n * Two possibilities if a client is asking to be mapped. One is that it's a new\n * window, so we handle that if it isn't in our clients list anywhere. The\n * other is that it already exists and wants to de-iconify, which is simple to\n * take care of. Since we iconify all of a window's transients when iconifying\n * that window, de-iconify them here.\n */\nstatic void\nhandle_map_request(XMapRequestEvent *e)\n{\n\tclient_t *c, *p;\n\n\tif ((c = find_client(e->window, MATCH_WINDOW))) {\n\t\tuniconify_client(c);\n\t\tfor (p = focused; p; p = p->next)\n\t\t\tif (p->trans == c->win)\n\t\t\t\tuniconify_client(p);\n\t} else {\n\t\tc = new_client(e->window);\n\t\tmap_client(c);\n\t}\n}\n\n/*\n * We don't get to intercept Unmap events, so this is post mortem. If we caused\n * the unmap ourselves earlier (explictly or by remapping), we will have\n * incremented c->ignore_unmap. If not, time to destroy the client.\n *\n * Because most clients unmap and destroy themselves at once, they're gone\n * before we even get the Unmap event, never mind the Destroy one. Therefore we\n * must be extra careful in del_client.\n */\nvoid\nhandle_unmap_event(XUnmapEvent *e)\n{\n\tclient_t *c;\n\n\tif ((c = find_client(e->window, MATCH_WINDOW))) {\n\t\tif (c->ignore_unmap)\n\t\t\tc->ignore_unmap--;\n\t\telse\n\t\t\tdel_client(c, DEL_WITHDRAW);\n\t}\n}\n\n/*\n * But a window can also go away when it's not mapped, in which case there is\n * no Unmap event.\n */\nstatic void\nhandle_destroy_event(XDestroyWindowEvent *e)\n{\n\tclient_t *c;\n\n\tif ((c = find_client(e->window, MATCH_WINDOW)))\n\t\tdel_client(c, DEL_WITHDRAW);\n}\n\nstatic void\nhandle_client_message(XClientMessageEvent *e)\n{\n\tclient_t *c;\n\n\tif (e->window == root) {\n\t\tif (e->message_type == net_cur_desk && e->format == 32)\n\t\t\tgoto_desk(e->data.l[0]);\n\t\telse if (e->message_type == net_num_desks && e->format == 32) {\n\t\t\tif (e->data.l[0] < ndesks)\n\t\t\t\t/* TODO: move clients from deleted desks */\n\t\t\t\treturn;\n\t\t\tndesks = e->data.l[0];\n\t\t}\n\t\treturn;\n\t}\n\n\tc = find_client(e->window, MATCH_WINDOW);\n\tif (!c)\n\t\treturn;\n\tif (e->format != 32)\n\t\treturn;\n\n\tif (e->message_type == wm_change_state && e->data.l[0] == IconicState)\n\t\ticonify_client(c);\n\telse if (e->message_type == net_close_window)\n\t\tsend_wm_delete(c);\n\telse if (e->message_type == net_active_window) {\n\t\tc->desk = cur_desk;\n\t\tmap_if_desk(c);\n\t\tif (c->state == STATE_ICONIFIED)\n\t\t\tuniconify_client(c);\n\t\tfocus_client(c, FOCUS_NORMAL);\n\t} else if (e->message_type == net_wm_state &&\n\t    e->data.l[1] == net_wm_state_fs) {\n\t\tif (e->data.l[0] == net_wm_state_add ||\n\t\t    (e->data.l[0] == net_wm_state_toggle &&\n\t\t    c->state != STATE_FULLSCREEN))\n\t\t\tfullscreen_client(c);\n\t\telse\n\t\t\tunfullscreen_client(c);\n\t}\n}\n\n/*\n * If we have something copied to a variable, or displayed on the screen, make\n * sure it is up to date. If redrawing the name is necessary, clear the window\n * because Xft uses alpha rendering.\n */\nstatic void\nhandle_property_change(XPropertyEvent *e)\n{\n\tclient_t *c;\n#ifdef DEBUG\n\tchar *atom;\n#endif\n\n\tif (!(c = find_client(e->window, MATCH_WINDOW)))\n\t\treturn;\n\n#ifdef DEBUG\n\tatom = XGetAtomName(dpy, e->atom);\n\tdump_name(c, __func__, \"\", atom);\n\tXFree(atom);\n#endif\n\n\tif (e->atom == XA_WM_NAME || e->atom == net_wm_name) {\n\t\tif (c->name)\n\t\t\tXFree(c->name);\n\t\tc->name = get_wm_name(c->win);\n\t\tif (c->frame_style & FRAME_TITLEBAR)\n\t\t\tredraw_frame(c, c->titlebar);\n\t} else if (e->atom == XA_WM_ICON_NAME || e->atom == net_wm_icon_name) {\n\t\tif (c->icon_name)\n\t\t\tXFree(c->icon_name);\n\t\tc->icon_name = get_wm_icon_name(c->win);\n\t\tif (c->state & STATE_ICONIFIED)\n\t\t\tredraw_icon(c, c->icon_label);\n\t} else if (e->atom == XA_WM_NORMAL_HINTS) {\n\t\tupdate_size_hints(c);\n\t\tfix_size(c);\n\t\tredraw_frame(c, None);\n\t\tsend_config(c);\n\t} else if (e->atom == XA_WM_HINTS) {\n\t\tif (c->wm_hints)\n\t\t\tXFree(c->wm_hints);\n\t\tc->wm_hints = XGetWMHints(dpy, c->win);\n\t\tif (c->wm_hints &&\n\t\t    c->wm_hints->flags & (IconPixmapHint | IconMaskHint)) {\n\t\t\tget_client_icon(c);\n\t\t\tif (c->state & STATE_ICONIFIED)\n\t\t\t\tredraw_icon(c, c->icon);\n\t\t}\n\t} else if (e->atom == net_wm_state || e->atom == wm_state) {\n\t\tint was_state = c->state;\n\t\tcheck_states(c);\n\t\tif (was_state != c->state) {\n\t\t\tif (c->state & STATE_ICONIFIED)\n\t\t\t\ticonify_client(c);\n\t\t\telse if (c->state & STATE_ZOOMED)\n\t\t\t\tzoom_client(c);\n\t\t\telse if (c->state & STATE_FULLSCREEN)\n\t\t\t\tfullscreen_client(c);\n\t\t\telse {\n\t\t\t\tif (was_state & STATE_ZOOMED)\n\t\t\t\t\tunzoom_client(c);\n\t\t\t\telse if (was_state & STATE_ICONIFIED)\n\t\t\t\t\tuniconify_client(c);\n\t\t\t\telse if (was_state & STATE_FULLSCREEN)\n\t\t\t\t\tunfullscreen_client(c);\n\t\t\t}\n\t\t}\n\t} else if (e->atom == net_wm_desk) {\n\t\tif (get_atoms(c->win, net_wm_desk, XA_CARDINAL, 0,\n\t\t\t&c->desk, 1, NULL)) {\n\t\t\tif (c->desk == -1)\n\t\t\t\tc->desk = DESK_ALL;\t/* FIXME */\n\t\t\tmap_if_desk(c);\n\t\t}\n\t}\n#ifdef DEBUG\n\telse {\n\t\tprintf(\"%s: unknown atom %ld (%s)\\n\", __func__, (long)e->atom,\n\t\t    XGetAtomName(dpy, e->atom));\n\t}\n#endif\n}\n\n/* Support click-to-focus policy. */\nstatic void\nhandle_enter_event(XCrossingEvent *e)\n{\n\tclient_t *c;\n\n\tif ((c = find_client(e->window, MATCH_FRAME)))\n\t\tXGrabButton(dpy, Button1, AnyModifier, c->win, False,\n\t\t    ButtonMask, GrabModeSync, GrabModeSync, None, None);\n}\n\n/*\n * Colormap policy: when a client installs a new colormap on itself, set the\n * display's colormap to that. We do this even if it's not focused.\n */\nstatic void\nhandle_cmap_change(XColormapEvent *e)\n{\n\tclient_t *c;\n\n\tif ((c = find_client(e->window, MATCH_WINDOW)) && e->new) {\n\t\tc->cmap = e->colormap;\n\t\tXInstallColormap(dpy, c->cmap);\n\t}\n}\n\n/*\n * If we were covered by multiple windows, we will usually get multiple expose\n * events, so ignore them unless e->count (the number of outstanding exposes)\n * is zero.\n */\nstatic void\nhandle_expose_event(XExposeEvent *e)\n{\n\tclient_t *c;\n\n\tif ((c = find_client(e->window, MATCH_FRAME)) && e->count == 0)\n\t\tredraw_frame(c, e->window);\n}\n\nstatic void\nhandle_shape_change(XShapeEvent *e)\n{\n\tclient_t *c;\n\n\tif ((c = find_client(e->window, MATCH_WINDOW)))\n\t\tset_shape(c);\n}\n\n#ifdef DEBUG\nvoid\nshow_event(XEvent e)\n{\n\tchar ev_type[128];\n\tWindow w;\n\tclient_t *c;\n\n\tswitch (e.type) {\n\tSHOW_EV(ButtonPress, xbutton)\n\tSHOW_EV(ButtonRelease, xbutton)\n\tSHOW_EV(ClientMessage, xclient)\n\tSHOW_EV(ColormapNotify, xcolormap)\n\tSHOW_EV(ConfigureNotify, xconfigure)\n\tSHOW_EV(ConfigureRequest, xconfigurerequest)\n\tSHOW_EV(CirculateRequest, xcirculaterequest)\n\tSHOW_EV(CreateNotify, xcreatewindow)\n\tSHOW_EV(DestroyNotify, xdestroywindow)\n\tSHOW_EV(EnterNotify, xcrossing)\n\tSHOW_EV(Expose, xexpose)\n\tSHOW_EV(KeyPress, xkey)\n\tSHOW_EV(KeyRelease, xkey)\n\tSHOW_EV(NoExpose, xexpose)\n\tSHOW_EV(MapNotify, xmap)\n\tSHOW_EV(MapRequest, xmaprequest)\n\tSHOW_EV(MappingNotify, xmapping)\n\tSHOW_EV(PropertyNotify, xproperty)\n\tSHOW_EV(ReparentNotify, xreparent)\n\tSHOW_EV(ResizeRequest, xresizerequest)\n\tSHOW_EV(UnmapNotify, xunmap)\n\tSHOW_EV(MotionNotify, xmotion)\n\tdefault:\n\t\tif (shape_support && e.type == shape_event) {\n\t\t\tsnprintf(ev_type, sizeof(ev_type), \"ShapeNotify\");\n\t\t\tw = ((XShapeEvent *) & e)->window;\n\t\t\tbreak;\n\t\t}\n\t\tsnprintf(ev_type, sizeof(ev_type), \"unknown event %d\", e.type);\n\t\tw = None;\n\t\tbreak;\n\t}\n\n\tif ((c = find_client(w, MATCH_WINDOW)))\n\t\tdump_name(c, ev_type, \"window\", c->name);\n\telse if ((c = find_client(w, MATCH_FRAME))) {\n\t\t/*\n\t\t * ConfigureNotify can only come from us (otherwise it'd be a\n\t\t * ConfigureRequest) and NoExpose events are just not useful\n\t\t */\n\t\tif (e.type != ConfigureNotify && e.type != NoExpose)\n\t\t\tdump_name(c, ev_type, frame_name(c, w), c->name);\n\t} else if (w == root)\n\t\tdump_name(NULL, ev_type, \"root\", \"(root)\");\n}\n#endif\n"
  },
  {
    "path": "icons/close.xpm",
    "content": "/* XPM */\nstatic char * close_xpm[] = {\n\"14 14 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888F\",\n\"              \",\n\"              \",\n\"              \",\n\"              \",\n\"              \",\n\"............. \",\n\".+++++++++++.@\",\n\".............@\",\n\" @@@@@@@@@@@@@\",\n\"              \",\n\"              \",\n\"              \",\n\"              \",\n\"              \"};\n"
  },
  {
    "path": "icons/default_icon.xpm",
    "content": "/* XPM */\nstatic char * default_icon_xpm[] = {\n\"32 32 6 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #808080\",\n\"@\tc #C0C0C0\",\n\"#\tc #00FFFF\",\n\"$\tc #FFFFFF\",\n\"                                \",\n\"                                \",\n\"                                \",\n\"                                \",\n\"                                \",\n\"                                \",\n\"     .                          \",\n\"     .....                      \",\n\"     .####....                  \",\n\"     .....####....              \",\n\"     .++++....####.....         \",\n\"     .$$$$++++....####.         \",\n\"     .$$$$$$$$++++.....         \",\n\"     .$$$$$$$$$$$$++++.         \",\n\"     .$$$$$$$$$$$$$$$$.         \",\n\"     .$$$$$$$$$$$$$$$$.         \",\n\"     .$$$$$$$$$$$$$$$$.         \",\n\"     .$$$$$$$$$$$$$$$$.         \",\n\"     .$$$$$$$$$$$$$$$$.         \",\n\"     .$$$$$$$$$$$$$$$$.         \",\n\"     .$$$$$$$$$$$$$$$$.         \",\n\"     .$$$$$$$$$$$$$$$$.@@       \",\n\"     .$$$$$$$$$$$$$$$$.@@@@     \",\n\"     ....+$$$$$$$$$$$$.@@@@@@   \",\n\"         .....+$$$$$$$.@@@@     \",\n\"              ....+$$$.@@       \",\n\"                  .....         \",\n\"                                \",\n\"                                \",\n\"                                \",\n\"                                \",\n\"                                \"};\n"
  },
  {
    "path": "icons/hidpi-close.xpm",
    "content": "/* XPM */\nstatic char * hidpi_close_xpm[] = {\n\"22 7 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888F\",\n\"....................  \",\n\".++++++++++++++++++.@@\",\n\".++++++++++++++++++.@@\",\n\".++++++++++++++++++.@@\",\n\"....................@@\",\n\"  @@@@@@@@@@@@@@@@@@@@\",\n\"  @@@@@@@@@@@@@@@@@@@@\"};\n"
  },
  {
    "path": "icons/hidpi-default_icon.xpm",
    "content": "/* XPM */\nstatic char * hidpi_default_icon_xpm[] = {\n\"64 64 6 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #00FFFF\",\n\"@\tc #808080\",\n\"#\tc #FFFFFF\",\n\"$\tc #C0C0C0\",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"          ..                                                    \",\n\"          ..                                                    \",\n\"          ..........                                            \",\n\"          ..........                                            \",\n\"          ..++++++++........                                    \",\n\"          ..++++++++........                                    \",\n\"          ..........++++++++........                            \",\n\"          ..........++++++++........                            \",\n\"          ..@@@@@@@@........++++++++..........                  \",\n\"          ..@@@@@@@@........++++++++..........                  \",\n\"          ..########@@@@@@@@........++++++++..                  \",\n\"          ..########@@@@@@@@........++++++++..                  \",\n\"          ..################@@@@@@@@..........                  \",\n\"          ..################@@@@@@@@..........                  \",\n\"          ..########################@@@@@@@@..                  \",\n\"          ..########################@@@@@@@@..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..                  \",\n\"          ..################################..$$$$              \",\n\"          ..################################..$$$$              \",\n\"          ..################################..$$$$$$$$          \",\n\"          ..################################..$$$$$$$$          \",\n\"          ........@@########################..$$$$$$$$$$$$      \",\n\"          ........@@########################..$$$$$$$$$$$$      \",\n\"                  ..........@@##############..$$$$$$$$          \",\n\"                  ..........@@##############..$$$$$$$$          \",\n\"                            ........@@######..$$$$              \",\n\"                            ........@@######..$$$$              \",\n\"                                    ..........                  \",\n\"                                    ..........                  \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \",\n\"                                                                \"};\n"
  },
  {
    "path": "icons/hidpi-iconify.xpm",
    "content": "/* XPM */\nstatic char * hidpi_iconify_xpm[] = {\n\"13 9 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"             \",\n\"             \",\n\".............\",\n\" ........... \",\n\"  .........  \",\n\"   .......   \",\n\"    .....    \",\n\"     ...     \",\n\"      .      \"};\n"
  },
  {
    "path": "icons/hidpi-unzoom.xpm",
    "content": "/* XPM */\nstatic char * hidpi_unzoom_xpm[] = {\n\"13 18 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"      .      \",\n\"     ...     \",\n\"    .....    \",\n\"   .......   \",\n\"  .........  \",\n\" ........... \",\n\".............\",\n\"             \",\n\"             \",\n\"             \",\n\"             \",\n\".............\",\n\" ........... \",\n\"  .........  \",\n\"   .......   \",\n\"    .....    \",\n\"     ...     \",\n\"      .      \"};\n"
  },
  {
    "path": "icons/hidpi-utility_close.xpm",
    "content": "/* XPM */\nstatic char * hidpi_utility_close_xpm[] = {\n\"14 14 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888F\",\n\"              \",\n\"              \",\n\"              \",\n\"              \",\n\"              \",\n\"  ..........  \",\n\"  .++++++++.@@\",\n\"  .++++++++.@@\",\n\"  ..........@@\",\n\"    @@@@@@@@@@\",\n\"    @@@@@@@@@@\",\n\"              \",\n\"              \",\n\"              \"};\n"
  },
  {
    "path": "icons/hidpi-zoom.xpm",
    "content": "/* XPM */\nstatic char * hidpi_zoom_xpm[] = {\n\"13 9 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"      .      \",\n\"     ...     \",\n\"    .....    \",\n\"   .......   \",\n\"  .........  \",\n\" ........... \",\n\".............\",\n\"             \",\n\"             \"};\n"
  },
  {
    "path": "icons/iconify.xpm",
    "content": "/* XPM */\nstatic char * iconify_xpm[] = {\n\"9 6 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"         \",\n\".........\",\n\" ....... \",\n\"  .....  \",\n\"   ...   \",\n\"    .    \"};\n"
  },
  {
    "path": "icons/unzoom.xpm",
    "content": "/* XPM */\nstatic char * unzoom_xpm[] = {\n\"9 12 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"    .    \",\n\"   ...   \",\n\"  .....  \",\n\" ....... \",\n\".........\",\n\"         \",\n\"         \",\n\".........\",\n\" ....... \",\n\"  .....  \",\n\"   ...   \",\n\"    .    \"};\n"
  },
  {
    "path": "icons/utility_close.xpm",
    "content": "/* XPM */\nstatic char * utility_close_xpm[] = {\n\"7 7 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888F\",\n\"       \",\n\"       \",\n\"...... \",\n\".++++.@\",\n\"......@\",\n\" @@@@@@\",\n\"       \"};\n"
  },
  {
    "path": "icons/zoom.xpm",
    "content": "/* XPM */\nstatic char * zoom_xpm[] = {\n\"9 6 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"    .    \",\n\"   ...   \",\n\"  .....  \",\n\" ....... \",\n\".........\",\n\"         \"};\n"
  },
  {
    "path": "keyboard.c",
    "content": "/*\n * Copyright (c) 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <ctype.h>\n#include <err.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <strings.h>\n#include \"progman.h\"\n\naction_t *key_actions = NULL;\nint nkey_actions = 0;\nstatic int cycle_key = 0;\n\naction_t *\nbind_key(int type, char *key, char *action)\n{\n\tchar *tkey, *sep;\n\tKeySym k = 0;\n\taction_t *taction;\n\tint x, mod = 0, iaction = -1, button = 0, overwrite, aidx;\n\n\ttkey = strdup(key);\n\n\t/* key can be \"shift+alt+f1\" or \"Super+Space\" or just \"ampersand\" */\n\twhile ((sep = strchr(tkey, '+'))) {\n\t\t*sep = '\\0';\n\t\tif (strcasecmp(tkey, \"shift\") == 0)\n\t\t\tmod |= ShiftMask;\n\t\telse if (strcasecmp(tkey, \"control\") == 0 ||\n\t\t    strcasecmp(tkey, \"ctrl\") == 0 ||\n\t\t    strcasecmp(tkey, \"ctl\") == 0)\n\t\t\tmod |= ControlMask;\n\t\telse if (strcasecmp(tkey, \"alt\") == 0 ||\n\t\t    strcasecmp(tkey, \"meta\") == 0 ||\n\t\t    strcasecmp(tkey, \"mod1\") == 0)\n\t\t\tmod |= Mod1Mask;\n\t\telse if (strcasecmp(tkey, \"mod2\") == 0)\n\t\t\tmod |= Mod2Mask;\n\t\telse if (strcasecmp(tkey, \"mod3\") == 0)\n\t\t\tmod |= Mod3Mask;\n\t\telse if (strcasecmp(tkey, \"super\") == 0 ||\n\t\t    strcasecmp(tkey, \"win\") == 0 ||\n\t\t    strcasecmp(tkey, \"mod4\") == 0)\n\t\t\tmod |= Mod4Mask;\n\t\telse {\n\t\t\twarnx(\"failed parsing modifier \\\"%s\\\" in \\\"%s\\\", \"\n\t\t\t    \"skipping\", tkey, key);\n\t\t\treturn NULL;\n\t\t}\n\n\t\ttkey = sep + 1;\n\t}\n\n\t/* modifiers have been parsed, only the key or button should remain */\n\tif (strncasecmp(tkey, \"mouse\", 5) == 0 &&\n\t    tkey[5] > '0' && tkey[5] <= '9')\n\t\tbutton = tkey[5] - '0';\n\telse if (tkey[0] != '\\0') {\n\t\tif (tkey[1] == '\\0') {\n\t\t\t/*\n\t\t\t * Assume a single-character key is meant to be used as\n\t\t\t * its lower-case key, e.g., \"Win+T\" is mod4+t, not\n\t\t\t * mod4+T, and if the user wanted it a capital t, they\n\t\t\t * would specify it as \"Win+Shift+T\"\n\t\t\t */\n\t\t\ttkey[0] = tolower(tkey[0]);\n\t\t}\n\n\t\tk = XStringToKeysym(tkey);\n\t\tif (k == 0) {\n\t\t\twarnx(\"failed parsing key \\\"%s\\\", skipping\\n\", tkey);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\t/* action can be e.g., \"cycle\" or \"exec xterm -g 80x50\" */\n\ttaction = parse_action(key, action);\n\tif (taction == NULL)\n\t\treturn NULL;\n\n\t/* if we're overriding an existing config, replace it in key_actions */\n\toverwrite = 0;\n\tfor (x = 0; x < nkey_actions; x++) {\n\t\tif (key_actions[x].type == type &&\n\t\t    key_actions[x].key == k &&\n\t\t    key_actions[x].mod == mod &&\n\t\t    key_actions[x].button == button) {\n\t\t\toverwrite = 1;\n\t\t\taidx = x;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!overwrite) {\n\t\tkey_actions = realloc(key_actions,\n\t\t    (nkey_actions + 1) * sizeof(action_t));\n\t\tif (key_actions == NULL)\n\t\t\terr(1, \"realloc\");\n\n\t\taidx = nkey_actions;\n\t}\n\n\tif (taction->action == ACTION_NONE) {\n\t\tkey_actions[aidx].key = -1;\n\t\tkey_actions[aidx].mod = -1;\n\t\tkey_actions[aidx].button = 0;\n\t} else {\n\t\tkey_actions[aidx].key = k;\n\t\tkey_actions[aidx].mod = mod;\n\t\tkey_actions[aidx].button = button;\n\t}\n\tkey_actions[aidx].type = type;\n\tkey_actions[aidx].action = taction->action;\n\tkey_actions[aidx].iarg = taction->iarg;\n\tif (overwrite && key_actions[aidx].sarg)\n\t\tfree(key_actions[aidx].sarg);\n\tkey_actions[aidx].sarg = taction->sarg;\n\n\t/* retain taction->sarg */\n\tfree(taction);\n\n\tif (!overwrite)\n\t\tnkey_actions++;\n\n#ifdef DEBUG\n\tif (key_actions[aidx].action == ACTION_NONE)\n\t\tprintf(\"%s(%s): unbinding key %ld/button %d with \"\n\t\t    \"mod mask 0x%x\\n\", __func__, key, k, button, mod);\n\telse\n\t\tprintf(\"%s(%s): binding key %ld/button %d with mod mask 0x%x \"\n\t\t    \"to action \\\"%s\\\"\\n\", __func__, key, k, button, mod,\n\t\t    action);\n#endif\n\n\tif (type == BINDING_TYPE_KEYBOARD) {\n\t\tif (overwrite && iaction == ACTION_NONE)\n\t\t\tXUngrabKey(dpy, XKeysymToKeycode(dpy, k), mod, root);\n\t\telse if (!overwrite)\n\t\t\tXGrabKey(dpy, XKeysymToKeycode(dpy, k), mod, root,\n\t\t\t    False, GrabModeAsync, GrabModeAsync);\n\t}\n\n\treturn &key_actions[aidx];\n}\n\nvoid\nhandle_key_event(XKeyEvent *e)\n{\n#ifdef DEBUG\n\tchar buf[64];\n#endif\n\tKeySym kc;\n\taction_t *action = NULL;\n\tint i;\n\n\tkc = XLookupKeysym(e, 0);\n\n#ifdef DEBUG\n\tsnprintf(buf, sizeof(buf), \"%c:%ld\", e->type == KeyRelease ? 'U' : 'D',\n\t    kc);\n\tdump_name(focused, __func__, buf, NULL);\n#endif\n\n\tif (cycle_key && kc != cycle_key && e->type == KeyRelease) {\n\t\t/*\n\t\t * If any key other than the non-modifier(s) of our cycle\n\t\t * binding was released, consider the cycle over.\n\t\t */\n\t\tcycle_key = 0;\n\t\tXUngrabKeyboard(dpy, CurrentTime);\n\t\tXAllowEvents(dpy, ReplayKeyboard, e->time);\n\t\tXFlush(dpy);\n\n\t\tif (cycle_head) {\n\t\t\tcycle_head = NULL;\n\t\t\tif (focused && focused->state & STATE_ICONIFIED)\n\t\t\t\tuniconify_client(focused);\n\t\t}\n\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < nkey_actions; i++) {\n\t\tif (key_actions[i].type == BINDING_TYPE_KEYBOARD &&\n\t\t    key_actions[i].key == kc &&\n\t\t    key_actions[i].mod == e->state) {\n\t\t\taction = &key_actions[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!action)\n\t\treturn;\n\n\tif (e->type != KeyPress)\n\t\treturn;\n\n\tswitch (key_actions[i].action) {\n\tcase ACTION_CYCLE:\n\tcase ACTION_REVERSE_CYCLE:\n\t\t/*\n\t\t * Keep watching input until the modifier is released, but the\n\t\t * keycode will be the modifier key\n\t\t */\n\t\tXGrabKeyboard(dpy, root, False, GrabModeAsync, GrabModeAsync,\n\t\t    e->time);\n\t\tcycle_key = key_actions[i].key;\n\t\ttake_action(&key_actions[i]);\n\t\tbreak;\n\tdefault:\n\t\ttake_action(&key_actions[i]);\n\t}\n}\n"
  },
  {
    "path": "launcher.c",
    "content": "/*\n * Copyright (c) 2021 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <stdlib.h>\n#include <err.h>\n#include \"parser.h\"\n#include \"progman.h\"\n\nstruct program {\n\tchar *name;\n\taction_t *action;\n\tstruct program *next;\n};\n\nWindow launcher_win;\nXftDraw *launcher_xftdraw;\nstruct program *program_head = NULL, *program_tail = NULL;\nint launcher_width = 0, launcher_height = 0, launcher_highlighted = 0,\n    launcher_item_height = 0, launcher_item_padding = 0;\n\nvoid launcher_reload(void);\nvoid launcher_redraw(void);\n\nvoid\nlauncher_setup(void)\n{\n\tXTextProperty name;\n\tchar *title = \"Programs\";\n\tXSizeHints *hints;\n\n\tlauncher_reload();\n\n\tlauncher_win = XCreateWindow(dpy, root, 0, 0, launcher_width,\n\t    launcher_height, 0, DefaultDepth(dpy, screen), CopyFromParent,\n\t    DefaultVisual(dpy, screen), 0, NULL);\n\tif (!launcher_win)\n\t\terr(1, \"XCreateWindow\");\n\n\thints = XAllocSizeHints();\n\tif (!hints)\n\t\terr(1, \"XAllocSizeHints\");\n\n\thints->flags = PMinSize | PMaxSize;\n\thints->min_width = launcher_width;\n\thints->min_height = launcher_height;\n\thints->max_width = launcher_width;\n\thints->max_height = launcher_height;\n\n\tXSetWMNormalHints(dpy, launcher_win, hints);\n\n\tXFree(hints);\n\n\tif (!XStringListToTextProperty(&title, 1, &name))\n\t\terr(1, \"XStringListToTextProperty\");\n\tXSetWMName(dpy, launcher_win, &name);\n\n\tXSetWindowBackground(dpy, launcher_win, launcher_bg.pixel);\n\n\tset_atoms(launcher_win, net_wm_state, XA_ATOM, &net_wm_state_above, 1);\n\tset_atoms(launcher_win, net_wm_wintype, XA_ATOM, &net_wm_type_utility,\n\t    1);\n\n\tlauncher_xftdraw = XftDrawCreate(dpy, launcher_win,\n\t    DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));\n}\n\nvoid\nlauncher_reload(void)\n{\n\tFILE *ini;\n\tstruct program *program = NULL;\n\tXGlyphInfo extents;\n\taction_t *action;\n\tchar *key, *val;\n\tint tw;\n\n\tlauncher_programs_free();\n\n\tlauncher_width = 0;\n\tlauncher_height = 0;\n\tlauncher_item_padding = opt_pad * 2;\n\tlauncher_item_height = font->ascent + (launcher_item_padding * 2);\n\n\tini = open_ini(opt_config_file);\n\n\tif (!find_ini_section(ini, \"launcher\"))\n\t\tgoto done;\n\n\twhile (get_ini_kv(ini, &key, &val)) {\n\t\taction = parse_action(key, val);\n\t\tif (action == NULL)\n\t\t\tcontinue;\n\n\t\tprogram = malloc(sizeof(struct program));\n\t\tif (!program)\n\t\t\terr(1, \"malloc\");\n\n\t\tprogram->next = NULL;\n\n\t\tif (program_tail) {\n\t\t\tprogram_tail->next = program;\n\t\t\tprogram_tail = program;\n\t\t} else {\n\t\t\tprogram_head = program;\n\t\t\tprogram_tail = program;\n\t\t}\n\n\t\tXftTextExtentsUtf8(dpy, font, (FcChar8 *)key, strlen(key),\n\t\t    &extents);\n\t\ttw = extents.xOff + (launcher_item_padding * 2);\n\t\tif (tw > launcher_width)\n\t\t\tlauncher_width = tw;\n\t\tlauncher_height += launcher_item_height;\n\n\t\tprogram->name = strdup(key);\n\t\tprogram->action = action;\n\n\t\tfree(key);\n\t\tfree(val);\n\t}\n\ndone:\n\tfclose(ini);\n}\n\nvoid\nlauncher_show(XButtonEvent *e)\n{\n\tclient_t *c;\n\tXEvent ev;\n\tstruct program *program;\n\tint x, y, mx, my, prev_highlighted;\n\n\tif (e) {\n\t\tx = e->x_root;\n\t\ty = e->y_root;\n\t} else\n\t\tget_pointer(&x, &y);\n\n\tXMoveResizeWindow(dpy, launcher_win, x, y, launcher_width,\n\t    launcher_height);\n\n\tc = new_client(launcher_win);\n\tc->placed = 1;\n\tc->desk = cur_desk;\n\tmap_client(c);\n\tmap_if_desk(c);\n\n\tx = c->geom.x;\n\ty = c->geom.y;\n\n\tlauncher_highlighted = prev_highlighted = -1;\n\tlauncher_redraw();\n\n\t/*\n\t * If we launched from a mouse button down event, grab the pointer to\n\t * watch for it to be released, otherwise we launched from the keyboard\n\t * and we'll dismiss on click\n\t */\n\tif (XGrabPointer(dpy, root, False, MouseMask, GrabModeAsync,\n\t    GrabModeAsync, root, None, CurrentTime) != GrabSuccess) {\n\t\twarnx(\"failed grabbing pointer\");\n\t\tgoto close_launcher;\n\t}\n\n\tfor (;;) {\n\t\tXMaskEvent(dpy, PointerMotionMask | ButtonPressMask |\n\t\t    ButtonReleaseMask, &ev);\n\n\t\tswitch (ev.type) {\n\t\tcase MotionNotify: {\n\t\t\tXMotionEvent *xmv = (XMotionEvent *)&ev;\n\t\t\tmx = xmv->x - x;\n\t\t\tmy = xmv->y - y;\n\n\t\t\tif (mx < 0 || mx > launcher_width ||\n\t\t\t    my < 0 || my > launcher_height)\n\t\t\t\tlauncher_highlighted = -1;\n\t\t\telse\n\t\t\t\tlauncher_highlighted = (my /\n\t\t\t\t    launcher_item_height);\n\n\t\t\tif (launcher_highlighted != prev_highlighted)\n\t\t\t\tlauncher_redraw();\n\n\t\t\tprev_highlighted = launcher_highlighted;\n\t\t\tbreak;\n\t\t}\n\t\tcase ButtonPress:\n\t\t\tbreak;\n\t\tcase ButtonRelease:\n\t\t\tgoto close_launcher;\n\t\t\tbreak;\n\t\t}\n\t}\n\nclose_launcher:\n\tXUngrabPointer(dpy, CurrentTime);\n\tXUnmapWindow(dpy, launcher_win);\n\tdel_client(c, DEL_WITHDRAW);\n\n\tif (launcher_highlighted < 0)\n\t\treturn;\n\n\tfor (x = 0, program = program_head; program;\n\t    program = program->next, x++) {\n\t\tif (x != launcher_highlighted)\n\t\t\tcontinue;\n\n\t\ttake_action(program->action);\n\t\tbreak;\n\t}\n}\n\nvoid\nlauncher_programs_free(void)\n{\n\tstruct program *program;\n\n\tfor (program = program_head; program;) {\n\t\tstruct program *t = program;\n\n\t\tif (program->name)\n\t\t\tfree(program->name);\n\t\tif (program->action) {\n\t\t\tif (program->action->sarg)\n\t\t\t\tfree(program->action->sarg);\n\t\t\tfree(program->action);\n\t\t}\n\t\tprogram = program->next;\n\t\tfree(t);\n\t}\n\n\tprogram_head = program_tail = NULL;\n}\n\nvoid\nlauncher_redraw(void)\n{\n\tstruct program *program;\n\tXftColor *color;\n\tint i;\n\n\tXClearWindow(dpy, launcher_win);\n\n\tfor (i = 0, program = program_head; program;\n\t    program = program->next, i++) {\n\t\tif (launcher_highlighted == i) {\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    launcher_fg.pixel);\n\t\t\tXFillRectangle(dpy, launcher_win,\n\t\t\t    DefaultGC(dpy, screen),\n\t\t\t    0, launcher_item_height * i,\n\t\t\t    launcher_width, launcher_item_height);\n\t\t\tcolor = &xft_launcher_highlighted;\n\t\t} else {\n\t\t\tXSetForeground(dpy, DefaultGC(dpy, screen),\n\t\t\t    launcher_fg.pixel);\n\t\t\tcolor = &xft_launcher;\n\t\t}\n\n\n\t\tXftDrawStringUtf8(launcher_xftdraw, color, font,\n\t\t    launcher_item_padding,\n\t\t    (launcher_item_height * (i + 1)) - launcher_item_padding,\n\t\t    (unsigned char *)program->name,\n\t\t    strlen(program->name));\n\t}\n}\n"
  },
  {
    "path": "manage.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <err.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n#include <time.h>\n#include <X11/Xatom.h>\n#include <X11/extensions/shape.h>\n#include \"progman.h\"\n#include \"atom.h\"\n\nstatic void do_iconify(client_t *);\nstatic void do_shade(client_t *);\nstatic void maybe_toolbar_click(client_t *, Window);\nstatic void monitor_toolbar_click(client_t *, geom_t, int, int, int, int,\n    strut_t *, void *);\n\nstatic struct {\n\tstruct timespec tv;\n\tclient_t *c;\n\tWindow win;\n\tint button;\n} last_click = { { 0, 0 }, NULL, 0 };\n\nvoid\nuser_action(client_t *c, Window win, int x, int y, int button, int down)\n{\n\tstruct timespec now;\n\tlong long tdiff;\n\tint double_click = 0;\n\n\tif (!down) {\n\t\tclock_gettime(CLOCK_MONOTONIC, &now);\n\n\t\tif (last_click.button == button && c == last_click.c &&\n\t\t    win == last_click.win) {\n\t\t\ttdiff = (((now.tv_sec * 1000000000) + now.tv_nsec) -\n\t\t\t    ((last_click.tv.tv_sec * 1000000000) +\n\t\t\t    last_click.tv.tv_nsec)) / 1000000;\n\t\t\tif (tdiff <= DOUBLE_CLICK_MSEC)\n\t\t\t\tdouble_click = 1;\n\t\t}\n\n\t\tlast_click.button = button;\n\t\tlast_click.c = c;\n\t\tlast_click.win = win;\n\t\tmemcpy(&last_click.tv, &now, sizeof(now));\n\t}\n\n#ifdef DEBUG\n\tprintf(\"%s(\\\"%s\\\", %lx, %d, %d, %d, %d) double:%d, c state %d\\n\",\n\t    __func__, c->name, win, x, y, button, down, double_click, c->state);\n\tdump_info(c);\n#endif\n\n\tif (c->state & STATE_ICONIFIED &&\n\t    (win == c->icon || win == c->icon_label)) {\n\t    \tif (down && !double_click) {\n\t\t\tfocus_client(c, FOCUS_NORMAL);\n\t\t\tmove_client(c);\n\t\t\tget_pointer(&x, &y);\n\t\t\tuser_action(c, win, x, y, button, 0);\n\t\t} else if (!down && double_click)\n\t\t\tuniconify_client(c);\n\t} else if (win == c->titlebar) {\n\t\tif (button == 1 && down) {\n\t\t\tif (!(c->state & (STATE_ZOOMED | STATE_FULLSCREEN))) {\n\t\t\t\tmove_client(c);\n\t\t\t\t/* sweep() eats the ButtonRelease event */\n\t\t\t\tget_pointer(&x, &y);\n\t\t\t\tuser_action(c, win, x, y, button, 0);\n\t\t\t}\n\t\t} else if (button == 1 && !down && double_click) {\n\t\t\tif (c->state & STATE_ZOOMED)\n\t\t\t\tunzoom_client(c);\n\t\t\telse\n\t\t\t\tzoom_client(c);\n\t\t} else if (button == 3 && !down) {\n\t\t\tif (c->state & STATE_SHADED)\n\t\t\t\tunshade_client(c);\n\t\t\telse\n\t\t\t\tshade_client(c);\n\t\t}\n\t} else if (win == c->close) {\n\t\tif (button == 1 && down) {\n\t\t\tmaybe_toolbar_click(c, win);\n\t\t\tif (!c->close_pressed)\n\t\t\t\treturn;\n\n\t\t\tc->close_pressed = False;\n\t\t\tredraw_frame(c, c->close);\n\n\t\t\tget_pointer(&x, &y);\n\t\t\tuser_action(c, win, x, y, button, 0);\n\t\t}\n\n\t\tif (double_click)\n\t\t\tsend_wm_delete(c);\n\t} else if (IS_RESIZE_WIN(c, win)) {\n\t\tif (button == 1 && down && !(c->state & STATE_SHADED))\n\t\t\tresize_client(c, win);\n\t} else if (win == c->iconify) {\n\t\tif (button == 1 && down) {\n\t\t\tmaybe_toolbar_click(c, win);\n\t\t\tif (c->iconify_pressed) {\n\t\t\t\tc->iconify_pressed = False;\n\t\t\t\tredraw_frame(c, c->iconify);\n\t\t\t\tif (c->state & STATE_ICONIFIED)\n\t\t\t\t\tuniconify_client(c);\n\t\t\t\telse\n\t\t\t\t\ticonify_client(c);\n\t\t\t}\n\t\t}\n\t} else if (win == c->zoom) {\n\t\tif (button == 1 && down) {\n\t\t\tmaybe_toolbar_click(c, win);\n\t\t\tif (c->zoom_pressed) {\n\t\t\t\tc->zoom_pressed = False;\n\t\t\t\tredraw_frame(c, c->zoom);\n\t\t\t\tif (c->state & STATE_ZOOMED)\n\t\t\t\t\tunzoom_client(c);\n\t\t\t\telse\n\t\t\t\t\tzoom_client(c);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (double_click)\n\t\t/* don't let a 3rd click keep counting as a double click */\n\t\tlast_click.tv.tv_sec = last_click.tv.tv_nsec = 0;\n}\n\nCursor\ncursor_for_resize_win(client_t *c, Window win)\n{\n\tif (win == c->resize_nw)\n\t\treturn resize_nw_curs;\n\telse if (win == c->resize_w)\n\t\treturn resize_w_curs;\n\telse if (win == c->resize_sw)\n\t\treturn resize_sw_curs;\n\telse if (win == c->resize_s)\n\t\treturn resize_s_curs;\n\telse if (win == c->resize_se)\n\t\treturn resize_se_curs;\n\telse if (win == c->resize_e)\n\t\treturn resize_e_curs;\n\telse if (win == c->resize_ne)\n\t\treturn resize_ne_curs;\n\telse if (win == c->resize_n)\n\t\treturn resize_n_curs;\n\telse\n\t\treturn None;\n}\n\n/* This can't do anything dangerous. */\nvoid\nfocus_client(client_t *c, int style)\n{\n\tclient_t *prevfocused = NULL;\n\tclient_t *trans[10] = { NULL };\n\tclient_t *p;\n\tint transcount = 0;\n\n\tif (!c) {\n\t\twarnx(\"%s with no c\", __func__);\n\t\tabort();\n\t}\n\n\tif (focused == c && style != FOCUS_FORCE)\n\t\treturn;\n\n#ifdef DEBUG\n\tdump_name(c, __func__, NULL, c->name);\n#endif\n\n\tif (c->state & STATE_ICONIFIED) {\n\t\tset_atoms(root, net_active_window, XA_WINDOW, &c->icon, 1);\n\t\tXSetInputFocus(dpy, c->icon, RevertToPointerRoot, CurrentTime);\n\t} else {\n\t\tset_atoms(root, net_active_window, XA_WINDOW, &c->win, 1);\n\t\tXSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);\n\t\tXInstallColormap(dpy, c->cmap);\n\t}\n\n\tif (focused && focused != c) {\n\t\tprevfocused = focused;\n\n\t\tif (focused->state & STATE_ICONIFIED)\n\t\t\tadjust_client_order(focused,\n\t\t\t    ORDER_ICONIFIED_TOP);\n\t\telse\n\t\t\tadjust_client_order(focused, ORDER_TOP);\n\t}\n\n\tadjust_client_order(c, ORDER_TOP);\n\n\t/* raise any transients of this window */\n\tfor (p = focused; p; p = p->next) {\n\t\tif (p->trans == c->win) {\n\t\t\ttrans[transcount++] = p;\n\t\t\tif (transcount == sizeof(trans) / (sizeof(trans[0])))\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tif (transcount != 0) {\n\t\tfor (transcount--; transcount >= 0; transcount--) {\n#ifdef DEBUG\n\t\t\tdump_name(c, __func__, \"transient\",\n\t\t\t    trans[transcount]->name);\n#endif\n\t\t\tadjust_client_order(trans[transcount], ORDER_TOP);\n\t\t}\n\t}\n\n\trestack_clients();\n\n\tif (prevfocused)\n\t\tredraw_frame(prevfocused, None);\n\n\tredraw_frame(c, None);\n}\n\nvoid\nmove_client(client_t *c)\n{\n\tstrut_t s = { 0 };\n\n\tif (c->state & (STATE_ZOOMED | STATE_FULLSCREEN | STATE_DOCK))\n\t\treturn;\n\n\tcollect_struts(c, &s);\n\tdragging = c;\n\tsweep(c, move_curs, recalc_move, NULL, &s);\n\tdragging = NULL;\n\n\tif (!(c->state & STATE_ICONIFIED))\n\t\tredraw_frame(c, None);\n\n\tsend_config(c);\n\tflush_expose_client(c);\n}\n\n/*\n * If we are resizing a client that was zoomed, we have to put it in an\n * unzoomed state, but we need to start sweeping from the effective geometry\n * rather than the \"real\" geometry that unzooming will restore. We get around\n * this by blatantly cheating.\n */\nvoid\nresize_client(client_t *c, Window resize_win)\n{\n\tstrut_t hold = { 0, 0, 0, 0 };\n\tXEvent junk;\n\n\tif (c->state & STATE_ZOOMED) {\n\t\tc->save = c->geom;\n\t\tunzoom_client(c);\n\t}\n\n\tsweep(c, cursor_for_resize_win(c, resize_win), recalc_resize,\n\t    &resize_win, &hold);\n\n\tif (c->shaped) {\n\t\t/* flush ShapeNotify events */\n\t\twhile (XCheckTypedWindowEvent(dpy, c->win, shape_event, &junk))\n\t\t\t;\n\t}\n}\n\n/*\n * The user has clicked on a toolbar button but may mouse off of it and then\n * let go, so only consider it a click if the mouse is still there when the\n * mouse button is released.\n */\nvoid\nmaybe_toolbar_click(client_t *c, Window win)\n{\n\tif (win == c->iconify)\n\t\tc->iconify_pressed = True;\n\telse if (win == c->zoom)\n\t\tc->zoom_pressed = True;\n\telse if (win == c->close)\n\t\tc->close_pressed = True;\n\telse\n\t\treturn;\n\n\tredraw_frame(c, win);\n\tsweep(c, None, monitor_toolbar_click, &win, NULL);\n\tredraw_frame(c, win);\n}\n\nvoid\nmonitor_toolbar_click(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,\n    strut_t *s, void *arg)\n{\n\tWindow win = *(Window *)arg;\n\tgeom_t *geom;\n\tBool was, *pr;\n\n\tif (win == c->iconify) {\n\t\tgeom = &c->iconify_geom;\n\t\tpr = &c->iconify_pressed;\n\t} else if (win == c->zoom) {\n\t\tgeom = &c->zoom_geom;\n\t\tpr = &c->zoom_pressed;\n\t} else if (win == c->close) {\n\t\tgeom = &c->close_geom;\n\t\tpr = &c->close_pressed;\n\t} else\n\t\treturn;\n\n\twas = *pr;\n\n\tif (x1 >= (c->frame_geom.x + geom->x) &&\n\t    x1 <= (c->frame_geom.x + geom->x + geom->w) &&\n\t    y1 >= (c->frame_geom.y + geom->y) &&\n\t    y1 <= (c->frame_geom.y + geom->y + geom->h))\n\t\t*pr = True;\n\telse\n\t\t*pr = False;\n\n\tif (was != *pr)\n\t\tredraw_frame(c, win);\n}\n\n\n/* Transients will be iconified when their owner is iconified. */\nvoid\niconify_client(client_t *c)\n{\n\tclient_t *p;\n\n\tfor (p = focused; p; p = p->next)\n\t\tif (p->trans == c->win)\n\t\t\tdo_iconify(p);\n\n\tdo_iconify(c);\n\n\tfocus_client(focused, FOCUS_FORCE);\n}\n\nvoid\ndo_iconify(client_t *c)\n{\n\tXSetWindowAttributes attrs = { 0 };\n\tXGCValues gv;\n\n\tadjust_client_order(c, ORDER_ICONIFIED_TOP);\n\n\tif (!c->ignore_unmap)\n\t\tc->ignore_unmap++;\n\tXUnmapWindow(dpy, c->frame);\n\tXUnmapWindow(dpy, c->win);\n\tc->state |= STATE_ICONIFIED;\n\tset_wm_state(c, IconicState);\n\n\tget_client_icon(c);\n\n\tif (c->icon_name)\n\t\tXFree(c->icon_name);\n\tc->icon_name = get_wm_icon_name(c->win);\n\n\tif (c->icon_geom.w < 1)\n\t\tc->icon_geom.w = icon_size;\n\tif (c->icon_geom.h < 1)\n\t\tc->icon_geom.h = icon_size;\n\n\tattrs.background_pixel = BlackPixel(dpy, screen);\n\tattrs.event_mask = ButtonPressMask | ButtonReleaseMask |\n\t    VisibilityChangeMask | ExposureMask | KeyPressMask |\n\t    EnterWindowMask | FocusChangeMask;\n\n\tplace_icon(c);\n\n\tc->icon = XCreateWindow(dpy, root, c->icon_geom.x, c->icon_geom.h,\n\t    c->icon_geom.w, c->icon_geom.h, 0, CopyFromParent, CopyFromParent,\n\t    CopyFromParent, CWBackPixel | CWEventMask, &attrs);\n\tset_atoms(c->icon, net_wm_wintype, XA_ATOM, &net_wm_type_desk, 1);\n\tXMapWindow(dpy, c->icon);\n\n\tc->icon_label = XCreateWindow(dpy, root, 0, 0, c->icon_geom.w,\n\t    c->icon_geom.h, 0, CopyFromParent, CopyFromParent, CopyFromParent,\n\t    CWBackPixel | CWEventMask, &attrs);\n\tset_atoms(c->icon_label, net_wm_wintype, XA_ATOM, &net_wm_type_desk, 1);\n\tXMapWindow(dpy, c->icon_label);\n\tc->icon_xftdraw = XftDrawCreate(dpy, (Drawable)c->icon_label,\n\t    DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));\n\n\tc->icon_gc = XCreateGC(dpy, c->icon, 0, &gv);\n\n\tredraw_icon(c, None);\n\tflush_expose_client(c);\n}\n\nvoid\nuniconify_client(client_t *c)\n{\n\tif (c->desk != cur_desk) {\n\t\tc->desk = cur_desk;\n\t\tset_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk, 1);\n\t}\n\n\tXMapWindow(dpy, c->win);\n\tXMapRaised(dpy, c->frame);\n\tc->state &= ~STATE_ICONIFIED;\n\tset_wm_state(c, NormalState);\n\n\tc->ignore_unmap++;\n\tXDestroyWindow(dpy, c->icon);\n\tc->icon = None;\n\tc->ignore_unmap++;\n\tif (c->icon_xftdraw) {\n\t\tXftDrawDestroy(c->icon_xftdraw);\n\t\tc->icon_xftdraw = None;\n\t}\n\tXDestroyWindow(dpy, c->icon_label);\n\tc->icon_label = None;\n\n\tfocus_client(c, FOCUS_FORCE);\n}\n\nvoid\nplace_icon(client_t *c)\n{\n\tstrut_t s = { 0 };\n\tclient_t *p;\n\tint x, y, isize;\n\n\tcollect_struts(c, &s);\n\n\ts.right = DisplayWidth(dpy, screen) - s.right;\n\ts.bottom = DisplayHeight(dpy, screen) - s.bottom;\n\n\tisize = icon_size * 2.25;\n\n\tfor (y = s.bottom - isize; y >= s.top; y -= isize) {\n\t\tfor (x = s.left + icon_size; x < s.right - isize; x += isize) {\n\t\t\tint overlap = 0;\n\n\t\t\tfor (p = focused; p; p = p->next) {\n\t\t\t\tif (p == c || !(p->state & STATE_ICONIFIED))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif ((p->icon_geom.x + icon_size >= x &&\n\t\t\t\t    p->icon_geom.x <= x) &&\n\t\t\t\t    (p->icon_geom.y + icon_size >= y &&\n\t\t\t\t    p->icon_geom.y <= y)) {\n\t\t\t\t\toverlap = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (overlap)\n\t\t\t\tcontinue;\n\n\t\t\tc->icon_geom.x = x;\n\t\t\tc->icon_geom.y = y;\n#ifdef DEBUG\n\t\t\tdump_geom(c, c->icon_geom, \"place_icon\");\n#endif\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/* shrug */\n\tc->icon_geom.x = s.left;\n\tc->icon_geom.y = s.top;\n}\n\nvoid\nshade_client(client_t *c)\n{\n\tif (c->state != STATE_NORMAL || (c->frame_style == FRAME_NONE))\n\t\treturn;\n\n\tc->state |= STATE_SHADED;\n\tappend_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_shaded, 1);\n\tdo_shade(c);\n}\n\nvoid\nunshade_client(client_t *c)\n{\n\tif (!(c->state & STATE_SHADED))\n\t\treturn;\n\n\tc->state &= ~(STATE_SHADED);\n\tremove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_shaded);\n\tdo_shade(c);\n}\n\nstatic void\ndo_shade(client_t *c)\n{\n\tif (c->frame) {\n\t\tredraw_frame(c, None);\n\n\t\tif (c->state & STATE_SHADED) {\n\t\t\tXUndefineCursor(dpy, c->resize_nw);\n\t\t\tXUndefineCursor(dpy, c->resize_n);\n\t\t\tXUndefineCursor(dpy, c->resize_ne);\n\t\t\tXUndefineCursor(dpy, c->resize_s);\n\t\t} else {\n\t\t\tXDefineCursor(dpy, c->resize_nw, resize_nw_curs);\n\t\t\tXDefineCursor(dpy, c->resize_n, resize_n_curs);\n\t\t\tXDefineCursor(dpy, c->resize_ne, resize_ne_curs);\n\t\t\tXDefineCursor(dpy, c->resize_s, resize_s_curs);\n\t\t}\n\t}\n\tsend_config(c);\n\tflush_expose_client(c);\n}\n\nvoid\nfullscreen_client(client_t *c)\n{\n\tint screen_x = DisplayWidth(dpy, screen);\n\tint screen_y = DisplayHeight(dpy, screen);\n\n#ifdef DEBUG\n\tdump_name(c, __func__, NULL, c->name);\n#endif\n\n\tif (c->state & (STATE_FULLSCREEN | STATE_DOCK))\n\t\treturn;\n\n\tif (c->state & STATE_SHADED)\n\t\tunshade_client(c);\n\n\tc->save = c->geom;\n\tc->geom.x = 0;\n\tc->geom.y = 0;\n\tc->geom.w = screen_x;\n\tc->geom.h = screen_y;\n\tc->state |= STATE_FULLSCREEN;\n\tredraw_frame(c, None);\n\tsend_config(c);\n\tflush_expose_client(c);\n}\n\nvoid\nunfullscreen_client(client_t *c)\n{\n#ifdef DEBUG\n\tdump_name(c, __func__, NULL, c->name);\n#endif\n\n\tif (!(c->state & STATE_FULLSCREEN))\n\t\treturn;\n\n\tc->geom = c->save;\n\tc->state &= ~STATE_FULLSCREEN;\n\n\trecalc_frame(c);\n\tredraw_frame(c, None);\n\tsend_config(c);\n\tflush_expose_client(c);\n}\n\n/*\n * When zooming a window, the old geom gets stuffed into c->save. Once we\n * unzoom, this should be considered garbage. Despite the existence of vertical\n * and horizontal hints, we only provide both at once.\n *\n * Zooming implies unshading, but the inverse is not true.\n */\nvoid\nzoom_client(client_t *c)\n{\n\tstrut_t s = { 0 };\n\n\tif (c->state & STATE_DOCK)\n\t\treturn;\n\n\tif (c->state & STATE_SHADED)\n\t\tunshade_client(c);\n\n\tc->save = c->geom;\n\tc->state |= STATE_ZOOMED;\n\n\tcollect_struts(c, &s);\n\trecalc_frame(c);\n\n\tc->geom.x = s.left;\n\tc->geom.y = s.top + c->titlebar_geom.h;\n\tc->geom.w = DisplayWidth(dpy, screen) - s.left - s.right;\n\tc->geom.h = DisplayHeight(dpy, screen) - s.top - s.bottom - c->geom.y;\n\n\tappend_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_mv, 1);\n\tappend_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_mh, 1);\n\tredraw_frame(c, None);\n\tsend_config(c);\n\tflush_expose_client(c);\n}\n\nvoid\nunzoom_client(client_t *c)\n{\n\tif (!(c->state & STATE_ZOOMED))\n\t\treturn;\n\n\tc->geom = c->save;\n\tc->state &= ~STATE_ZOOMED;\n\n\tremove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_mv);\n\tremove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_mh);\n\tredraw_frame(c, None);\n\tsend_config(c);\n\tflush_expose_client(c);\n}\n\n/*\n * The name of this function is a little misleading: if the client doesn't\n * listen to WM_DELETE then we just terminate it with extreme prejudice.\n */\nvoid\nsend_wm_delete(client_t *c)\n{\n\tint i, n, found = 0;\n\tAtom *protocols;\n\n\tif (XGetWMProtocols(dpy, c->win, &protocols, &n)) {\n\t\tfor (i = 0; i < n; i++)\n\t\t\tif (protocols[i] == wm_delete)\n\t\t\t\tfound++;\n\t\tXFree(protocols);\n\t}\n\tif (found)\n\t\tsend_xmessage(c->win, c->win, wm_protos, wm_delete,\n\t\t    NoEventMask);\n\telse\n\t\tXKillClient(dpy, c->win);\n}\n\nvoid\ngoto_desk(int new_desk)\n{\n\tclient_t *c, *newfocus = NULL;\n\n\tif (new_desk >= ndesks || new_desk < 0)\n\t\treturn;\n\n\tcur_desk = new_desk;\n\tset_atoms(root, net_cur_desk, XA_CARDINAL, &cur_desk, 1);\n\n\tfor (c = focused; c; c = c->next) {\n\t\tif (dragging == c) {\n\t\t\tc->desk = cur_desk;\n\t\t\tset_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk,\n\t\t\t    1);\n\t\t}\n\n\t\tif (IS_ON_CUR_DESK(c)) {\n\t\t\tif (c->state & STATE_ICONIFIED) {\n\t\t\t\tXMapWindow(dpy, c->icon);\n\t\t\t\tXMapWindow(dpy, c->icon_label);\n\t\t\t} else {\n\t\t\t\tif (!newfocus && !(c->state & STATE_DOCK))\n\t\t\t\t\tnewfocus = c;\n\n\t\t\t\tXMapWindow(dpy, c->frame);\n\t\t\t}\n\t\t} else {\n\t\t\tif (c->state & STATE_ICONIFIED) {\n\t\t\t\tXUnmapWindow(dpy, c->icon);\n\t\t\t\tXUnmapWindow(dpy, c->icon_label);\n\t\t\t} else\n\t\t\t\tXUnmapWindow(dpy, c->frame);\n\t\t}\n\n\t\tsend_config(c);\n\t}\n\n\trestack_clients();\n\n\tif (newfocus)\n\t\tfocus_client(newfocus, FOCUS_FORCE);\n}\n\nvoid\nmap_if_desk(client_t *c)\n{\n\tif (IS_ON_CUR_DESK(c) && get_wm_state(c->win) == NormalState)\n\t\tXMapWindow(dpy, c->frame);\n\telse\n\t\tXUnmapWindow(dpy, c->frame);\n}\n\nstatic XEvent sweepev;\nvoid\nsweep(client_t *c, Cursor curs, sweep_func cb, void *cb_arg, strut_t *s)\n{\n\tgeom_t orig = (c->state & STATE_ICONIFIED ? c->icon_geom : c->geom);\n\tclient_t *ec;\n\tstrut_t as = { 0 };\n\tint x0, y0, done = 0;\n\n\tget_pointer(&x0, &y0);\n\tcollect_struts(c, &as);\n\trecalc_frame(c);\n\n\tif (XGrabPointer(dpy, root, False, MouseMask, GrabModeAsync,\n\t    GrabModeAsync, root, curs, CurrentTime) != GrabSuccess)\n\t\treturn;\n\n\tcb(c, orig, x0, y0, x0, y0, s, cb_arg);\n\n\twhile (!done) {\n\t\tXMaskEvent(dpy, ExposureMask | MouseMask | PointerMotionMask |\n\t\t    StructureNotifyMask | SubstructureNotifyMask |\n\t\t    KeyPressMask | KeyReleaseMask, &sweepev);\n#ifdef DEBUG\n\t\tshow_event(sweepev);\n#endif\n\t\tswitch (sweepev.type) {\n\t\tcase Expose:\n\t\t\tif ((ec = find_client(sweepev.xexpose.window,\n\t\t\t    MATCH_FRAME)))\n\t\t\t\tredraw_frame(ec, sweepev.xexpose.window);\n\t\t\tbreak;\n\t\tcase MotionNotify:\n\t\t\tcb(c, orig, x0, y0, sweepev.xmotion.x,\n\t\t\t    sweepev.xmotion.y, s, cb_arg);\n\t\t\tbreak;\n\t\tcase ButtonRelease:\n\t\t\tdone = 1;\n\t\t\tbreak;\n\t\tcase UnmapNotify:\n\t\t\tif (c->win == sweepev.xunmap.window) {\n\t\t\t\tdone = 1;\n\t\t\t\tXPutBackEvent(dpy, &sweepev);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\thandle_unmap_event(&sweepev.xunmap);\n\t\t\tbreak;\n\t\tcase KeyPress:\n\t\tcase KeyRelease:\n\t\t\t/* to allow switching desktops while dragging */\n\t\t\thandle_key_event(&sweepev.xkey);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tXUngrabPointer(dpy, CurrentTime);\n}\n\n/*\n * This is simple and dumb: if the cursor is in the center of the screen,\n * center the window on the available space. If it's at the top left, then at\n * the top left. As you go between, and to other edges, scale it.\n */\nvoid\nrecalc_map(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,\n    strut_t *s, void *arg)\n{\n\tint screen_x = DisplayWidth(dpy, screen);\n\tint screen_y = DisplayHeight(dpy, screen);\n\tint wmax = screen_x - s->left - s->right;\n\tint hmax = screen_y - s->top - s->bottom;\n\n\tc->geom.x = s->left + ((float) x1 / (float) screen_x) *\n\t    (wmax + 1 - c->geom.w - (2 * c->resize_nw_geom.w));\n\tc->geom.y = s->top + ((float) y1 / (float) screen_y) *\n\t    (hmax + 1 - c->geom.h - c->titlebar_geom.h -\n\t    (2 * c->resize_w_geom.w));\n}\n\nvoid\nrecalc_move(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,\n    strut_t *s, void *arg)\n{\n\tint newx = orig.x + x1 - x0;\n\tint newy = orig.y + y1 - y0;\n\tint sw = DisplayWidth(dpy, screen);\n\tint sh = DisplayHeight(dpy, screen);\n\tgeom_t tg;\n\n\tif (c->state & STATE_ICONIFIED) {\n\t\tint xd = newx - c->icon_geom.x;\n\t\tint yd = newy - c->icon_geom.y;\n\n\t\tc->icon_geom.x = newx;\n\t\tc->icon_geom.y = newy;\n\t\tc->icon_label_geom.x += xd;\n\t\tc->icon_label_geom.y += yd;\n\n\t\tXMoveWindow(dpy, c->icon,\n\t\t    c->icon_geom.x + ((icon_size - c->icon_geom.w) / 2),\n\t\t    c->icon_geom.y + ((icon_size - c->icon_geom.h) / 2));\n\t\tXMoveWindow(dpy, c->icon_label, c->icon_label_geom.x,\n\t\t    c->icon_label_geom.y);\n\t\tsend_config(c);\n\t\tflush_expose_client(c);\n\t\treturn;\n\t}\n\n\tsw -= s->right;\n\tsh -= s->bottom;\n\tmemcpy(&tg, &c->frame_geom, sizeof(c->frame_geom));\n\n\t/* provide some resistance at screen edges */\n\tif (x1 < x0) {\n\t\t/* left edge */\n\t\tif (newx - c->resize_w_geom.w >= (long)s->left ||\n\t\t    newx - c->resize_w_geom.w < (long)s->left -\n\t\t    opt_edge_resist) {\n\t\t\tc->geom.x = newx;\n\t\t} else {\n\t\t\tc->geom.x = (long)s->left + c->resize_w_geom.w;\n\t\t}\n\t} else {\n\t\t/* right edge */\n\t\tif (newx + c->geom.w + c->resize_e_geom.w <= sw ||\n\t\t    newx + c->geom.w + c->resize_e_geom.w > sw +\n\t\t    opt_edge_resist) {\n\t\t\tc->geom.x = newx;\n\t\t} else {\n\t\t\tc->geom.x = sw - c->geom.w - c->resize_e_geom.w;\n\t\t}\n\t}\n\n\tif (y1 < y0) {\n\t\t/* top edge */\n\t\tif (newy - c->resize_n_geom.h - c->titlebar_geom.h >=\n\t\t    (long)s->top ||\n\t\t    newy - c->resize_n_geom.h - c->titlebar_geom.h <\n\t\t    (long)s->top - opt_edge_resist) {\n\t\t\tc->geom.y = newy;\n\t\t} else {\n\t\t\tc->geom.y = (long)s->top + c->resize_n_geom.h +\n\t\t\t\t    c->titlebar_geom.h;\n\t\t}\n\t} else {\n\t\t/* bottom edge */\n\t\tif (newy + c->geom.h + c->resize_s_geom.h <= sh ||\n\t\t    newy + c->geom.h + c->resize_s_geom.h > sh +\n\t\t    opt_edge_resist) {\n\t\t\tc->geom.y = newy;\n\t\t} else {\n\t\t\tc->geom.y = sh - c->geom.h - c->resize_s_geom.h;\n\t\t}\n\t}\n\n\trecalc_frame(c);\n\n\tif (c->frame_geom.x == tg.x && c->frame_geom.y == tg.y)\n\t\treturn;\n\n\tXMoveWindow(dpy, c->frame, c->frame_geom.x, c->frame_geom.y);\n}\n\nvoid\nrecalc_resize(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,\n    strut_t *move, void *arg)\n{\n\tWindow resize_pos = *(Window *)arg;\n\tgeom_t now = { c->geom.x, c->geom.y, c->geom.w, c->geom.h };\n\n\tif (resize_pos == c->resize_nw)\n\t\tmove->top = move->left = 1;\n\telse if (resize_pos == c->resize_n)\n\t\tmove->top = 1;\n\telse if (resize_pos == c->resize_ne)\n\t\tmove->top = move->right = 1;\n\telse if (resize_pos == c->resize_e)\n\t\tmove->right = 1;\n\telse if (resize_pos == c->resize_se)\n\t\tmove->right = move->bottom = 1;\n\telse if (resize_pos == c->resize_s)\n\t\tmove->bottom = 1;\n\telse if (resize_pos == c->resize_sw)\n\t\tmove->bottom = move->left = 1;\n\telse if (resize_pos == c->resize_w)\n\t\tmove->left = 1;\n\n\tif (move->left)\n\t\tc->geom.w = orig.w + (x0 - x1);\n\tif (move->top)\n\t\tc->geom.h = orig.h + (y0 - y1);\n\tif (move->right) {\n\t\tc->geom.w = orig.w - (x0 - x1);\n\t\tc->geom.x = orig.x - (x0 - x1);\n\t}\n\tif (move->bottom) {\n\t\tc->geom.h = orig.h - (y0 - y1);\n\t\tc->geom.y = orig.y - (y0 - y1);\n\t}\n\n\tfix_size(c);\n\n\tif (move->left)\n\t\tc->geom.x = orig.x + orig.w - c->geom.w;\n\tif (move->top)\n\t\tc->geom.y = orig.y + orig.h - c->geom.h;\n\tif (move->right)\n\t\tc->geom.x = orig.x;\n\tif (move->bottom)\n\t\tc->geom.y = orig.y;\n\n\tfix_size(c);\n\n\tif (c->geom.w != now.w || c->geom.h != now.h) {\n\t\tredraw_frame(c, None);\n\t\tif (c->shaped)\n\t\t\tset_shape(c);\n\t\tsend_config(c);\n\t}\n}\n\n/*\n * If the window in question has a ResizeInc hint, then it wants to be resized\n * in multiples of some (x,y). We constrain the values in c->geom based on that\n * and any min/max size hints.\n */\nvoid\nfix_size(client_t *c)\n{\n\tint width_inc, height_inc;\n\tint base_width, base_height;\n\n\tif (c->size_hints.flags & PMinSize) {\n\t\tif (c->geom.w < c->size_hints.min_width)\n\t\t\tc->geom.w = c->size_hints.min_width;\n\t\tif (c->geom.h < c->size_hints.min_height)\n\t\t\tc->geom.h = c->size_hints.min_height;\n\t}\n\tif (c->size_hints.flags & PMaxSize) {\n\t\tif (c->geom.w > c->size_hints.max_width)\n\t\t\tc->geom.w = c->size_hints.max_width;\n\t\tif (c->geom.h > c->size_hints.max_height)\n\t\t\tc->geom.h = c->size_hints.max_height;\n\t}\n\n\tif (c->size_hints.flags & PResizeInc) {\n\t\twidth_inc = c->size_hints.width_inc ?\n\t\t    c->size_hints.width_inc : 1;\n\t\theight_inc = c->size_hints.height_inc ?\n\t\t    c->size_hints.height_inc : 1;\n\t\tbase_width = (c->size_hints.flags & PBaseSize) ?\n\t\t    c->size_hints.base_width :\n\t\t    ((c->size_hints.flags & PMinSize) ?\n\t\t    c->size_hints.min_width : 0);\n\t\tbase_height = (c->size_hints.flags & PBaseSize) ?\n\t\t    c->size_hints.base_height :\n\t\t    (c->size_hints.flags & PMinSize) ?\n\t\t    c->size_hints.min_height : 0;\n\t\tc->geom.w -= (c->geom.w - base_width) % width_inc;\n\t\tc->geom.h -= (c->geom.h - base_height) % height_inc;\n\t}\n}\n\n/* make sure a frame fits on the screen */\nvoid\nconstrain_frame(client_t *c)\n{\n\tstrut_t s = { 0 };\n\tint h, w, delta;\n\n\tif (c->state & STATE_FULLSCREEN)\n\t\treturn;\n\n#ifdef DEBUG\n\tdump_geom(c, c->geom, \"constrain_frame initial\");\n#endif\n\n\trecalc_frame(c);\n\tfix_size(c);\n\n\tcollect_struts(c, &s);\n\n\tif (c->frame_geom.x < s.left) {\n\t\tdelta = s.left - c->frame_geom.x;\n\t\tc->frame_geom.x += delta;\n\t\tc->geom.x += delta;\n\t}\n\tif (c->frame_geom.y < s.top) {\n\t\tdelta = s.top - c->frame_geom.y;\n\t\tc->frame_geom.y += delta;\n\t\tc->geom.y += delta;\n\t}\n\n\th = DisplayHeight(dpy, screen) - s.top - s.bottom;\n\tif (c->frame_geom.y + c->frame_geom.h > h) {\n\t\tdelta = c->frame_geom.y + c->frame_geom.h - h;\n\t\tc->frame_geom.h -= delta;\n\t\tc->geom.h -= delta;\n\t}\n\n\tw = DisplayWidth(dpy, screen) - s.left - s.right;\n\tif (c->frame_geom.x + c->frame_geom.w > w) {\n\t\tdelta = c->frame_geom.x + c->frame_geom.w - w;\n\t\tc->frame_geom.w -= delta;\n\t\tc->geom.w -= delta;\n\t}\n\n\t/* TODO: fix_size() again but don't allow enlarging */\n\n\trecalc_frame(c);\n\n#ifdef DEBUG\n\tdump_geom(c, c->geom, \"constrain_frame final\");\n#endif\n}\n\nvoid\nflush_expose_client(client_t *c)\n{\n\tif (c->resize_nw)\n\t\tflush_expose(c->resize_nw);\n\tif (c->resize_n)\n\t\tflush_expose(c->resize_n);\n\tif (c->resize_ne)\n\t\tflush_expose(c->resize_ne);\n\tif (c->resize_e)\n\t\tflush_expose(c->resize_e);\n\tif (c->resize_se)\n\t\tflush_expose(c->resize_se);\n\tif (c->resize_s)\n\t\tflush_expose(c->resize_s);\n\tif (c->resize_sw)\n\t\tflush_expose(c->resize_sw);\n\tif (c->resize_w)\n\t\tflush_expose(c->resize_w);\n\tif (c->close)\n\t\tflush_expose(c->close);\n\tif (c->iconify)\n\t\tflush_expose(c->iconify);\n\tif (c->zoom)\n\t\tflush_expose(c->zoom);\n\tif (c->titlebar)\n\t\tflush_expose(c->titlebar);\n\tif (c->icon)\n\t\tflush_expose(c->icon);\n\tif (c->icon_label)\n\t\tflush_expose(c->icon_label);\n}\n\n/* remove expose events for a window from the event queue */\nvoid\nflush_expose(Window win)\n{\n\tXEvent junk;\n\twhile (XCheckTypedWindowEvent(dpy, win, Expose, &junk))\n\t\t;\n}\n\nint\noverlapping_geom(geom_t a, geom_t b)\n{\n\tif (a.x <= b.x + b.w && a.x + a.w >= b.x &&\n\t    a.y <= b.y + b.h && a.y + a.h >= b.y)\n\t\treturn 1;\n\n\treturn 0;\n}\n\nvoid\nrestack_clients(void)\n{\n\tWindow *wins = NULL;\n\tclient_t *p;\n\tint twins = 0, nwins = 0;\n\n\t/* restack windows - ABOVE, normal, BELOW, ICONIFIED */\n\tfor (p = focused, twins = 0; p; p = p->next)\n\t\ttwins += 2;\n\n\tif (twins == 0)\n\t\treturn;\n\n\twins = realloc(wins, twins * sizeof(Window));\n\tif (wins == NULL)\n\t\terr(1, \"realloc\");\n\n\t/* STATE_ABOVE first */\n\tfor (p = focused; p; p = p->next) {\n\t\tif (!IS_ON_CUR_DESK(p))\n\t\t\tcontinue;\n\n\t\tif ((p->state & STATE_ABOVE) && !(p->state & STATE_ICONIFIED))\n\t\t\twins[nwins++] = p->frame;\n\t}\n\n\t/* then non-iconified windows */\n\tfor (p = focused; p; p = p->next) {\n\t\tif (!IS_ON_CUR_DESK(p))\n\t\t\tcontinue;\n\n\t\tif (!(p->state & (STATE_ICONIFIED | STATE_BELOW | STATE_ABOVE |\n\t\t    STATE_DOCK)))\n\t\t\twins[nwins++] = p->frame;\n\t}\n\n\t/* then BELOW windows */\n\tfor (p = focused; p; p = p->next) {\n\t\tif (!IS_ON_CUR_DESK(p))\n\t\t\tcontinue;\n\n\t\tif (p->state & (STATE_BELOW | STATE_DOCK))\n\t\t\twins[nwins++] = p->frame;\n\t}\n\n\t/* then icons, taking from all desks */\n\tfor (p = focused; p; p = p->next) {\n\t\tif (p->state & STATE_ICONIFIED) {\n\t\t\twins[nwins++] = p->icon;\n\t\t\twins[nwins++] = p->icon_label;\n\t\t}\n\t}\n\n\tif (nwins > twins) {\n\t\twarnx(\"%s allocated for %d windows, used %d\", __func__,\n\t\t    twins, nwins);\n\t\tabort();\n\t}\n\n\tXRestackWindows(dpy, wins, nwins);\n\n\tfree(wins);\n\n\t/* TODO: update net_client_stack */\n}\n\nvoid\nadjust_client_order(client_t *c, int where)\n{\n\tclient_t *p, *pp;\n\n\tif (c != NULL && focused == c && !c->next)\n\t\treturn;\n\n\tswitch (where) {\n\tcase ORDER_TOP:\n\t\tfor (p = focused; p && p->next; p = p->next) {\n\t\t\tif (p->next == c) {\n\t\t\t\tp->next = c->next;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (c != focused) {\n\t\t\tc->next = focused;\n\t\t\tfocused = c;\n\t\t}\n\t\tbreak;\n\tcase ORDER_ICONIFIED_TOP:\n\t\t/* remove first */\n\t\tif (focused == c)\n\t\t\tfocused = c->next;\n\t\telse {\n\t\t\tfor (p = focused; p && p->next; p = p->next)\n\t\t\t\tif (p->next == c) {\n\t\t\t\t\tp->next = c->next;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\tc->next = NULL;\n\n\t\tp = focused; pp = NULL;\n\t\twhile (p && p->next) {\n\t\t\tif (!(p->state & STATE_ICONIFIED)) {\n\t\t\t\tpp = p;\n\t\t\t\tp = p->next;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (pp)\n\t\t\t\t/* place ahead of this first iconfied client */\n\t\t\t\tpp->next = c;\n\t\t\telse\n\t\t\t\t/* no previous non-iconified clients */\n\t\t\t\tfocused = c;\n\n\t\t\tif (c != p)\n\t\t\t\tc->next = p;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!c->next) {\n\t\t\t/* no iconified clients, place at the bottom */\n\t\t\tif (p)\n\t\t\t\tp->next = c;\n\t\t\telse\n\t\t\t\tfocused = c;\n\t\t\tc->next = NULL;\n\t\t}\n\t\tbreak;\n\tcase ORDER_BOTTOM:\n\t\tfor (p = focused; p && p->next; p = p->next) {\n\t\t\tif (p->next == c)\n\t\t\t\tp->next = c->next;\n\t\t\t/* continue until p->next == NULL */\n\t\t}\n\n\t\tif (p)\n\t\t\tp->next = c;\n\t\telse\n\t\t\tfocused = c;\n\n\t\tc->next = NULL;\n\t\tbreak;\n\tcase ORDER_OUT:\n\t\tif (c == focused)\n\t\t\tfocused = c->next;\n\t\telse {\n\t\t\tfor (p = focused; p && p->next; p = p->next) {\n\t\t\t\tif (p->next == c) {\n\t\t\t\t\tp->next = c->next;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase ORDER_INVERT: {\n\t\tclient_t *pp, *n, *tail;\n\n\t\tfor (tail = focused; tail && tail->next; tail = tail->next)\n\t\t\t;\n\n\t\tfor (p = focused, pp = NULL, n = tail; n; pp = p, p = n) {\n\t\t\tn = p->next;\n\t\t\tp->next = pp;\n\t\t}\n\t\tfocused = tail;\n\n\t\tbreak;\n\t}\n\tdefault:\n\t\tprintf(\"unknown client sort option %d\\n\", where);\n\t}\n}\n\nclient_t *\nnext_client_for_focus(client_t *head)\n{\n\tclient_t *n;\n\n\tfor (n = head->next; n; n = n->next)\n\t\tif (IS_ON_CUR_DESK(n) && !(n->state & STATE_DOCK))\n\t\t\treturn n;\n\n\treturn NULL;\n}\n\n#ifdef DEBUG\nchar *\nstate_name(client_t *c)\n{\n\tint s = 30;\n\tchar *res;\n\n\tres = malloc(s);\n\tif (res == NULL)\n\t\terr(1, \"malloc\");\n\n\tres[0] = '\\0';\n\n\tif (c->state == STATE_NORMAL)\n\t\tstrlcat(res, \"normal\", s);\n\tif (c->state & STATE_ZOOMED)\n\t\tstrlcat(res, \"| zoomed\", s);\n\tif (c->state & STATE_ICONIFIED)\n\t\tstrlcat(res, \"| iconified\", s);\n\tif (c->state & STATE_SHADED)\n\t\tstrlcat(res, \"| shaded\", s);\n\tif (c->state & STATE_FULLSCREEN)\n\t\tstrlcat(res, \"| fs\", s);\n\tif (c->state & STATE_DOCK)\n\t\tstrlcat(res, \"| dock\", s);\n\n\tif (res[0] == '|')\n\t\tres = strdup(res + 2);\n\n\treturn res;\n}\n\nconst char *\nframe_name(client_t *c, Window w)\n{\n\tif (w == None)\n\t\treturn \"\";\n\tif (w == c->frame)\n\t\treturn \"frame\";\n\tif (w == c->resize_nw)\n\t\treturn \"resize_nw\";\n\tif (w == c->resize_w)\n\t\treturn \"resize_w\";\n\tif (w == c->resize_sw)\n\t\treturn \"resize_sw\";\n\tif (w == c->resize_s)\n\t\treturn \"resize_s\";\n\tif (w == c->resize_se)\n\t\treturn \"resize_se\";\n\tif (w == c->resize_e)\n\t\treturn \"resize_e\";\n\tif (w == c->resize_ne)\n\t\treturn \"resize_ne\";\n\tif (w == c->resize_n)\n\t\treturn \"resize_n\";\n\tif (w == c->titlebar)\n\t\treturn \"titlebar\";\n\tif (w == c->close)\n\t\treturn \"close\";\n\tif (w == c->iconify)\n\t\treturn \"iconify\";\n\tif (w == c->zoom)\n\t\treturn \"zoom\";\n\tif (w == c->icon)\n\t\treturn \"icon\";\n\tif (w == c->icon_label)\n\t\treturn \"icon_label\";\n\treturn \"unknown\";\n}\n\nstatic const char *\nshow_grav(client_t *c)\n{\n\tif (!(c->size_hints.flags & PWinGravity))\n\t\treturn \"no grav (NW)\";\n\n\tswitch (c->size_hints.win_gravity) {\n\tSHOW(UnmapGravity)\n\tSHOW(NorthWestGravity)\n\tSHOW(NorthGravity)\n\tSHOW(NorthEastGravity)\n\tSHOW(WestGravity)\n\tSHOW(CenterGravity)\n\tSHOW(EastGravity)\n\tSHOW(SouthWestGravity)\n\tSHOW(SouthGravity)\n\tSHOW(SouthEastGravity)\n\tSHOW(StaticGravity)\n\tdefault:\n\t\treturn \"unknown grav\";\n\t}\n}\n\nvoid\ndump_name(client_t *c, const char *label, const char *detail, const char *name)\n{\n\tprintf(\"%18.18s: %#010lx [%-9.9s] %-35.35s\\n\", label,\n\t    c ? c->win : 0, detail == NULL ? \"\" : detail,\n\t    name == NULL ? \"\" : name);\n}\n\nvoid\ndump_info(client_t *c)\n{\n\tchar *s = state_name(c);\n\n\tprintf(\"%31s[i] ignore_unmap %d, trans 0x%lx, focus %d, shape %d\\n\", \"\",\n\t    c->ignore_unmap, c->trans, focused == c ? 1 : 0, c->shaped ? 1 : 0);\n\tprintf(\"%31s[i] desk %ld, state %s, %s\\n\", \"\",\n\t    c->desk, s, show_grav(c));\n\n\tfree(s);\n}\n\nvoid\ndump_geom(client_t *c, geom_t g, const char *label)\n{\n\tprintf(\"%31s[g] %s %ldx%ld+%ld+%ld\\n\", \"\",\n\t    label, g.w, g.h, g.x, g.y);\n}\n\nvoid\ndump_removal(client_t *c, int mode)\n{\n\tprintf(\"%31s[r] %s, %d pending\\n\", \"\",\n\t    mode == DEL_WITHDRAW ? \"withdraw\" : \"remap\", XPending(dpy));\n}\n\nvoid\ndump_clients(void)\n{\n\tclient_t *c;\n\n\tfor (c = focused; c; c = c->next) {\n\t\tdump_name(c, __func__, NULL, c->name);\n\t\tdump_geom(c, c->geom, \"current\");\n\t\tdump_info(c);\n\t}\n}\n#endif\n"
  },
  {
    "path": "parser.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <err.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"progman.h\"\n#include \"parser.h\"\n#include \"progman_ini.h\"\n\n/*\n * If the user specifies an ini file, return NULL immediately if it's not\n * found; otherwise, search for the usual suspects.\n */\nFILE *\nopen_ini(char *inifile)\n{\n\tFILE *ini = NULL;\n\tchar buf[BUF_SIZE];\n\n\tif (inifile) {\n\t\tini = fopen(inifile, \"r\");\n\t\tif (!ini)\n\t\t\terr(1, \"can't open config file %s\", inifile);\n\n\t\treturn ini;\n\t}\n\n\tsnprintf(buf, sizeof(buf), \"%s/.config/progman/progman.ini\",\n\t    getenv(\"HOME\"));\n\tif ((ini = fopen(buf, \"r\")))\n\t\treturn ini;\n\n\t/* load compiled-in defaults */\n\tini = fmemopen(progman_ini, sizeof(progman_ini), \"r\");\n\tif (!ini || sizeof(progman_ini) == 0)\n\t\terrx(1, \"no compiled-in default config file\");\n\n\treturn ini;\n}\n\nint\nfind_ini_section(FILE *stream, char *section)\n{\n\tchar buf[BUF_SIZE], marker[BUF_SIZE];\n\n\tsnprintf(marker, sizeof(marker), \"[%s]\\n\", section);\n\n\tfseek(stream, 0, SEEK_SET);\n\twhile (fgets(buf, sizeof(buf), stream)) {\n\t\tif (buf[0] == '#' || buf[0] == '\\n')\n\t\t\tcontinue;\n\t\tif (strncmp(buf, marker, strlen(marker)) == 0)\n\t\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nint\nget_ini_kv(FILE *stream, char **key, char **val)\n{\n\tchar buf[BUF_SIZE], *tval;\n\tlong pos = ftell(stream);\n\tint len;\n\n\tbuf[0] = '\\0';\n\n\t/* find next non-comment, non-section line */\n\twhile (fgets(buf, sizeof(buf), stream)) {\n\t\tif (buf[0] == '#' || buf[0] == '\\n') {\n\t\t\tpos = ftell(stream);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (buf[0] == '[') {\n\t\t\t/* new section, rewind so find_ini_section can see it */\n\t\t\tfseek(stream, pos, SEEK_SET);\n\t\t\treturn 0;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tif (!buf[0])\n\t\treturn 0;\n\n\ttval = strchr(buf, '=');\n\tif (tval == NULL) {\n\t\twarnx(\"bad line in ini file: %s\", buf);\n\t\treturn 0;\n\t}\n\n\ttval[0] = '\\0';\n\ttval++;\n\n\t/* trim trailing spaces from key */\n\tfor (len = strlen(buf); len > 0 && buf[len - 1] == ' '; len--)\n\t\t;\n\tbuf[len] = '\\0';\n\n\t/* trim leading spaces from val */\n\twhile (tval[0] == ' ')\n\t\ttval++;\n\n\t/* and trailing spaces and newlines from val */\n\tlen = strlen(tval) - 1;\n\twhile (len) {\n\t\tif (tval[len] == ' ' || tval[len] == '\\r' || tval[len] == '\\n')\n\t\t\tlen--;\n\t\telse\n\t\t\tbreak;\n\t}\n\ttval[len + 1] = '\\0';\n\n\t*key = strdup(buf);\n\t*val = strdup(tval);\n\n\treturn 1;\n}\n"
  },
  {
    "path": "parser.h",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef PROGMAN_PARSER_H\n#define PROGMAN_PARSER_H\n\n#include <stdio.h>\n\nFILE *open_ini(char *);\nint find_ini_section(FILE *, char *);\nint get_ini_kv(FILE *, char **, char **);\n\n#endif\t/* PROGMAN_PARSER_H */\n"
  },
  {
    "path": "progman.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifdef __linux__\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <signal.h>\n#include <locale.h>\n#include <errno.h>\n#include <err.h>\n#include <fcntl.h>\n#include <sys/wait.h>\n#include <X11/Xatom.h>\n#include <X11/cursorfont.h>\n#include <X11/extensions/shape.h>\n#ifdef USE_GDK_PIXBUF\n#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>\n#endif\n#include \"progman.h\"\n#include \"atom.h\"\n#include \"parser.h\"\n\n#include \"icons/close.xpm\"\n#include \"icons/utility_close.xpm\"\n#include \"icons/iconify.xpm\"\n#include \"icons/zoom.xpm\"\n#include \"icons/unzoom.xpm\"\n#include \"icons/default_icon.xpm\"\n#include \"icons/hidpi-close.xpm\"\n#include \"icons/hidpi-utility_close.xpm\"\n#include \"icons/hidpi-iconify.xpm\"\n#include \"icons/hidpi-zoom.xpm\"\n#include \"icons/hidpi-unzoom.xpm\"\n#include \"icons/hidpi-default_icon.xpm\"\n\n#ifndef WAIT_ANY\n#define WAIT_ANY (-1)\n#endif\n\nchar *orig_argv0;\nDisplay *dpy;\nWindow root;\nclient_t *cycle_head;\nclient_t *focused, *dragging;\nint screen;\nint ignore_xerrors = 0;\nunsigned long ndesks = DEF_NDESKS;\nunsigned long cur_desk = 0;\nunsigned int focus_order = 0;\nBool shape_support;\nint shape_event;\nWindow supporting_wm_win;\n\nXftFont *font;\nXftFont *iconfont;\nXftColor xft_fg;\nXftColor xft_fg_unfocused;\nXftColor xft_launcher;\nXftColor xft_launcher_highlighted;\n\nColormap def_cmap;\nXColor fg;\nXColor bg;\nXColor unfocused_fg;\nXColor unfocused_bg;\nXColor button_bg;\nXColor bevel_dark;\nXColor bevel_light;\nXColor border_fg;\nXColor border_bg;\nXColor launcher_fg;\nXColor launcher_bg;\nGC pixmap_gc;\nGC invert_gc;\nPixmap close_pm;\nPixmap close_pm_mask;\nXpmAttributes close_pm_attrs;\nPixmap utility_close_pm;\nPixmap utility_close_pm_mask;\nXpmAttributes utility_close_pm_attrs;\nPixmap iconify_pm;\nPixmap iconify_pm_mask;\nXpmAttributes iconify_pm_attrs;\nPixmap zoom_pm;\nPixmap zoom_pm_mask;\nXpmAttributes zoom_pm_attrs;\nPixmap unzoom_pm;\nPixmap unzoom_pm_mask;\nXpmAttributes unzoom_pm_attrs;\nPixmap default_icon_pm;\nPixmap default_icon_pm_mask;\nXpmAttributes default_icon_pm_attrs;\nCursor map_curs;\nCursor move_curs;\nCursor resize_n_curs;\nCursor resize_s_curs;\nCursor resize_e_curs;\nCursor resize_w_curs;\nCursor resize_nw_curs;\nCursor resize_sw_curs;\nCursor resize_ne_curs;\nCursor resize_se_curs;\n\nint exitmsg[2];\n\nchar *opt_config_file = NULL;\nchar *opt_font = DEF_FONT;\nchar *opt_iconfont = DEF_ICONFONT;\nchar *opt_fg = DEF_FG;\nchar *opt_bg = DEF_BG;\nchar *opt_unfocused_fg = DEF_UNFOCUSED_FG;\nchar *opt_unfocused_bg = DEF_UNFOCUSED_BG;\nchar *opt_button_bg = DEF_BUTTON_BG;\nchar *opt_bevel_dark = DEF_BEVEL_DARK;\nchar *opt_bevel_light = DEF_BEVEL_LIGHT;\nchar *opt_border_fg = DEF_BORDER_FG;\nchar *opt_border_bg = DEF_BORDER_BG;\nchar *opt_launcher_fg = DEF_LAUNCHER_FG;\nchar *opt_launcher_bg = DEF_LAUNCHER_BG;\nchar *opt_root_bg = DEF_ROOTBG;\nint opt_bw = DEF_BW;\nint opt_pad = DEF_PAD;\nint opt_bevel = DEF_BEVEL;\nint opt_edge_resist = DEF_EDGE_RES;\nint opt_scale = DEF_SCALE;\nint icon_size = ICON_SIZE_MULT * DEF_SCALE;\nint opt_drag_button = 0;\nint opt_drag_mod = 0;\n\nvoid read_config(void);\nvoid setup_display(void);\nvoid scale_icon(void *xpm, void *hidpi_xpm, Pixmap *pm, Pixmap *pm_mask,\n    XpmAttributes *xpm_attrs);\n\nint\nmain(int argc, char **argv)\n{\n\tstruct sigaction act;\n\tint ch;\n\n\torig_argv0 = strdup(argv[0]);\n\n\tsetlocale(LC_ALL, \"\");\n\n\twhile ((ch = getopt(argc, argv, \"c:\")) != -1) {\n\t\tswitch (ch) {\n\t\tcase 'c':\n\t\t\tif (opt_config_file)\n\t\t\t\tfree(opt_config_file);\n\t\t\topt_config_file = strdup(optarg);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tprintf(\"usage: %s [-c <config file>]\\n\", argv[0]);\n\t\t\texit(1);\n\t\t}\n\t}\n\targc -= optind;\n\targv += optind;\n\n\t/* parsing the config file may need dpy, so connect early */\n\tdpy = XOpenDisplay(NULL);\n\tif (!dpy)\n\t\terr(1, \"can't open $DISPLAY \\\"%s\\\"\", getenv(\"DISPLAY\"));\n\n\tXSetErrorHandler(handle_xerror);\n\tscreen = DefaultScreen(dpy);\n\troot = RootWindow(dpy, screen);\n\n\tread_config();\n\n\tif (pipe2(exitmsg, O_CLOEXEC) != 0)\n\t\terr(1, \"pipe2\");\n\n\tact.sa_handler = sig_handler;\n\tact.sa_flags = 0;\n\tsigaction(SIGTERM, &act, NULL);\n\tsigaction(SIGINT, &act, NULL);\n\tsigaction(SIGHUP, &act, NULL);\n\tsigaction(SIGCHLD, &act, NULL);\n\n\tsetup_display();\n\tlauncher_setup();\n\tevent_loop();\n\tcleanup();\n\n\treturn 0;\n}\n\nvoid\nread_config(void)\n{\n\tFILE *ini = NULL;\n\tchar *key, *val;\n\taction_t *act;\n\n\tini = open_ini(opt_config_file);\n\n\tif (find_ini_section(ini, \"progman\")) {\n\t\twhile (get_ini_kv(ini, &key, &val)) {\n\t\t\tif (strcmp(key, \"font\") == 0)\n\t\t\t\topt_font = strdup(val);\n\t\t\telse if (strcmp(key, \"iconfont\") == 0)\n\t\t\t\topt_iconfont = strdup(val);\n\t\t\telse if (strcmp(key, \"fgcolor\") == 0)\n\t\t\t\topt_fg = strdup(val);\n\t\t\telse if (strcmp(key, \"bgcolor\") == 0)\n\t\t\t\topt_bg = strdup(val);\n\t\t\telse if (strcmp(key, \"unfocused_fgcolor\") == 0)\n\t\t\t\topt_unfocused_fg = strdup(val);\n\t\t\telse if (strcmp(key, \"unfocused_bgcolor\") == 0)\n\t\t\t\topt_unfocused_bg = strdup(val);\n\t\t\telse if (strcmp(key, \"button_bgcolor\") == 0)\n\t\t\t\topt_button_bg = strdup(val);\n\t\t\telse if (strcmp(key, \"border_fgcolor\") == 0)\n\t\t\t\topt_border_fg = strdup(val);\n\t\t\telse if (strcmp(key, \"border_bgcolor\") == 0)\n\t\t\t\topt_border_bg = strdup(val);\n\t\t\telse if (strcmp(key, \"launcher_fgcolor\") == 0)\n\t\t\t\topt_launcher_fg = strdup(val);\n\t\t\telse if (strcmp(key, \"launcher_bgcolor\") == 0)\n\t\t\t\topt_launcher_bg = strdup(val);\n\t\t\telse if (strcmp(key, \"root_bgcolor\") == 0)\n\t\t\t\topt_root_bg = strdup(val);\n\t\t\telse if (strcmp(key, \"border_width\") == 0) {\n\t\t\t\topt_bw = atoi(val);\n\t\t\t\tif (opt_bw < 0) {\n\t\t\t\t\twarnx(\"invalid value for border_width\");\n\t\t\t\t\topt_bw = DEF_BW;\n\t\t\t\t}\n\t\t\t} else if (strcmp(key, \"title_padding\") == 0) {\n\t\t\t\topt_pad = atoi(val);\n\t\t\t\tif (opt_pad < 0) {\n\t\t\t\t\twarnx(\"invalid value for \"\n\t\t\t\t\t    \"title_padding\");\n\t\t\t\t\topt_pad = DEF_PAD;\n\t\t\t\t}\n\t\t\t} else if (strcmp(key, \"edgeresist\") == 0) {\n\t\t\t\topt_edge_resist = atoi(val);\n\t\t\t\tif (opt_edge_resist < 0) {\n\t\t\t\t\twarnx(\"invalid value for edgeresist\");\n\t\t\t\t\topt_edge_resist = DEF_EDGE_RES;\n\t\t\t\t}\n\t\t\t} else if (strcmp(key, \"scale\") == 0) {\n\t\t\t\topt_scale = atoi(val);\n\t\t\t\tif (opt_scale < 0) {\n\t\t\t\t\twarnx(\"invalid value for scale\");\n\t\t\t\t\topt_scale = DEF_SCALE;\n\t\t\t\t}\n\t\t\t} else if (strcmp(key, \"drag_combo\") == 0) {\n\t\t\t\tact = bind_key(BINDING_TYPE_DRAG, val, \"drag\");\n\t\t\t\tif (act == NULL)\n\t\t\t\t\twarnx(\"invalid drag_combo \\\"%s\\\" in \"\n\t\t\t\t\t    \"ini\", val);\n\t\t\t\telse {\n\t\t\t\t\topt_drag_button = act->button;\n\t\t\t\t\topt_drag_mod = act->mod;\n\t\t\t\t}\n\t\t\t} else\n\t\t\t\twarnx(\"unknown key \\\"%s\\\" and value \\\"%s\\\" in \"\n\t\t\t\t    \"ini\", key, val);\n\n\t\t\tfree(key);\n\t\t\tfree(val);\n\t\t}\n\t}\n\n\tif (find_ini_section(ini, \"keyboard\"))\n\t\twhile (get_ini_kv(ini, &key, &val))\n\t\t\tbind_key(BINDING_TYPE_KEYBOARD, key, val);\n\n\tif (find_ini_section(ini, \"desktop\"))\n\t\twhile (get_ini_kv(ini, &key, &val))\n\t\t\tbind_key(BINDING_TYPE_DESKTOP, key, val);\n\n\tfclose(ini);\n}\n\nvoid\nsetup_display(void)\n{\n\tXGCValues gv;\n\tXColor exact;\n\tXSetWindowAttributes sattr;\n\tXWindowAttributes attr;\n\tXIconSize *xis;\n\tXColor root_bg;\n\tPixmap rootpx;\n\tint shape_err;\n\tWindow qroot, qparent, *wins;\n\tunsigned int nwins, i;\n\tclient_t *c;\n\n\tfocused = NULL;\n\tdragging = NULL;\n\n#ifdef USE_GDK_PIXBUF\n\tgdk_pixbuf_xlib_init(dpy, screen);\n#endif\n\n\tmap_curs = XCreateFontCursor(dpy, XC_dotbox);\n\tmove_curs = XCreateFontCursor(dpy, XC_fleur);\n\tresize_n_curs = XCreateFontCursor(dpy, XC_top_side);\n\tresize_s_curs = XCreateFontCursor(dpy, XC_bottom_side);\n\tresize_e_curs = XCreateFontCursor(dpy, XC_right_side);\n\tresize_w_curs = XCreateFontCursor(dpy, XC_left_side);\n\tresize_nw_curs = XCreateFontCursor(dpy, XC_top_left_corner);\n\tresize_sw_curs = XCreateFontCursor(dpy, XC_bottom_left_corner);\n\tresize_ne_curs = XCreateFontCursor(dpy, XC_top_right_corner);\n\tresize_se_curs = XCreateFontCursor(dpy, XC_bottom_right_corner);\n\n#define alloc_color(val, var, name) \\\n\tif (!XAllocNamedColor(dpy, def_cmap, val, var, &exact)) \\\n\t\twarnx(\"invalid %s value \\\"%s\\\"\", name, val);\n\n\tdef_cmap = DefaultColormap(dpy, screen);\n\talloc_color(opt_fg, &fg, \"fgcolor\");\n\talloc_color(opt_bg, &bg, \"opt_fg\");\n\talloc_color(opt_unfocused_fg, &unfocused_fg, \"unfocused_fgcolor\");\n\talloc_color(opt_unfocused_bg, &unfocused_bg, \"unfocused_bgcolor\");\n\talloc_color(opt_button_bg, &button_bg, \"button_bgcolor\");\n\talloc_color(opt_bevel_dark, &bevel_dark, \"bevel_darkcolor\");\n\talloc_color(opt_bevel_light, &bevel_light, \"bevel_lightcolor\");\n\talloc_color(opt_border_fg, &border_fg, \"border_fgcolor\");\n\talloc_color(opt_border_bg, &border_bg, \"border_bgcolor\");\n\talloc_color(opt_launcher_fg, &launcher_fg, \"launcher_fgcolor\");\n\talloc_color(opt_launcher_bg, &launcher_bg, \"launcher_bgcolor\");\n\n\tXSetLineAttributes(dpy, DefaultGC(dpy, screen), 1, LineSolid, CapButt,\n\t    JoinBevel);\n\tXSetFillStyle(dpy, DefaultGC(dpy, screen), FillSolid);\n\n#define create_xft_color(_xft, _pixel) \\\n\t(_xft).color.red = (_pixel).red; \\\n\t(_xft).color.green = (_pixel).green; \\\n\t(_xft).color.blue = (_pixel).blue; \\\n\t(_xft).color.alpha = 0xffff; \\\n\t(_xft).pixel = (_pixel).pixel;\n\n\tcreate_xft_color(xft_fg, fg);\n\tcreate_xft_color(xft_fg_unfocused, unfocused_fg);\n\tcreate_xft_color(xft_launcher, launcher_fg);\n\tcreate_xft_color(xft_launcher_highlighted, launcher_bg);\n\n\tfont = XftFontOpenName(dpy, screen, opt_font);\n\tif (!font)\n\t\terrx(1, \"Xft font \\\"%s\\\" not found\", opt_font);\n\n\ticonfont = XftFontOpenName(dpy, screen, opt_iconfont);\n\tif (!iconfont)\n\t\terrx(1, \"icon Xft font \\\"%s\\\" not found\", opt_iconfont);\n\n\tpixmap_gc = XCreateGC(dpy, root, 0, &gv);\n\n\tgv.function = GXinvert;\n\tgv.subwindow_mode = IncludeInferiors;\n\tinvert_gc = XCreateGC(dpy, root,\n\t    GCFunction | GCSubwindowMode | GCLineWidth, &gv);\n\n\tscale_icon(close_xpm, hidpi_close_xpm, &close_pm, &close_pm_mask,\n\t    &close_pm_attrs);\n\tscale_icon(utility_close_xpm, hidpi_utility_close_xpm,\n\t    &utility_close_pm, &utility_close_pm_mask, &utility_close_pm_attrs);\n\tscale_icon(iconify_xpm, hidpi_iconify_xpm, &iconify_pm,\n\t    &iconify_pm_mask, &iconify_pm_attrs);\n\tscale_icon(zoom_xpm, hidpi_zoom_xpm, &zoom_pm, &zoom_pm_mask,\n\t    &zoom_pm_attrs);\n\tscale_icon(unzoom_xpm, hidpi_unzoom_xpm, &unzoom_pm, &unzoom_pm_mask,\n\t    &unzoom_pm_attrs);\n\tscale_icon(default_icon_xpm, hidpi_default_icon_xpm, &default_icon_pm,\n\t    &default_icon_pm_mask, &default_icon_pm_attrs);\n\n\ticon_size = ICON_SIZE_MULT * opt_scale;\n\txis = XAllocIconSize();\n\txis->min_width = icon_size;\n\txis->min_height = icon_size;\n\txis->max_width = icon_size;\n\txis->max_height = icon_size;\n\txis->width_inc = 1;\n\txis->height_inc = 1;\n\tXSetIconSizes(dpy, root, xis, 1);\n\tXFree(xis);\n\n\tfind_supported_atoms();\n\n\tif (opt_root_bg != NULL && strlen(opt_root_bg) &&\n\t    XAllocNamedColor(dpy, def_cmap, opt_root_bg, &root_bg, &exact)) {\n\t\trootpx = XCreatePixmap(dpy, root, 1, 1,\n\t\t    DefaultDepth(dpy, screen));\n\t\tXSetForeground(dpy, pixmap_gc, root_bg.pixel);\n\t\tXFillRectangle(dpy, rootpx, pixmap_gc, 0, 0, 1, 1);\n\t\tXSetWindowBackgroundPixmap(dpy, root, rootpx);\n\t\tXClearWindow(dpy, root);\n\t\tset_atoms(root, xrootpmap_id, XA_PIXMAP, &rootpx, 1);\n\t} else if (opt_root_bg)\n\t\twarnx(\"invalid root_bgcolor value \\\"%s\\\"\", opt_root_bg);\n\n\tset_atoms(root, net_num_desks, XA_CARDINAL, &ndesks, 1);\n\tget_atoms(root, net_cur_desk, XA_CARDINAL, 0, &cur_desk, 1, NULL);\n\tif (cur_desk >= ndesks) {\n\t\tcur_desk = ndesks - 1;\n\t\tset_atoms(root, net_cur_desk, XA_CARDINAL, &cur_desk, 1);\n\t}\n\n\tshape_support = XShapeQueryExtension(dpy, &shape_event, &shape_err);\n\n\tXQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);\n\tfor (i = 0; i < nwins; i++) {\n\t\tignore_xerrors++;\n\t\tXGetWindowAttributes(dpy, wins[i], &attr);\n\t\tignore_xerrors--;\n\t\tif (!attr.override_redirect && attr.map_state == IsViewable) {\n\t\t\tc = new_client(wins[i]);\n\t\t\tc->placed = 1;\n\t\t\tmap_client(c);\n\t\t\tmap_if_desk(c);\n\t\t}\n\t}\n\tXFree(wins);\n\n\t/* become \"the\" window manager with SubstructureRedirectMask on root */\n\tsattr.event_mask = SubMask | ColormapChangeMask | ButtonMask;\n\tXChangeWindowAttributes(dpy, root, CWEventMask, &sattr);\n\n\t/* create a hidden window for _NET_SUPPORTING_WM_CHECK */\n\tsupporting_wm_win = XCreateWindow(dpy, root, 0, 0, 1, 1,\n\t    0, DefaultDepth(dpy, screen), CopyFromParent,\n\t    DefaultVisual(dpy, screen), 0, NULL);\n\tset_string_atom(supporting_wm_win, net_wm_name,\n\t    (unsigned char *)\"progman\", 7);\n\tset_atoms(root, net_supporting_wm, XA_WINDOW, &supporting_wm_win, 1);\n}\n\nvoid\nscale_icon(void *xpm, void *hidpi_xpm, Pixmap *pm, Pixmap *pm_mask,\n    XpmAttributes *xpm_attrs)\n{\n\tGC scale_gc, mask_scale_gc;\n\tPixmap pm_scaled, pm_scaled_mask;\n\tint x, y, i, j;\n\n\tif (opt_scale == 2) {\n\t\t/* regular-sized icons are too big to 2x, so use hidpi ones */\n\t\tif (XpmCreatePixmapFromData(dpy, root, hidpi_xpm, pm, pm_mask,\n\t\t    xpm_attrs) != XpmSuccess)\n\t\t\terr(1, \"XpmCreatePixmapFromData\");\n\n\t\treturn;\n\t}\n\n\tif (XpmCreatePixmapFromData(dpy, root, xpm, pm, pm_mask,\n\t    xpm_attrs) != XpmSuccess)\n\t\terr(1, \"XpmCreatePixmapFromData\");\n\n\tif (opt_scale == 1)\n\t\treturn;\n\n\tscale_gc = XCreateGC(dpy, *pm, 0, 0);\n\tmask_scale_gc = XCreateGC(dpy, *pm_mask, 0, 0);\n\n\tpm_scaled = XCreatePixmap(dpy, *pm,\n\t    xpm_attrs->width * opt_scale, xpm_attrs->height * opt_scale,\n\t    DefaultDepth(dpy, screen));\n\tpm_scaled_mask = XCreatePixmap(dpy, *pm_mask,\n\t    xpm_attrs->width * opt_scale, xpm_attrs->height * opt_scale,\n\t    1);\n\n\tfor (y = 0; y < xpm_attrs->height; y++) {\n\t\tfor (x = 0; x < xpm_attrs->width; x++) {\n\t\t\tfor (i = 0; i < opt_scale; i++) {\n\t\t\t\tfor (j = 0; j < opt_scale; j++) {\n\t\t\t\t\tXCopyArea(dpy, *pm, pm_scaled, scale_gc,\n\t\t\t\t\t    x, y, 1, 1,\n\t\t\t\t\t    (x * opt_scale) + i,\n\t\t\t\t\t    (y * opt_scale) + j);\n\t\t\t\t\tXCopyArea(dpy, *pm_mask, pm_scaled_mask,\n\t\t\t\t\t    mask_scale_gc,\n\t\t\t\t\t    x, y, 1, 1,\n\t\t\t\t\t    (x * opt_scale) + i,\n\t\t\t\t\t    (y * opt_scale) + j);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tXFreeGC(dpy, scale_gc);\n\tXFreeGC(dpy, mask_scale_gc);\n\n\txpm_attrs->width *= opt_scale;\n\txpm_attrs->height *= opt_scale;\n\n\tXFreePixmap(dpy, *pm);\n\tXFreePixmap(dpy, *pm_mask);\n\t*pm = pm_scaled;\n\t*pm_mask = pm_scaled_mask;\n}\n\nvoid\nsig_handler(int signum)\n{\n\tpid_t pid;\n\tint status;\n\n\tswitch (signum) {\n\tcase SIGINT:\n\tcase SIGTERM:\n\tcase SIGHUP:\n\t\tquit();\n\t\tbreak;\n\tcase SIGCHLD:\n\t\twhile ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0 ||\n\t\t    (pid < 0 && errno == EINTR))\n\t\t\t;\n\t\tbreak;\n\t}\n}\n\nvoid\nquit(void)\n{\n\tif (write(exitmsg[1], &exitmsg, 1))\n\t\treturn;\n\n\twarn(\"failed to exit cleanly\");\n\texit(0);\n}\n\nint\nhandle_xerror(Display *dpy, XErrorEvent *e)\n{\n\tchar msg[255];\n\n\tif (e->error_code == BadAccess && e->resourceid == root)\n\t\terrx(1, \"root window unavailable\");\n\n\tif (!ignore_xerrors) {\n\t\tXGetErrorText(dpy, e->error_code, msg, sizeof(msg));\n\t\twarnx(\"X error (%#lx): %s\", e->resourceid, msg);\n\t\tfflush(stdout);\n\t\tfflush(stderr);\n\t}\n\n\treturn 0;\n}\n\n/*\n * We use XQueryTree here to preserve the window stacking order, since the\n * order in our linked list is different.\n */\nvoid\ncleanup(void)\n{\n\tunsigned int nwins, i;\n\tXSetWindowAttributes sattr;\n\tWindow qroot, qparent, *wins;\n\tclient_t *c;\n\n\tXQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);\n\tfor (i = 0; i < nwins; i++) {\n\t\tc = find_client(wins[i], MATCH_FRAME);\n\t\tif (c)\n\t\t\tdel_client(c, DEL_REMAP);\n\t}\n\tXFree(wins);\n\n\tXftFontClose(dpy, font);\n\tXftFontClose(dpy, iconfont);\n\tXFreeCursor(dpy, map_curs);\n\tXFreeCursor(dpy, move_curs);\n\tXFreeCursor(dpy, resize_n_curs);\n\tXFreeCursor(dpy, resize_s_curs);\n\tXFreeCursor(dpy, resize_e_curs);\n\tXFreeCursor(dpy, resize_w_curs);\n\tXFreeCursor(dpy, resize_nw_curs);\n\tXFreeCursor(dpy, resize_sw_curs);\n\tXFreeCursor(dpy, resize_ne_curs);\n\tXFreeCursor(dpy, resize_se_curs);\n\tXFreeGC(dpy, pixmap_gc);\n\tXFreeGC(dpy, invert_gc);\n\tXFreePixmap(dpy, close_pm);\n\tXFreePixmap(dpy, close_pm_mask);\n\tXFreePixmap(dpy, utility_close_pm);\n\tXFreePixmap(dpy, utility_close_pm_mask);\n\tXFreePixmap(dpy, iconify_pm);\n\tXFreePixmap(dpy, iconify_pm_mask);\n\tXFreePixmap(dpy, zoom_pm);\n\tXFreePixmap(dpy, zoom_pm_mask);\n\tXFreePixmap(dpy, unzoom_pm);\n\tXFreePixmap(dpy, unzoom_pm_mask);\n\tXFreePixmap(dpy, default_icon_pm);\n\tXFreePixmap(dpy, default_icon_pm_mask);\n\n\tXInstallColormap(dpy, DefaultColormap(dpy, screen));\n\tXSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);\n\n\tXDeleteProperty(dpy, root, net_supporting_wm);\n\tXDestroyWindow(dpy, supporting_wm_win);\n\n\tXDeleteProperty(dpy, root, net_supported);\n\tXDeleteProperty(dpy, root, net_client_list);\n\n\tlauncher_programs_free();\n\n\t/* resign as \"the\" window manager */\n\tsattr.event_mask = 0;\n\tXChangeWindowAttributes(dpy, root, CWEventMask, &sattr);\n\n\tXCloseDisplay(dpy);\n}\n"
  },
  {
    "path": "progman.h",
    "content": "/*\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef PROGMAN_H\n#define PROGMAN_H\n\n#include <X11/Xlib.h>\n#include <X11/Xutil.h>\n#include <X11/Xft/Xft.h>\n#include <X11/xpm.h>\n#include \"atom.h\"\n\n/* Default options */\n\n/* Title bars */\n#define DEF_FG \"white\"\n#define DEF_BG \"#0000a8\"\n#define DEF_UNFOCUSED_FG \"black\"\n#define DEF_UNFOCUSED_BG \"white\"\n#define DEF_BUTTON_BG \"#c0c7c8\"\n#define DEF_BEVEL_DARK \"#87888f\"\n#define DEF_BEVEL_LIGHT \"white\"\n\n/* Borders */\n#define DEF_BORDER_FG \"black\"\n#define DEF_BORDER_BG \"#c0c7c8\"\n\n/* Launcher */\n#define DEF_LAUNCHER_FG \"black\"\n#define DEF_LAUNCHER_BG \"#c0c7c8\"\n\n/* Default root color is unchanged */\n#define DEF_ROOTBG NULL\n\n#define DEF_FONT \"Microsoft Sans Serif:bold:size=14\"\n#define DEF_ICONFONT \"Microsoft Sans Serif:size=11\"\n#define DEF_BEVEL 2\n#define DEF_PAD 6\n#define DEF_BW 3\n#define DEF_EDGE_RES 80\n#define DEF_SCALE 2\n#define ICON_SIZE_MULT 32\n\n#define DEF_NDESKS 5\n\n#define DOUBLE_CLICK_MSEC 250\n\n#define BUF_SIZE 2048\n\n/* End of options */\n\n#define SubMask (SubstructureRedirectMask|SubstructureNotifyMask)\n#define ButtonMask (ButtonPressMask|ButtonReleaseMask)\n#define MouseMask (ButtonMask|PointerMotionMask)\n\n#define GRAV(c) ((c->size.flags & PWinGravity) ? c->size.win_gravity : \\\n    NorthWestGravity)\n#define CAN_PLACE_SELF(t) ((t) == net_wm_type_dock || \\\n    (t) == net_wm_type_menu || (t) == net_wm_type_splash || \\\n    (t) == net_wm_type_desk || (t) == net_wm_type_notif)\n#define HAS_DECOR(t) (!CAN_PLACE_SELF(t))\n#define DESK_ALL 0xFFFFFFFF\n#define IS_ON_DESK(w, d) (w == d || w == DESK_ALL)\n#define IS_ON_CUR_DESK(c) \\\n\t(IS_ON_DESK((c)->desk, cur_desk) || (c)->state & STATE_ICONIFIED)\n#define IS_RESIZE_WIN(c, w) (w == c->resize_nw || w == c->resize_w || \\\n\tw == c->resize_sw || w == c->resize_s || w == c->resize_se || \\\n\tw == c->resize_e || w == c->resize_ne || w == c->resize_n)\n\n#ifdef DEBUG\n#define SHOW_EV(name, memb) \\\n    case name: \\\n    \tsnprintf(ev_type, sizeof(ev_type), #name); \\\n\tw = e.memb.window; \\\n\tbreak;\n#define SHOW(name) \\\n    case name: \\\n    \treturn #name;\n#endif\n\ntypedef struct geom geom_t;\nstruct geom {\n\tlong x;\n\tlong y;\n\tlong w;\n\tlong h;\n};\n\n/* types of bindings */\nenum {\n\tBINDING_TYPE_KEYBOARD,\n\tBINDING_TYPE_DESKTOP,\n\tBINDING_TYPE_DRAG,\n};\n\n/* keyboard and mouse bindings */\ntypedef struct action action_t;\nstruct action {\n\tint type;\n\tKeySym key;\n\tunsigned int mod;\n\tunsigned int button;\n\tint action;\n\tint iarg;\n\tchar *sarg;\n};\nextern action_t *key_actions;\nextern int nkey_actions;\n\n/* types of actions in action_t.action */\nenum {\n\tACTION_INVALID = -2,\n\tACTION_NONE = -1,\n\tACTION_CYCLE = 1,\n\tACTION_REVERSE_CYCLE,\n\tACTION_DESK,\n\tACTION_DESK_NEXT,\n\tACTION_DESK_PREVIOUS,\n\tACTION_CLOSE,\n\tACTION_EXEC,\n\tACTION_LAUNCHER,\n\tACTION_RESTART,\n\tACTION_QUIT,\n\tACTION_DRAG,\n};\n\n/* client_t state */\nenum {\n\tSTATE_NORMAL = 0,\n\tSTATE_ZOOMED = (1 << 1),\n\tSTATE_SHADED = (1 << 2),\n\tSTATE_FULLSCREEN = (1 << 3),\n\tSTATE_ICONIFIED = (1 << 4),\n\tSTATE_DOCK = (1 << 5),\n\tSTATE_ABOVE = (1 << 6),\n\tSTATE_BELOW = (1 << 7),\n};\n\n/* client_t frame_style */\nenum {\n\tFRAME_NONE = 0,\n\tFRAME_BORDER = (1 << 1),\n\tFRAME_RESIZABLE = (1 << 2),\n\tFRAME_TITLEBAR = (1 << 3),\n\tFRAME_CLOSE = (1 << 4),\n\tFRAME_ICONIFY = (1 << 5),\n\tFRAME_ZOOM = (1 << 6),\n\tFRAME_ALL = FRAME_BORDER | FRAME_RESIZABLE | FRAME_TITLEBAR |\n\t    FRAME_CLOSE | FRAME_ICONIFY | FRAME_ZOOM,\n};\n\ntypedef struct client client_t;\nstruct client {\n\tclient_t *next;\n\tchar *name;\n\tXftDraw *xftdraw;\n\tWindow win, trans;\n\tgeom_t geom, save;\n\tWindow frame;\n\tgeom_t frame_geom;\n\tunsigned int frame_style;\n\tWindow close;\n\tgeom_t close_geom;\n\tBool close_pressed;\n\tWindow titlebar;\n\tgeom_t titlebar_geom;\n\tWindow iconify;\n\tgeom_t iconify_geom;\n\tBool iconify_pressed;\n\tWindow zoom;\n\tgeom_t zoom_geom;\n\tBool zoom_pressed;\n\tint border_width;\n\tWindow resize_nw;\n\tgeom_t resize_nw_geom;\n\tWindow resize_n;\n\tgeom_t resize_n_geom;\n\tWindow resize_ne;\n\tgeom_t resize_ne_geom;\n\tWindow resize_e;\n\tgeom_t resize_e_geom;\n\tWindow resize_se;\n\tgeom_t resize_se_geom;\n\tWindow resize_s;\n\tgeom_t resize_s_geom;\n\tWindow resize_sw;\n\tgeom_t resize_sw_geom;\n\tWindow resize_w;\n\tgeom_t resize_w_geom;\n\tWindow icon;\n\tgeom_t icon_geom;\n\tWindow icon_label;\n\tgeom_t icon_label_geom;\n\tPixmap icon_pixmap;\n\tPixmap icon_mask;\n\tint icon_managed;\n\tGC icon_gc;\n\tchar *icon_name;\n\tXftDraw *icon_xftdraw;\n\tint icon_depth;\n\tXWMHints *wm_hints;\n\tXSizeHints size_hints;\n\tColormap cmap;\n\tint ignore_unmap;\n\tunsigned long desk;\n\tBool placed;\n\tBool shaped;\n\tint state;\n#define MAX_WIN_TYPE_ATOMS 5\n\tAtom win_type[MAX_WIN_TYPE_ATOMS];\n\tint old_bw;\n};\n\ntypedef struct xft_line xft_line_t;\nstruct xft_line_t {\n\tchar *str;\n\tunsigned int len;\n\tunsigned int xft_width;\n};\n\ntypedef void sweep_func(client_t *, geom_t, int, int, int, int, strut_t *,\n    void *);\n\nenum {\n\tMATCH_WINDOW,\n\tMATCH_FRAME,\n\tMATCH_ANY,\n};\t/* find_client */\nenum {\n\tDEL_WITHDRAW,\n\tDEL_REMAP,\n};\t/* del_client */\n\nenum {\n\tORDER_TOP,\n\tORDER_ICONIFIED_TOP,\n\tORDER_BOTTOM,\n\tORDER_OUT,\n\tORDER_INVERT,\n};\t/* adjust_client_order */\n\nenum {\n\tFOCUS_NORMAL,\n\tFOCUS_FORCE,\n};\t/* focus_client */\n\n/* progman.c */\nextern char *orig_argv0;\nextern Display *dpy;\nextern Window root;\nextern client_t *focused, *dragging;\nextern int screen;\nextern int ignore_xerrors;\nextern unsigned long cur_desk;\nextern unsigned long ndesks;\nextern Bool shape_support;\nextern int shape_event;\nextern Window supporting_wm_win;\nextern int icon_size;\nextern XftFont *font;\nextern XftFont *iconfont;\nextern XftColor xft_fg;\nextern XftColor xft_fg_unfocused;\nextern XftColor xft_launcher;\nextern XftColor xft_launcher_highlighted;\nextern Colormap cmap;\nextern XColor fg;\nextern XColor bg;\nextern XColor unfocused_fg;\nextern XColor unfocused_bg;\nextern XColor button_bg;\nextern XColor bevel_dark;\nextern XColor bevel_light;\nextern XColor border_fg;\nextern XColor border_bg;\nextern XColor launcher_fg;\nextern XColor launcher_bg;\nextern GC pixmap_gc;\nextern GC invert_gc;\nextern Pixmap close_pm;\nextern Pixmap close_pm_mask;\nextern XpmAttributes close_pm_attrs;\nextern Pixmap utility_close_pm;\nextern Pixmap utility_close_pm_mask;\nextern XpmAttributes utility_close_pm_attrs;\nextern Pixmap iconify_pm;\nextern Pixmap iconify_pm_mask;\nextern XpmAttributes iconify_pm_attrs;\nextern Pixmap zoom_pm;\nextern Pixmap zoom_pm_mask;\nextern XpmAttributes zoom_pm_attrs;\nextern Pixmap unzoom_pm;\nextern Pixmap unzoom_pm_mask;\nextern XpmAttributes unzoom_pm_attrs;\nextern Pixmap default_icon_pm;\nextern Pixmap default_icon_pm_mask;\nextern XpmAttributes default_icon_pm_attrs;\nextern Cursor map_curs;\nextern Cursor move_curs;\nextern Cursor resize_n_curs;\nextern Cursor resize_s_curs;\nextern Cursor resize_e_curs;\nextern Cursor resize_w_curs;\nextern Cursor resize_nw_curs;\nextern Cursor resize_sw_curs;\nextern Cursor resize_ne_curs;\nextern Cursor resize_se_curs;\nextern char *opt_config_file;\nextern char *opt_font;\nextern char *opt_iconfont;\nextern char *opt_fg;\nextern char *opt_bg;\nextern char *opt_unfocused_fg;\nextern char *opt_unfocused_bg;\nextern char *opt_button_bg;\nextern char *opt_bevel_dark;\nextern char *opt_bevel_light;\nextern char *opt_border_fg;\nextern char *opt_border_bg;\nextern char *opt_root_bg;\nextern int opt_bevel;\nextern int opt_bw;\nextern int opt_pad;\nextern int opt_edge_resist;\nextern int opt_scale;\nextern int opt_drag_button;\nextern int opt_drag_mod;\nextern void sig_handler(int signum);\nextern int exitmsg[2];\n\n/* progman.c */\nvoid cleanup(void);\nvoid quit(void);\n\n/* event.c */\nextern void event_loop(void);\nextern int handle_xerror(Display *, XErrorEvent *);\nextern void handle_unmap_event(XUnmapEvent *);\n#ifdef DEBUG\nextern void show_event(XEvent);\n#endif\n\n/* client.c */\nextern client_t *new_client(Window);\nextern client_t *find_client(Window, int);\nextern client_t *find_client_at_coords(Window, int, int);\nextern client_t *top_client(void);\nextern client_t *prev_focused(int);\nextern void map_client(client_t *);\nextern void update_size_hints(client_t *);\nextern int has_win_type(client_t *, Atom);\nextern void recalc_frame(client_t *);\nextern int set_wm_state(client_t *, unsigned long);\nextern void check_states(client_t *);\nextern void parse_state_atom(client_t *, Atom);\nextern void send_config(client_t *);\nextern void redraw_frame(client_t *, Window);\nextern void collect_struts(client_t *, strut_t *);\nextern void get_client_icon(client_t *);\nextern void redraw_icon(client_t *, Window);\nextern void set_shape(client_t *);\nextern void del_client(client_t *, int);\n\n/* manage.c */\nextern void user_action(client_t *, Window, int, int, int, int);\nextern int pos_in_frame(client_t *, int, int);\nextern Cursor cursor_for_resize_win(client_t *, Window);\nextern void focus_client(client_t *, int);\nextern void move_client(client_t *);\nextern void resize_client(client_t *, Window);\nextern void iconify_client(client_t *);\nextern void uniconify_client(client_t *);\nextern void place_icon(client_t *);\nextern void shade_client(client_t *);\nextern void unshade_client(client_t *);\nextern void fullscreen_client(client_t *);\nextern void unfullscreen_client(client_t *);\nextern void zoom_client(client_t *);\nextern void unzoom_client(client_t *);\nextern void send_wm_delete(client_t *);\nextern void goto_desk(int);\nextern void map_if_desk(client_t *);\nextern void sweep(client_t *, Cursor, sweep_func, void *, strut_t *);\nextern void recalc_map(client_t *, geom_t, int, int, int, int, strut_t *,\n    void *);\nextern void recalc_move(client_t *, geom_t, int, int, int, int, strut_t *,\n    void *);\nextern void recalc_resize(client_t *, geom_t, int, int, int, int, strut_t *,\n    void *);\nextern void fix_size(client_t *);\nextern void constrain_frame(client_t *);\nextern char *state_name(client_t *);\nextern void flush_expose_client(client_t *);\nextern void flush_expose(Window);\nextern int overlapping_geom(geom_t, geom_t);\nextern void restack_clients(void);\nextern void adjust_client_order(client_t *, int);\nextern client_t *next_client_for_focus(client_t *);\n#ifdef DEBUG\nextern void dump_name(client_t *, const char *, const char *, const char *);\nextern void dump_info(client_t *);\nextern void dump_geom(client_t *, geom_t, const char *);\nextern void dump_removal(client_t *, int);\nextern void dump_clients(void);\nextern const char *frame_name(client_t *, Window);\n#endif\n\n/* keyboard.c */\nextern void bind_keys(void);\nextern void handle_key_event(XKeyEvent *);\n\n/* launcher.c */\nextern Window launcher_win;\nextern void launcher_setup(void);\nextern void launcher_show(XButtonEvent *);\nextern void launcher_programs_free(void);\nextern client_t *cycle_head;\n\n/* util.c */\nextern void fork_exec(char *);\nextern int get_pointer(int *, int *);\nextern int send_xmessage(Window, Window, Atom, unsigned long, unsigned long);\nextern action_t *bind_key(int, char *, char *);\nextern void take_action(action_t *);\nextern action_t *parse_action(char *, char *);\n\n#endif\t/* PROGMAN_H */\n"
  },
  {
    "path": "progman.ini",
    "content": "#\n# This is the configuration file for progman, and is optional.  It should exist\n# at ~/.config/progman/progman.ini\n#\n# Lines starting with '#' are ignored as comments.\n#\n\n[progman]\nfont = Microsoft Sans Serif:bold:size=14\niconfont = Microsoft Sans Serif:size=11\n\n# Focused windows\nfgcolor = white\nbgcolor = #0000a8\n\n# Unfocused windows\nunfocused_fgcolor = black\nunfocused_bgcolor = white\n\n# Borders\nborder_fgcolor = black\nborder_bgcolor = #c0c7c8\nborder_width = 6\nbutton_bgcolor = #c0c7c8\ntitle_padding = 6\n\n# For HiDPI displays, how many times to scale icons and buttons\nscale = 2\n\n# Launcher\nlauncher_fgcolor = black\nlauncher_bgcolor = #c0c7c8\n\n# When not specified, the root color is not changed\n#root_bgcolor = #c0c7c8\n\n# Move windows by holding down this key and mouse button\ndrag_combo = Alt+Mouse1\n\n# When moving windows, how hard to resist going off-screen\nedgeresist = 80\n\n# Custom key bindings can be specified as \"Modifier+Key = action\".\n[keyboard]\nAlt+Tab = cycle\nShift+Alt+Tab = reverse_cycle\nAlt+F4 = close\nAlt+1 = desk 0\nAlt+2 = desk 1\nAlt+3 = desk 2\nAlt+4 = desk 3\nAlt+5 = desk 4\nAlt+6 = desk 5\nAlt+7 = desk 6\nAlt+8 = desk 7\nAlt+9 = desk 8\nAlt+0 = desk 9\nWin+T = exec xterm\n\n# Mouse clicks on desktop: right click reveals launcher, middle click launches\n# terminal, wheel navigates desktops\n[desktop]\nMouse2 = exec xterm\nMouse3 = launcher\nMouse4 = desk next\nMouse5 = desk previous\n\n# When the launcher action is performed from a key binding or desktop click,\n# this list will be shown; actions are the same as keyboard bindings\n[launcher]\nXterm = exec xterm\nFirefox = exec firefox\nXCalc = exec xcalc\nXEyes = exec xeyes\nXClock = exec xclock\nLock = exec pkill -USR1 xidle\nRestart = restart\nQuit = quit\n"
  },
  {
    "path": "tests/Makefile",
    "content": "#\n# Copyright 2020 joshua stein <jcs@jcs.org>\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n# OTHER DEALINGS IN THE SOFTWARE.\n#\n\nPKGLIBS=\tx11 xft\n\nCC?=\t\tcc\nCFLAGS+=\t-O2 -Wall -Wunused \\\n\t\t-Wunused -Wmissing-prototypes -Wstrict-prototypes \\\n\t\t-Wpointer-sign \\\n\t\t`pkg-config --cflags ${PKGLIBS}`\nLDFLAGS+=\t`pkg-config --libs ${PKGLIBS}`\n\nBIN=\t\tgeometry \\\n\t\tno-resize \\\n\t\twin-type-utility\n\nall: $(BIN)\n\natom.o: ../atom.c\nharness.o: harness.c\n\n$(BIN): atom.o harness.o $@.c\n\t$(CC) $(CFLAGS) -o $@ $@.c atom.o harness.o $(LDFLAGS)\n\nclean:\n\trm -f $(BIN) *.o\n\n.PHONY: all install clean\n"
  },
  {
    "path": "tests/geometry.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include \"harness.h\"\n\nvoid\nsetup(int argc, char **argv)\n{\n\tXSizeHints *hints;\n\n\thints = XAllocSizeHints();\n\tif (!hints)\n\t\terr(1, \"XAllocSizeHints\");\n\n\thints->flags = PPosition | PSize;\n\thints->x = 10;\n\thints->y = 10;\n\thints->width = 200;\n\thints->height = 200;\n\n\tXSetWMNormalHints(dpy, win, hints);\n}\n\nvoid\nprocess_event(XEvent *ev)\n{\n}\n"
  },
  {
    "path": "tests/harness.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include \"harness.h\"\n#include <libgen.h>\n\nDisplay *dpy;\nWindow win, root;\nint screen;\nint ignore_xerrors = 0;\n\nint\nmain(int argc, char **argv)\n{\n\tXEvent ev;\n\tXTextProperty name;\n\tchar *title = basename(argv[0]);\n\tint screen;\n\n\tdpy = XOpenDisplay(NULL);\n\tif (!dpy)\n\t\terr(1, \"can't open $DISPLAY\");\n\n\tscreen = DefaultScreen(dpy);\n\troot = RootWindow(dpy, screen);\n\n\tfind_supported_atoms();\n\n\twin = XCreateWindow(dpy, root, 0, 0, 300, 200, 0,\n\t    DefaultDepth(dpy, screen), CopyFromParent,\n\t    DefaultVisual(dpy, screen), 0, NULL);\n\tif (!win)\n\t\terr(1, \"XCreateWindow\");\n\n\tif (!XStringListToTextProperty(&title, 1, &name))\n\t\terr(1, \"!XStringListToTextProperty\");\n\tXSetWMName(dpy, win, &name);\n\n\tXSetWindowBackground(dpy, win, WhitePixel(dpy, screen));\n\tXSelectInput(dpy, win, KeyPressMask);\n\n\tsetup(argc, argv);\n\n\tXMapWindow(dpy, win);\n\n\tfor (;;) {\n\t\tXNextEvent(dpy, &ev);\n\n\t\tswitch (ev.type) {\n\t\tcase KeyPress: {\n\t\t\tKeySym kc = XLookupKeysym(&ev.xkey, 0);\n\t\t\tif (kc == XK_Escape)\n\t\t\t\texit(0);\n\t\t\tbreak;\n\t\t}\n\t\t}\n\n\t\tprocess_event(&ev);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "tests/harness.h",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <err.h>\n#include <string.h>\n#include <X11/Xlib.h>\n#include <X11/Xutil.h>\n\n#include \"../atom.h\"\n\nextern Display *dpy;\nextern Window win;\nextern int screen;\n\nextern void setup(int, char **);\nextern void process_event(XEvent *ev);\n"
  },
  {
    "path": "tests/no-resize.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include \"harness.h\"\n\nvoid\nsetup(int argc, char **argv)\n{\n\tXSizeHints *hints;\n\n\thints = XAllocSizeHints();\n\tif (!hints)\n\t\terr(1, \"XAllocSizeHints\");\n\n\thints->flags = PMinSize | PMaxSize;\n\thints->min_width = 300;\n\thints->min_height = 200;\n\thints->max_width = 300;\n\thints->max_height = 200;\n\n\tXSetWMNormalHints(dpy, win, hints);\n}\n\nvoid\nprocess_event(XEvent *ev)\n{\n}\n"
  },
  {
    "path": "tests/win-type-utility.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include \"harness.h\"\n\nvoid\nsetup(int argc, char **argv)\n{\n\tset_atoms(win, net_wm_state, XA_ATOM, &net_wm_state_above, 1);\n\tset_atoms(win, net_wm_wintype, XA_ATOM, &net_wm_type_utility, 1);\n}\n\nvoid\nprocess_event(XEvent *ev)\n{\n}\n"
  },
  {
    "path": "themes/hotdogstand.ini",
    "content": "# Hotdog Stand\n[progman]\nfgcolor = white\nbgcolor = black\nunfocused_fgcolor = white\nunfocused_bgcolor = red\nborder_bgcolor = red\nborder_fgcolor = black\nroot_bgcolor = yellow\n"
  },
  {
    "path": "util.c",
    "content": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\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\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell 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\n * all 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 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <err.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <X11/Xlib.h>\n#include <X11/Xutil.h>\n#include \"progman.h\"\n\nvoid\nfork_exec(char *cmd)\n{\n\tpid_t pid = fork();\n\n\tswitch (pid) {\n\tcase 0:\n\t\tsetsid();\n\t\texecl(\"/bin/sh\", \"sh\", \"-c\", cmd, NULL);\n\t\tfprintf(stderr, \"exec failed, cleaning up child\\n\");\n\t\texit(1);\n\tcase -1:\n\t\tfprintf(stderr, \"can't fork\\n\");\n\t}\n}\n\nint\nget_pointer(int *x, int *y)\n{\n\tWindow real_root, real_win;\n\tint wx, wy;\n\tunsigned int mask;\n\n\tXQueryPointer(dpy, root, &real_root, &real_win, x, y, &wx, &wy, &mask);\n\treturn mask;\n}\n\nint\nsend_xmessage(Window t, Window w, Atom a, unsigned long x, unsigned long mask)\n{\n\tXClientMessageEvent e;\n\n\te.type = ClientMessage;\n\te.window = w;\n\te.message_type = a;\n\te.format = 32;\n\te.data.l[0] = x;\n\te.data.l[1] = CurrentTime;\n\n\treturn XSendEvent(dpy, t, False, mask, (XEvent *)&e);\n}\n\naction_t *\nparse_action(char *prefix, char *action)\n{\n\tchar *taction = NULL, *targ = NULL;\n\tchar *sep;\n\tchar *sarg = NULL;\n\taction_t *out = NULL;\n\tint iaction = ACTION_NONE;\n\tint iarg = 0;\n\n\ttaction = strdup(action);\n\tif ((sep = strchr(taction, ' '))) {\n\t\t*sep = '\\0';\n\t\ttarg = sep + 1;\n\t} else\n\t\ttarg = NULL;\n\n\tif (strcmp(taction, \"cycle\") == 0)\n\t\tiaction = ACTION_CYCLE;\n\telse if (strcmp(taction, \"reverse_cycle\") == 0)\n\t\tiaction = ACTION_REVERSE_CYCLE;\n\telse if (strcmp(taction, \"desk\") == 0)\n\t\tiaction = ACTION_DESK;\n\telse if (strcmp(taction, \"close\") == 0)\n\t\tiaction = ACTION_CLOSE;\n\telse if (strcmp(taction, \"exec\") == 0)\n\t\tiaction = ACTION_EXEC;\n\telse if (strcmp(taction, \"launcher\") == 0)\n\t\tiaction = ACTION_LAUNCHER;\n\telse if (strcmp(taction, \"restart\") == 0)\n\t\tiaction = ACTION_RESTART;\n\telse if (strcmp(taction, \"quit\") == 0)\n\t\tiaction = ACTION_QUIT;\n\telse if (strcmp(taction, \"drag\") == 0)\n\t\tiaction = ACTION_DRAG;\n\telse if (taction[0] == '\\n' || taction[0] == '\\0')\n\t\tiaction = ACTION_NONE;\n\telse\n\t\tiaction = ACTION_INVALID;\n\n\t/* parse numeric or string args */\n\tswitch (iaction) {\n\tcase ACTION_DESK:\n\t\tif (targ == NULL) {\n\t\t\twarnx(\"%s: missing argument for \\\"%s\\\"\",\n\t\t\t    prefix, taction);\n\t\t\tgoto done;\n\t\t}\n\n\t\tif (strcmp(targ, \"next\") == 0)\n\t\t\tiaction = ACTION_DESK_NEXT;\n\t\telse if (strcmp(targ, \"previous\") == 0)\n\t\t\tiaction = ACTION_DESK_PREVIOUS;\n\t\telse {\n\t\t\terrno = 0;\n\t\t\tiarg = strtol(targ, NULL, 10);\n\t\t\tif (errno != 0) {\n\t\t\t\twarnx(\"%s: failed parsing numeric argument \"\n\t\t\t\t    \"\\\"%s\\\" for \\\"%s\\\"\", prefix, targ, taction);\n\t\t\t\tgoto done;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase ACTION_EXEC:\n\t\tif (targ == NULL) {\n\t\t\twarnx(\"%s: missing string argument for \\\"%s\\\"\", prefix,\n\t\t\t    taction);\n\t\t\tgoto done;\n\t\t}\n\t\tsarg = strdup(targ);\n\t\tbreak;\n\tcase ACTION_INVALID:\n\t\twarnx(\"%s: invalid action \\\"%s\\\"\", prefix, taction);\n\t\tgoto done;\n\tdefault:\n\t\t/* no args expected of other commands */\n\t\tif (targ != NULL) {\n\t\t\twarnx(\"%s: unexpected argument \\\"%s\\\" for \\\"%s\\\"\",\n\t\t\t    prefix, taction, targ);\n\t\t\tgoto done;\n\t\t}\n\t}\n\n\tout = malloc(sizeof(action_t));\n\tout->action = iaction;\n\tout->iarg = iarg;\n\tout->sarg = sarg;\n\ndone:\n\tif (taction)\n\t\tfree(taction);\n\n\treturn out;\n}\n\nvoid\ntake_action(action_t *action)\n{\n\tclient_t *p, *next;\n\n\tswitch (action->action) {\n\tcase ACTION_CYCLE:\n\tcase ACTION_REVERSE_CYCLE:\n\t\tif (!cycle_head) {\n\t\t\tif (!focused)\n\t\t\t\treturn;\n\n\t\t\tcycle_head = focused;\n\t\t}\n\n\t\tif ((next = next_client_for_focus(cycle_head)))\n\t\t\tfocus_client(next, FOCUS_FORCE);\n\t\telse {\n\t\t\t/* probably at the end of the list, invert it */\n\t\t\tp = focused;\n\t\t\tadjust_client_order(NULL, ORDER_INVERT);\n\n\t\t\tif (p)\n\t\t\t\t/* p should now not be focused */\n\t\t\t\tredraw_frame(p, None);\n\n\t\t\tfocus_client(cycle_head, FOCUS_FORCE);\n\t\t}\n\t\tbreak;\n\tcase ACTION_DESK:\n\t\tgoto_desk(action->iarg);\n\t\tbreak;\n\tcase ACTION_DESK_NEXT:\n\t\tif (cur_desk < ndesks - 1)\n\t\t\tgoto_desk(cur_desk + 1);\n\t\tbreak;\n\tcase ACTION_DESK_PREVIOUS:\n\t\tif (cur_desk > 0)\n\t\t\tgoto_desk(cur_desk - 1);\n\t\tbreak;\n\tcase ACTION_CLOSE:\n\t\tif (focused)\n\t\t\tsend_wm_delete(focused);\n\t\tbreak;\n\tcase ACTION_EXEC:\n\t\tfork_exec(action->sarg);\n\t\tbreak;\n\tcase ACTION_LAUNCHER:\n\t\tlauncher_show(NULL);\n\t\tbreak;\n\tcase ACTION_RESTART:\n\t\tcleanup();\n\t\texeclp(orig_argv0, orig_argv0, NULL);\n\t\tbreak;\n\tcase ACTION_QUIT:\n\t\tquit();\n\t\tbreak;\n\tdefault:\n\t\twarnx(\"unhandled action %d\\n\", action->action);\n\t}\n}\n"
  }
]