Full Code of jcs/progman for AI

master dba2dc691b97 cached
36 files
190.1 KB
59.0k tokens
128 symbols
1 requests
Download .txt
Showing preview only (200K chars total). Download the full file or copy to clipboard to get everything.
Repository: jcs/progman
Branch: master
Commit: dba2dc691b97
Files: 36
Total size: 190.1 KB

Directory structure:
gitextract_0g4nf18d/

├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── atom.c
├── atom.h
├── client.c
├── events.c
├── icons/
│   ├── close.xpm
│   ├── default_icon.xpm
│   ├── hidpi-close.xpm
│   ├── hidpi-default_icon.xpm
│   ├── hidpi-iconify.xpm
│   ├── hidpi-unzoom.xpm
│   ├── hidpi-utility_close.xpm
│   ├── hidpi-zoom.xpm
│   ├── iconify.xpm
│   ├── unzoom.xpm
│   ├── utility_close.xpm
│   └── zoom.xpm
├── keyboard.c
├── launcher.c
├── manage.c
├── parser.c
├── parser.h
├── progman.c
├── progman.h
├── progman.ini
├── tests/
│   ├── Makefile
│   ├── geometry.c
│   ├── harness.c
│   ├── harness.h
│   ├── no-resize.c
│   └── win-type-utility.c
├── themes/
│   └── hotdogstand.ini
└── util.c

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
progman
progman_ini.h
*.o


================================================
FILE: LICENSE
================================================
MIT License

Copyright 2020 joshua stein <jcs@jcs.org>
Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: Makefile
================================================
#
# Copyright 2020 joshua stein <jcs@jcs.org>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#

PREFIX?=	/usr/local
X11BASE?=	/usr/X11R6

PKGLIBS=	x11 xft xext xpm

CC?=		cc
CFLAGS+=	-O2 -Wall -Wunused \
		-Wunused -Wmissing-prototypes -Wstrict-prototypes \
		-Wpointer-sign \
		`pkg-config --cflags ${PKGLIBS}`
LDFLAGS+=	`pkg-config --libs ${PKGLIBS}`

# use gdk-pixbuf to rescale icons; optional
PKGLIBS+=	gdk-pixbuf-xlib-2.0
CFLAGS+=	-DUSE_GDK_PIXBUF

# enable debugging; optional
CFLAGS+=	-g
#CFLAGS+=	-g -DDEBUG=1

BINDIR=		$(PREFIX)/bin

SRC=		atom.c \
		client.c \
		events.c \
		keyboard.c \
		launcher.c \
		manage.c \
		parser.c \
		progman.c \
		util.c

OBJ=		${SRC:.c=.o}

BIN=		progman

all: $(BIN)

$(OBJ):		progman.h Makefile
parser.o:	progman.h progman_ini.h

progman_ini.h: progman.ini
	xxd -i progman.ini > $@ || (rm -f progman_ini.h; exit 1)

progman: $(OBJ)
	$(CC) -o $@ $(OBJ) $(LDFLAGS)

install: all
	mkdir -p $(BINDIR) $(MANDIR)
	install -s $(BIN) $(BINDIR)

clean:
	rm -f $(BIN) $(OBJ) progman_ini.h

.PHONY: all install clean


================================================
FILE: README.md
================================================
## progman

progman is a simple X11 window manager modeled after the Windows 3 era.

![progman screenshot](https://jcs.org/images/progman-20200810.png)

### License

MIT

### Compiling

Run `make` to compile, and `make install` to install to `/usr/local` by
default.

### Features

- Window minimizing, drawing icons and labels on the root/desktop
- Window maximizing by double-clicking on a window titlebar, and full-screen
  support (via `_NET_WM_STATE_FULLSCREEN`)
- Window shading by right-clicking on a window titlebar
- Window moving by holding down `Alt` (configurable) and clicking anywhere on a
  window
- Built-in keyboard binding support by adding items to the `[keyboard]`
  section of `~/.config/progman/progman.ini` such as `Win+L = exec xlock`
- Built-in mouse button binding on the desktop by adding items to the
  `[desktop]` section of `~/.config/progman/progman.ini` such as
  `Mouse3 = exec xterm`, with right-click setup by default to show a
  configurable launcher menu containing programs listed in the `[launcher]`
  section of `progman.ini`
- Virtual desktops with keyboard shortcuts for switching between them bound
  to `Alt+1` through `Alt+0` by default, and using the mouse wheel on the
  desktop to scroll through virtual desktops
- Window cycling with `Alt+Tab` and `Shift+Alt+Tab`
- [Theme support](https://github.com/jcs/progman/tree/master/themes)
- Optional HiDPI scaling support to magnify icons and buttons


================================================
FILE: atom.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <err.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <X11/Xutil.h>
#include "progman.h"

Atom kde_net_wm_window_type_override;
Atom net_active_window;
Atom net_client_list;
Atom net_client_stack;
Atom net_close_window;
Atom net_cur_desk;
Atom net_num_desks;
Atom net_supported;
Atom net_supporting_wm;
Atom net_wm_desk;
Atom net_wm_icon;
Atom net_wm_icon_name;
Atom net_wm_name;
Atom net_wm_state;
Atom net_wm_state_above;
Atom net_wm_state_add;
Atom net_wm_state_below;
Atom net_wm_state_fs;
Atom net_wm_state_mh;
Atom net_wm_state_mv;
Atom net_wm_state_rm;
Atom net_wm_state_shaded;
Atom net_wm_state_skipp;
Atom net_wm_state_skipt;
Atom net_wm_state_toggle;
Atom net_wm_strut;
Atom net_wm_strut_partial;
Atom net_wm_type_desk;
Atom net_wm_type_dock;
Atom net_wm_type_menu;
Atom net_wm_type_notif;
Atom net_wm_type_splash;
Atom net_wm_type_utility;
Atom net_wm_wintype;
Atom utf8_string;
Atom wm_change_state;
Atom wm_delete;
Atom wm_protos;
Atom wm_state;
Atom xrootpmap_id;

static char *get_string_atom(Window, Atom, Atom);
static char *_get_wm_name(Window, int);

void
find_supported_atoms(void)
{
	net_supported = XInternAtom(dpy, "_NET_SUPPORTED", False);
	utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
	wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", False);
	wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	wm_protos = XInternAtom(dpy, "WM_PROTOCOLS", False);
	wm_state = XInternAtom(dpy, "WM_STATE", False);
	net_wm_state_rm = 0;
	net_wm_state_add = 1;
	net_wm_state_toggle = 2;

	xrootpmap_id = XInternAtom(dpy, "_XROOTPMAP_ID", False);

	kde_net_wm_window_type_override = XInternAtom(dpy,
	    "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", False);
	append_atoms(root, net_supported, XA_ATOM,
	    &kde_net_wm_window_type_override, 1);

	net_active_window = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
	append_atoms(root, net_supported, XA_ATOM, &net_active_window, 1);

	net_client_list = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
	append_atoms(root, net_supported, XA_ATOM, &net_client_list, 1);

	net_client_stack = XInternAtom(dpy, "_NET_CLIENT_LIST_STACKING", False);
	append_atoms(root, net_supported, XA_ATOM, &net_client_stack, 1);

	net_close_window = XInternAtom(dpy, "_NET_CLOSE_WINDOW", False);
	append_atoms(root, net_supported, XA_ATOM, &net_close_window, 1);

	net_cur_desk = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False);
	append_atoms(root, net_supported, XA_ATOM, &net_cur_desk, 1);

	net_num_desks = XInternAtom(dpy, "_NET_NUMBER_OF_DESKTOPS", False);
	append_atoms(root, net_supported, XA_ATOM, &net_num_desks, 1);

	net_supporting_wm = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False);
	append_atoms(root, net_supported, XA_ATOM, &net_supporting_wm, 1);

	net_wm_desk = XInternAtom(dpy, "_NET_WM_DESKTOP", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_desk, 1);

	net_wm_icon = XInternAtom(dpy, "_NET_WM_ICON", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_icon, 1);

	net_wm_icon_name = XInternAtom(dpy, "_NET_WM_ICON_NAME", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_icon_name, 1);

	net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_name, 1);

	net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_state, 1);

	net_wm_state_above = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_state_above, 1);

	net_wm_state_below = XInternAtom(dpy, "_NET_WM_STATE_BELOW", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_state_below, 1);

	net_wm_state_fs = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_state_fs, 1);

	net_wm_state_mh = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ",
	    False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_state_mh, 1);

	net_wm_state_mv = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT",
	    False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_state_mv, 1);

	net_wm_state_shaded = XInternAtom(dpy, "_NET_WM_STATE_SHADED", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_state_shaded, 1);

	net_wm_strut = XInternAtom(dpy, "_NET_WM_STRUT", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_strut, 1);

	net_wm_strut_partial = XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_strut_partial, 1);

	net_wm_type_desk = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DESKTOP",
	    False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_type_desk, 1);

	net_wm_type_dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_type_dock, 1);

	net_wm_type_menu = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_MENU", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_type_menu, 1);

	net_wm_type_notif = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION",
	    False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_type_utility, 1);

	net_wm_type_splash = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_SPLASH",
	    False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_type_splash, 1);

	net_wm_type_utility = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_UTILITY",
	    False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_type_utility, 1);

	net_wm_wintype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
	append_atoms(root, net_supported, XA_ATOM, &net_wm_wintype, 1);
}

/*
 * Despite the fact that all these are 32 bits on the wire, libX11 really does
 * stuff an array of longs into *data, so you get 64 bits on 64-bit archs. So
 * we gotta be careful here.
 */

unsigned long
get_atoms(Window w, Atom a, Atom type, unsigned long off, unsigned long *ret,
    unsigned long nitems, unsigned long *left)
{
	Atom real_type;
	int i, real_format = 0;
	unsigned long items_read = 0;
	unsigned long bytes_left = 0;
	unsigned long *p;
	unsigned char *data = NULL;

	ignore_xerrors++;
	XGetWindowProperty(dpy, w, a, off, nitems, False, type, &real_type,
	    &real_format, &items_read, &bytes_left, &data);
	ignore_xerrors--;

	if (real_format == 32 && items_read) {
		p = (unsigned long *)data;
		for (i = 0; i < items_read; i++)
			*ret++ = *p++;
		if (left)
			*left = bytes_left;
	} else {
		items_read = 0;
		if (left)
			*left = 0;
	}

	if (data != NULL)
		XFree(data);

	return items_read;
}

unsigned long
set_atoms(Window w, Atom a, Atom type, unsigned long *val,
    unsigned long nitems)
{
	return (XChangeProperty(dpy, w, a, type, 32, PropModeReplace,
		(unsigned char *) val, nitems) == Success);
}

unsigned long
append_atoms(Window w, Atom a, Atom type, unsigned long *val,
    unsigned long nitems)
{
	return (XChangeProperty(dpy, w, a, type, 32, PropModeAppend,
		(unsigned char *) val, nitems) == Success);
}

void
remove_atom(Window w, Atom a, Atom type, unsigned long remove)
{
	unsigned long tmp, read, left, *new;
	int i, j = 0;

	read = get_atoms(w, a, type, 0, &tmp, 1, &left);
	if (!read)
		return;

	new = malloc((read + left) * sizeof(*new));
	if (new == NULL)
		err(1, "malloc");
	if (read && tmp != remove)
		new[j++] = tmp;

	for (i = 1, read = left = 1; read && left; i += read) {
		read = get_atoms(w, a, type, i, &tmp, 1, &left);
		if (!read)
			break;
		if (tmp != remove)
			new[j++] = tmp;
	}

	if (j)
		XChangeProperty(dpy, w, a, type, 32, PropModeReplace,
		    (unsigned char *)new, j);
	else
		XDeleteProperty(dpy, w, a);

	free(new);
}

/*
 * Get the window-manager name (aka human-readable "title") for a given window.
 * There are two ways a client can set this:
 *
 * 1. _NET_WM_STRING, which has type UTF8_STRING.
 * This is preferred and is always used if available.
 *
 * 2. WM_NAME, which has type COMPOUND_STRING or STRING.
 * This is the old ICCCM way, which we fall back to in the absence of
 * _NET_WM_STRING. In this case, we ask X to convert the value of the property
 * to UTF-8 for us. N.b.: STRING is Latin-1 whatever the locale.
 * COMPOUND_STRING is the most hideous abomination ever created. Thankfully we
 * do not have to worry about any of this.
 *
 * If UTF-8 conversion is not available (XFree86 < 4.0.2, or any older X
 * implementation), only WM_NAME will be checked, and, at least for XFree86 and
 * X.Org, it will only be returned if it has type STRING. This is due to an
 * inherent limitation in their implementation of XFetchName. If you have a
 * different X vendor, YMMV.
 *
 * In all cases, this function asks X to allocate the returned string, so it
 * must be freed with XFree.
 */
char *
_get_wm_name(Window w, int icon)
{
	char *name;
	XTextProperty name_prop;
	XTextProperty name_prop_converted;
	char **name_list;
	int nitems;

	if (icon) {
		if ((name = get_string_atom(w, net_wm_icon_name, utf8_string)))
			return name;
		if (!XGetWMIconName(dpy, w, &name_prop))
			return NULL;
	} else {
		if ((name = get_string_atom(w, net_wm_name, utf8_string)))
			return name;
		if (!XGetWMName(dpy, w, &name_prop))
			return NULL;
	}

	if (Xutf8TextPropertyToTextList(dpy, &name_prop, &name_list,
	    &nitems) == Success && nitems >= 1) {
		/*
		 * Now we've got a freshly allocated XTextList. Since
		 * it might have multiple items that need to be joined,
		 * and we need to return something that can be freed by
		 * XFree, we roll it back up into an XTextProperty.
		 */
		if (Xutf8TextListToTextProperty(dpy, name_list, nitems,
			XUTF8StringStyle, &name_prop_converted) == Success) {
			XFreeStringList(name_list);
			return (char *)name_prop_converted.value;
		}

		/*
		 * Not much we can do here. This should never
		 * happen anyway. Famous last words.
		 */
		XFreeStringList(name_list);
		return NULL;
	}

	return (char *)name_prop.value;
}

char *
get_wm_name(Window w)
{
	return _get_wm_name(w, 0);
}

char *
get_wm_icon_name(Window w)
{
	return _get_wm_name(w, 1);
}

/*
 * I give up on trying to do this the right way. We'll just request as many
 * elements as possible. If that's not the entire string, we're fucked. In
 * reality this should never happen. (That's the second time I get to say "this
 * should never happen" in this file!)
 *
 * Standard gripe about casting nonsense applies.
 */
static char *
get_string_atom(Window w, Atom a, Atom type)
{
	Atom real_type;
	int real_format = 0;
	unsigned long items_read = 0;
	unsigned long bytes_left = 0;
	unsigned char *data;

	XGetWindowProperty(dpy, w, a, 0, LONG_MAX, False, type,
	    &real_type, &real_format, &items_read, &bytes_left, &data);

	/* XXX: should check bytes_left here and bail if nonzero, in case
	 * someone wants to store a >4gb string on the server */

	if (real_format == 8 && items_read >= 1)
		return (char *)data;

	return NULL;
}

void
set_string_atom(Window w, Atom a, unsigned char *str, unsigned long len)
{
	XChangeProperty(dpy, w, a, utf8_string, 8, PropModeReplace, str, len);
}

/*
 * Reads the _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT hint into the args, if it
 * exists. In the case of _NET_WM_STRUT_PARTIAL we cheat and only take the
 * first 4 values, because that's all we care about. This means we can use the
 * same code for both (_NET_WM_STRUT only specifies 4 elements). Each number is
 * the margin in pixels on that side of the display where we don't want to
 * place clients. If there is no hint, we act as if it was all zeros (no
 * margin).
 */
int
get_strut(Window w, strut_t *s)
{
	Atom real_type;
	int real_format = 0;
	unsigned long items_read = 0;
	unsigned long bytes_left = 0;
	unsigned char *data;
	unsigned long *strut_data;

	XGetWindowProperty(dpy, w, net_wm_strut_partial, 0, 12, False,
	    XA_CARDINAL, &real_type, &real_format, &items_read, &bytes_left,
	    &data);

	if (!(real_format == 32 && items_read >= 12))
		XGetWindowProperty(dpy, w, net_wm_strut, 0, 4, False,
		    XA_CARDINAL, &real_type, &real_format, &items_read,
		    &bytes_left, &data);

	if (real_format == 32 && items_read >= 4) {
		strut_data = (unsigned long *) data;
		s->left = strut_data[0];
		s->right = strut_data[1];
		s->top = strut_data[2];
		s->bottom = strut_data[3];
		XFree(data);
		return 1;
	}

	s->left = 0;
	s->right = 0;
	s->top = 0;
	s->bottom = 0;

	return 0;
}

unsigned long
get_wm_state(Window w)
{
	unsigned long state;

	if (get_atoms(w, wm_state, wm_state, 0, &state, 1, NULL))
		return state;

	return WithdrawnState;
}


================================================
FILE: atom.h
================================================
/*
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef PROGMAN_ATOM_H
#define PROGMAN_ATOM_H

#include <X11/Xlib.h>
#include <X11/Xatom.h>

struct strut {
	long left;
	long right;
	long top;
	long bottom;
};

typedef struct strut strut_t;

extern Atom kde_net_wm_window_type_override;
extern Atom net_active_window;
extern Atom net_client_list;
extern Atom net_client_stack;
extern Atom net_close_window;
extern Atom net_cur_desk;
extern Atom net_num_desks;
extern Atom net_supported;
extern Atom net_supporting_wm;
extern Atom net_wm_desk;
extern Atom net_wm_icon;
extern Atom net_wm_icon_name;
extern Atom net_wm_name;
extern Atom net_wm_state;
extern Atom net_wm_state_above;
extern Atom net_wm_state_add;
extern Atom net_wm_state_below;
extern Atom net_wm_state_fs;
extern Atom net_wm_state_mh;
extern Atom net_wm_state_mv;
extern Atom net_wm_state_rm;
extern Atom net_wm_state_shaded;
extern Atom net_wm_state_skipp;
extern Atom net_wm_state_skipt;
extern Atom net_wm_state_toggle;
extern Atom net_wm_strut;
extern Atom net_wm_strut_partial;
extern Atom net_wm_type_desk;
extern Atom net_wm_type_dock;
extern Atom net_wm_type_menu;
extern Atom net_wm_type_notif;
extern Atom net_wm_type_splash;
extern Atom net_wm_type_utility;
extern Atom net_wm_wintype;
extern Atom utf8_string;
extern Atom wm_change_state;
extern Atom wm_delete;
extern Atom wm_protos;
extern Atom wm_state;
extern Atom xrootpmap_id;

extern void find_supported_atoms(void);
extern unsigned long get_atoms(Window, Atom, Atom, unsigned long,
    unsigned long *, unsigned long, unsigned long *);
extern unsigned long set_atoms(Window, Atom, Atom, unsigned long *,
    unsigned long);
extern unsigned long append_atoms(Window, Atom, Atom, unsigned long *,
    unsigned long);
extern void remove_atom(Window, Atom, Atom, unsigned long);
extern char *get_wm_name(Window);
extern char *get_wm_icon_name(Window);
extern void set_string_atom(Window, Atom, unsigned char *, unsigned long);
extern int get_strut(Window, strut_t *);
extern unsigned long get_wm_state(Window);

#endif	/* PROGMAN_ATOM_H */


================================================
FILE: client.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#ifdef DEBUG
#include <stdio.h>
#endif
#include <string.h>
#include <err.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#ifdef USE_GDK_PIXBUF
#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
#endif
#include "progman.h"
#include "atom.h"

static void init_geom(client_t *, strut_t *);
static void reparent(client_t *, strut_t *);
static void bevel(Window, geom_t, int);
static void *word_wrap_xft(char *, char, XftFont *, int, int *);

/*
 * Set up a client structure for the new (not-yet-mapped) window. We have to
 * ignore two unmap events if the client was already mapped but has IconicState
 * set (for instance, when we are the second window manager in a session).
 * That's because there's one for the reparent (which happens on all viewable
 * windows) and then another for the unmapping itself.
 */
client_t *
new_client(Window w)
{
	client_t *c;
	XWindowAttributes attr;

	c = malloc(sizeof *c);
	memset(c, 0, sizeof(*c));

	c->name = get_wm_name(w);
	c->icon_name = get_wm_icon_name(w);
	c->win = w;

	update_size_hints(c);
	XGetTransientForHint(dpy, c->win, &c->trans);

	ignore_xerrors++;
	XGetWindowAttributes(dpy, c->win, &attr);
	ignore_xerrors--;
	c->geom.x = attr.x;
	c->geom.y = attr.y;
	c->geom.w = attr.width;
	c->geom.h = attr.height;
	c->cmap = attr.colormap;
	c->old_bw = attr.border_width;

	if (get_atoms(c->win, net_wm_desk, XA_CARDINAL, 0, &c->desk, 1, NULL)) {
		if (c->desk == -1)
			c->desk = DESK_ALL;	/* FIXME */
		if (c->desk >= ndesks && c->desk != DESK_ALL)
			c->desk = cur_desk;
	} else {
		set_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk, 1);
		c->desk = cur_desk;
	}

	/*
	 * We are not actually keeping the stack one in order. However, every
	 * fancy panel uses it and nothing else, no matter what the spec says.
	 * (I'm not sure why, as rearranging the list every time the stacking
	 * changes would be distracting. GNOME's window list applet doesn't.)
	 */
	append_atoms(root, net_client_list, XA_WINDOW, &c->win, 1);
	append_atoms(root, net_client_stack, XA_WINDOW, &c->win, 1);

	if (opt_drag_button)
		/* setup for mod+click dragging */
		XGrabButton(dpy, opt_drag_button, opt_drag_mod, c->win, True,
		    ButtonPressMask | ButtonReleaseMask, GrabModeAsync,
		    GrabModeAsync, None, move_curs);

	check_states(c);

	if (c->wm_hints)
		XFree(c->wm_hints);
	c->wm_hints = XGetWMHints(dpy, c->win);
	if (c->wm_hints && (c->wm_hints->flags & StateHint) &&
	    c->wm_hints->initial_state == IconicState)
		c->state = STATE_ICONIFIED;

#ifdef DEBUG
	dump_name(c, __func__, "", c->name);
	dump_geom(c, c->geom, "XGetWindowAttributes");
	dump_info(c);
#endif

	return c;
}

client_t *
find_client(Window w, int mode)
{
	client_t *c;

	for (c = focused; c; c = c->next) {
		switch (mode) {
		case MATCH_ANY:
			if (w == c->frame || w == c->win || w == c->resize_nw ||
			    w == c->resize_w || w == c->resize_sw ||
			    w == c->resize_s || w == c->resize_se ||
			    w == c->resize_e || w == c->resize_ne ||
			    w == c->resize_n || w == c->titlebar ||
			    w == c->close || w == c->iconify || w == c->zoom ||
			    w == c->icon || w == c->icon_label)
				return c;
			break;
		case MATCH_FRAME:
			if (w == c->frame || w == c->resize_nw ||
			    w == c->resize_w || w == c->resize_sw ||
			    w == c->resize_s || w == c->resize_se ||
			    w == c->resize_e || w == c->resize_ne ||
			    w == c->resize_n || w == c->titlebar ||
			    w == c->close || w == c->iconify || w == c->zoom ||
			    w == c->icon || w == c->icon_label)
				return c;
			break;
		case MATCH_WINDOW:
			if (w == c->win)
				return c;
		}
	}

	return NULL;
}

client_t *
find_client_at_coords(Window w, int x, int y)
{
	unsigned int nwins, i;
	Window qroot, qparent, *wins;
	XWindowAttributes attr;
	client_t *c, *foundc = NULL;

	XQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);
	for (i = nwins - 1; i > 0; i--) {
		ignore_xerrors++;
		XGetWindowAttributes(dpy, wins[i], &attr);
		ignore_xerrors--;
		if (!(c = find_client(wins[i], MATCH_ANY)))
			continue;

		if (c->state & STATE_ICONIFIED) {
			if (x >= c->icon_geom.x &&
			    x <= c->icon_geom.x + c->icon_geom.w &&
			    y >= c->icon_geom.y &&
			    y <= c->icon_geom.y + c->icon_geom.h) {
				foundc = c;
				break;
			}
			if (x >= c->icon_label_geom.x &&
			    x <= c->icon_label_geom.x + c->icon_label_geom.w &&
			    y >= c->icon_label_geom.y &&
			    y <= c->icon_label_geom.y + c->icon_label_geom.h) {
				foundc = c;
				break;
			}
		} else {
			if (x >= c->frame_geom.x &&
			    x <= c->frame_geom.x + c->frame_geom.w &&
			    y >= c->frame_geom.y &&
			    y <= c->frame_geom.y + c->frame_geom.h) {
				foundc = c;
				break;
			}
		}
	}
	XFree(wins);

	return foundc;
}

client_t *
top_client(void)
{
	unsigned int nwins, i;
	Window qroot, qparent, *wins;
	XWindowAttributes attr;
	client_t *c, *foundc = NULL;

	XQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);
	for (i = nwins - 1; i > 0; i--) {
		ignore_xerrors++;
		XGetWindowAttributes(dpy, wins[i], &attr);
		ignore_xerrors--;
		if ((c = find_client(wins[i], MATCH_FRAME)) &&
		    !(c->state & STATE_ICONIFIED)) {
		    	foundc = c;
			break;
		}
	}
	XFree(wins);

	return foundc;
}

void
map_client(client_t *c)
{
	strut_t s = { 0 };
	int want_raise = 0;

	XGrabServer(dpy);

	collect_struts(c, &s);
	init_geom(c, &s);

	/* this also builds (but does not map) the frame windows */
	reparent(c, &s);

	constrain_frame(c);

	if (shape_support)
		set_shape(c);

	if (c->state & STATE_ICONIFIED) {
		c->ignore_unmap++;
		set_wm_state(c, IconicState);
		XUnmapWindow(dpy, c->win);
		iconify_client(c);
	} else {
		/* we're not allowing WithdrawnState */
		set_wm_state(c, NormalState);
		if (!((c->state & STATE_DOCK) ||
		    has_win_type(c, net_wm_type_notif)))
			want_raise = 1;
	}

	if (c->name)
		XFree(c->name);
	c->name = get_wm_name(c->win);

	if (c->icon_name)
		XFree(c->icon_name);
	c->icon_name = get_wm_icon_name(c->win);

	if (c->state & STATE_ICONIFIED) {
		XResizeWindow(dpy, c->win, c->geom.w, c->geom.h);
		send_config(c);
		adjust_client_order(c, ORDER_ICONIFIED_TOP);
	} else {
		/* we haven't drawn anything yet, setup at the right place */
		recalc_frame(c);
		XMoveResizeWindow(dpy, c->frame,
		    c->frame_geom.x, c->frame_geom.y,
		    c->frame_geom.w, c->frame_geom.h);
		XMoveResizeWindow(dpy, c->win,
		    c->geom.x - c->frame_geom.x, c->geom.y - c->frame_geom.y,
		    c->geom.w, c->geom.h);

		if (want_raise) {
			XMapWindow(dpy, c->frame);
			XMapWindow(dpy, c->win);
			focus_client(c, FOCUS_FORCE);
		} else {
			XMapWindow(dpy, c->frame);
			XMapWindow(dpy, c->win);
			adjust_client_order(c, ORDER_BOTTOM);
			redraw_frame(c, None);
		}

		send_config(c);
		flush_expose_client(c);
	}

	XSync(dpy, False);
	XUngrabServer(dpy);
}

void
update_size_hints(client_t *c)
{
	long supplied;

	XGetWMNormalHints(dpy, c->win, &c->size_hints, &supplied);

	/* Discard bogus hints */
	if ((c->size_hints.flags & PAspect) &&
	    (c->size_hints.min_aspect.x < 1 || c->size_hints.min_aspect.y < 1 ||
	    c->size_hints.max_aspect.x < 1 || c->size_hints.max_aspect.y < 1))
		c->size_hints.flags &= ~PAspect;
	if ((c->size_hints.flags & PMaxSize) && c->size_hints.max_width < 1)
		c->size_hints.flags &= ~PMaxSize;
	if ((c->size_hints.flags & PMinSize) && c->size_hints.min_width < 1)
		c->size_hints.flags &= ~PMinSize;
	if ((c->size_hints.flags & PResizeInc) &&
	    (c->size_hints.width_inc < 1 || c->size_hints.height_inc < 1))
		c->size_hints.flags &= ~PResizeInc;
	if ((c->size_hints.flags & (USSize | PSize)) &&
	    (c->size_hints.width < 1 || c->size_hints.height < 1))
		c->size_hints.flags &= ~(USSize|PSize);
}

/*
 * When we're ready to map, we have two things to consider: the literal
 * geometry of the window (what the client passed to XCreateWindow), and the
 * size hints (what they set with XSetWMSizeHints, if anything). Generally, the
 * client doesn't care, and leaves the literal geometry at +0+0. If the client
 * wants to be mapped in a particular place, though, they either set this
 * geometry to something different or set a size hint. The size hint is the
 * recommended method, and takes precedence. If there is already something in
 * c->geom, though, we just leave it.
 */
static void
init_geom(client_t *c, strut_t *s)
{
#ifdef DEBUG
	geom_t size_flags = { 0 };
#endif
	unsigned long win_type, read, left;
	int screen_x = DisplayWidth(dpy, screen);
	int screen_y = DisplayHeight(dpy, screen);
	int wmax = screen_x - s->left - s->right;
	int hmax = screen_y - s->top - s->bottom;
	int mouse_x, mouse_y;
	int i;

	if (c->state & (STATE_ZOOMED | STATE_FULLSCREEN)) {
		/*
		 * For zoomed windows, we'll adjust later to accommodate the
		 * titlebar.
		 */
		c->geom.x = s->top;
		c->geom.y = s->left;
		c->geom.w = wmax;
		c->geom.h = hmax;
#ifdef DEBUG
		dump_geom(c, c->geom, "init_geom zoom/fs");
#endif
		return;
	}

	/*
	 * If size/position hints are zero but the initial XGetWindowAttributes
	 * reported non-zero, ignore these hint values
	 */
	if (c->size_hints.width == 0 && c->geom.w != 0 &&
	    c->size_hints.height == 0 && c->geom.h != 0)
		c->size_hints.flags &= ~(USSize|PSize);
	if (c->size_hints.x == 0 && c->geom.x != 0 &&
	    c->size_hints.y == 0 && c->geom.y != 0)
		c->size_hints.flags &= ~(USPosition|PPosition);

	/*
	 * Here, we merely set the values; they're in the same place regardless
	 * of whether the user or the program specified them. We'll distinguish
	 * between the two cases later, if we need to.
	 */
	if (c->size_hints.flags & (USSize|PSize)) {
		if (c->size_hints.width >= 0)
			c->geom.w = c->size_hints.width;
		if (c->size_hints.height > 0)
			c->geom.h = c->size_hints.height;

#ifdef DEBUG
		size_flags.w = c->size_hints.width;
		size_flags.h = c->size_hints.height;
		dump_geom(c, c->geom, "init_geom size_hints w/h");
#endif
	}

	if (c->size_hints.flags & (USPosition | PPosition)) {
		if (c->size_hints.x >= 0)
			c->geom.x = c->size_hints.x;
		if (c->size_hints.y >= 0)
			c->geom.y = c->size_hints.y;
#ifdef DEBUG
		size_flags.x = c->size_hints.x;
		size_flags.y = c->size_hints.y;
		dump_geom(c, c->geom, "init_geom size_hints x/y");
#endif
	}

#ifdef DEBUG
	if (c->size_hints.flags & (USSize | PSize | USPosition | PPosition))
		dump_geom(c, size_flags, "init_geom size flags");
#endif

	/*
	 * Several types of windows can put themselves wherever they want, but
	 * we need to read the size hints to get that position before
	 * returning.
	 */
	for (i = 0, left = 1; left; i += read) {
		read = get_atoms(c->win, net_wm_wintype, XA_ATOM, i, &win_type,
		    1, &left);
		if (!read)
			break;
		if (CAN_PLACE_SELF(win_type))
			return;
	}

	if (!c->placed) {
		if (c->geom.x <= 0 && c->geom.y <= 0) {
			/* Place the window near the cursor */
			get_pointer(&mouse_x, &mouse_y);
			recalc_map(c, c->geom, mouse_x, mouse_y, mouse_x,
			    mouse_y, s, NULL);
		} else {
			/*
			 * Place the window's frame where the window requested
			 * to be
			 */
			recalc_frame(c);
			c->geom.x += c->border_width;
			c->geom.y += c->border_width + c->titlebar_geom.h;
		}
	}

	/*
	 * In any case, if we got this far, we need to do a further sanity
	 * check and make sure that the window isn't overlapping any struts --
	 * except for transients, because they might be a panel-type client
	 * popping up a notification window over themselves.
	 */
	if (c->geom.x + c->geom.w > screen_x - s->right)
		c->geom.x = screen_x - s->right - c->geom.w;
	if (c->geom.y + c->geom.h > screen_y - s->bottom)
		c->geom.y = screen_y - s->bottom - c->geom.h;
	if (c->geom.x < s->left || c->geom.w > wmax)
		c->geom.x = s->left;
	if (c->geom.y < s->top || c->geom.h > hmax)
		c->geom.y = s->top;

	recalc_frame(c);

	/* only move already-placed windows if they're off-screen */
	if (c->placed &&
	    (c->frame_geom.x < s->left || c->geom.y <= s->top)) {
		c->geom.x += (c->geom.x - c->frame_geom.x);
		c->geom.y += (c->geom.y - c->frame_geom.y);
		recalc_frame(c);
	}

#ifdef DEBUG
	dump_geom(c, c->geom, __func__);
#endif
}

/*
 * The frame window is not created until we actually do the reparenting here,
 * and thus the Xft surface cannot exist until this runs. Anything that has to
 * manipulate the client before we are called must make sure not to attempt to
 * use either.
 */
static void
reparent(client_t *c, strut_t *s)
{
	XSetWindowAttributes pattr;

	recalc_frame(c);

	pattr.override_redirect = True;
	pattr.background_pixel = border_bg.pixel;
	pattr.event_mask = SubMask | ButtonPressMask | ButtonReleaseMask |
	    ExposureMask | EnterWindowMask;
	c->frame = XCreateWindow(dpy, root,
	    c->frame_geom.x, c->frame_geom.y,
	    c->frame_geom.w, c->frame_geom.h,
	    0,
	    DefaultDepth(dpy, screen), CopyFromParent,
	    DefaultVisual(dpy, screen),
	    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);

	/*
	 * Init all windows to 1x1+1+1 because a width/height of 0 causes a
	 * BadValue error.  redraw_frame moves them to the right size and
	 * position anyway, and some of these never even get mapped/shown.
	 */

#define _(x,y,z) x##y##z
#define CREATE_RESIZE_WIN(DIR) \
	pattr.background_pixel = BlackPixel(dpy, screen); \
	pattr.cursor = _(resize_,DIR,_curs); \
	_(c->resize_,DIR,) = XCreateWindow(dpy, c->frame, 1, 1, 1, 1, \
	    0, CopyFromParent, InputOutput, CopyFromParent, \
	    CWOverrideRedirect | CWBackPixel | CWEventMask | CWCursor, \
	    &pattr); \
	XReparentWindow(dpy, _(c->resize_,DIR,), c->frame, \
	    _(c->resize_,DIR,_geom.x), _(c->resize_,DIR,_geom.y));

	CREATE_RESIZE_WIN(nw);
	CREATE_RESIZE_WIN(n);
	CREATE_RESIZE_WIN(ne);
	CREATE_RESIZE_WIN(e);
	CREATE_RESIZE_WIN(se);
	CREATE_RESIZE_WIN(s);
	CREATE_RESIZE_WIN(sw);
	CREATE_RESIZE_WIN(w);
#undef _
#undef CREATE_RESIZE_WIN

	/* no CWCursor for these */
	c->close = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,
	    0, CopyFromParent, InputOutput, CopyFromParent,
	    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);
	XReparentWindow(dpy, c->close, c->frame, c->close_geom.x,
	    c->close_geom.y);

	c->titlebar = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,
	    0, CopyFromParent, InputOutput, CopyFromParent,
	    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);
	XReparentWindow(dpy, c->titlebar, c->frame, c->titlebar_geom.x,
	    c->titlebar_geom.y);

	c->iconify = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,
	    0, CopyFromParent, InputOutput, CopyFromParent,
	    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);
	XReparentWindow(dpy, c->iconify, c->frame, c->iconify_geom.x,
	    c->iconify_geom.y);

	c->zoom = XCreateWindow(dpy, c->frame, 1, 1, 1, 1,
	    0, CopyFromParent, InputOutput, CopyFromParent,
	    CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr);
	XReparentWindow(dpy, c->zoom, c->frame, c->zoom_geom.x,
	    c->zoom_geom.y);

	c->xftdraw = XftDrawCreate(dpy, (Drawable)c->titlebar,
	    DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));

	if (shape_support)
		XShapeSelectInput(dpy, c->win, ShapeNotifyMask);

	XAddToSaveSet(dpy, c->win);
	XSelectInput(dpy, c->win, ColormapChangeMask | PropertyChangeMask);
	XSetWindowBorderWidth(dpy, c->win, 0);
	XReparentWindow(dpy, c->win, c->frame, c->resize_w_geom.w,
	    c->titlebar_geom.y + c->titlebar_geom.h + 1);
}

int
has_win_type(client_t *c, Atom type)
{
	int i;

	for (i = 0; i < (sizeof(c->win_type) / sizeof(c->win_type[0])); i++) {
		if (c->win_type[i] == type)
			return 1;
	}

	return 0;
}

void
recalc_frame(client_t *c)
{
	int buts = font->ascent + font->descent + (2 * opt_pad) + 2;

	if (buts < close_pm_attrs.width)
	    buts = close_pm_attrs.width;

	if (has_win_type(c, net_wm_type_dock) ||
	    has_win_type(c, net_wm_type_menu) ||
	    has_win_type(c, net_wm_type_splash) ||
	    has_win_type(c, net_wm_type_desk) ||
	    has_win_type(c, kde_net_wm_window_type_override))
		c->frame_style = FRAME_NONE;
	else if (has_win_type(c, net_wm_type_notif))
		c->frame_style = FRAME_BORDER;
	else if (has_win_type(c, net_wm_type_utility))
		c->frame_style = (FRAME_BORDER | FRAME_RESIZABLE |
		    FRAME_CLOSE | FRAME_TITLEBAR);
	else if (c->state & (STATE_DOCK | STATE_FULLSCREEN))
		c->frame_style = FRAME_NONE;
	else if (c->state & STATE_ZOOMED)
		c->frame_style = FRAME_ALL & ~(FRAME_BORDER | FRAME_RESIZABLE);
	else
		c->frame_style = FRAME_ALL;

	if ((c->size_hints.flags & PMinSize) &&
	    (c->size_hints.flags & PMaxSize) &&
	    c->size_hints.min_width == c->size_hints.max_width &&
	    c->size_hints.min_height == c->size_hints.max_height)
		c->frame_style &= ~(FRAME_RESIZABLE | FRAME_ZOOM |
		    FRAME_ICONIFY);

	if (c->frame_style & FRAME_BORDER)
		c->border_width = opt_bw + 2;
	else
		c->border_width = 0;

	if (has_win_type(c, net_wm_type_utility)) {
		/* use tiny titlebar with no window title */
		buts = (2 * opt_pad) + 2;
		if (buts < utility_close_pm_attrs.width)
		    buts = utility_close_pm_attrs.width;
		if (c->frame_style & FRAME_RESIZABLE)
			c->border_width = (opt_bw / 2) + 2;
	}

	if (c->frame_style & FRAME_RESIZABLE) {
		c->resize_nw_geom.x = 0;
		c->resize_nw_geom.y = 0;
		c->resize_nw_geom.w = c->border_width + buts;
		c->resize_nw_geom.h = c->border_width + buts;
	} else
		memset(&c->resize_nw_geom, 0, sizeof(geom_t));

	if (c->frame_style & FRAME_CLOSE) {
		c->close_geom.x = c->border_width - 1;
		c->close_geom.y = c->border_width - 1;
		c->close_geom.w = buts + 1;
		c->close_geom.h = buts + 1;
		if (!(c->frame_style & FRAME_RESIZABLE)) {
			c->close_geom.x++;
			c->close_geom.y++;
			c->close_geom.h--;
			c->close_geom.w--;
		}
	} else
		memset(&c->close_geom, 0, sizeof(geom_t));

	if (c->frame_style & FRAME_RESIZABLE) {
		c->resize_n_geom.x = c->border_width + buts;
		c->resize_n_geom.y = 0;
		c->resize_n_geom.w = c->geom.w - buts - buts;
		c->resize_n_geom.h = c->border_width;
	} else
		memset(&c->resize_n_geom, 0, sizeof(geom_t));

	if (c->frame_style & FRAME_RESIZABLE) {
		c->resize_ne_geom.x = c->border_width + c->geom.w - buts;
		c->resize_ne_geom.y = 0;
		c->resize_ne_geom.w = c->border_width + buts;
		c->resize_ne_geom.h = c->border_width + buts;
	} else
		memset(&c->resize_ne_geom, 0, sizeof(geom_t));

	if (c->frame_style & FRAME_ZOOM) {
		c->zoom_geom.x = c->border_width + c->geom.w - buts;
		c->zoom_geom.y = c->border_width - 1;
		c->zoom_geom.w = buts + 1;
		c->zoom_geom.h = buts + 1;
	} else
		memset(&c->zoom_geom, 0, sizeof(geom_t));

	if (c->frame_style & FRAME_ICONIFY) {
		c->iconify_geom.x = c->border_width + c->geom.w - buts;
		c->iconify_geom.y = c->border_width - 1;
		c->iconify_geom.w = buts + 1;
		c->iconify_geom.h = buts + 1;
		if (c->frame_style & FRAME_ZOOM)
			c->iconify_geom.x -= c->zoom_geom.w - 1;
	} else
		memset(&c->iconify_geom, 0, sizeof(geom_t));

	if (c->frame_style & FRAME_TITLEBAR) {
		c->titlebar_geom.x = c->border_width + c->close_geom.w;
		if (c->frame_style & FRAME_CLOSE)
			c->titlebar_geom.x--;
		c->titlebar_geom.y = c->border_width;
		c->titlebar_geom.w = c->geom.w;
		if (c->frame_style & FRAME_CLOSE)
			c->titlebar_geom.w -= c->close_geom.w - 1;
		if (c->frame_style & FRAME_ICONIFY)
			c->titlebar_geom.w -= c->iconify_geom.w - 2;
		if (c->frame_style & FRAME_ZOOM)
			c->titlebar_geom.w -= c->zoom_geom.w - 2;
		if ((c->frame_style & FRAME_ZOOM) &&
		    (c->frame_style & FRAME_ICONIFY))
			c->titlebar_geom.w++;
		c->titlebar_geom.h = buts;
	} else
		memset(&c->titlebar_geom, 0, sizeof(geom_t));

	if ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) {
		c->resize_e_geom.x = c->border_width + c->geom.w;
		c->resize_e_geom.y = c->border_width + buts;
		c->resize_e_geom.w = c->border_width;
		if (c->frame_style & FRAME_TITLEBAR)
			c->resize_e_geom.h = c->geom.h - buts;
		else
			c->resize_e_geom.h = c->geom.h - buts - buts;
	} else
		memset(&c->resize_e_geom, 0, sizeof(geom_t));

	if ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) {
		c->resize_se_geom.x = c->resize_ne_geom.x;
		c->resize_se_geom.y = c->resize_e_geom.y + c->resize_e_geom.h;
		c->resize_se_geom.w = c->border_width + buts;
		c->resize_se_geom.h = c->border_width + buts;
	} else
		memset(&c->resize_se_geom, 0, sizeof(geom_t));

	if (c->frame_style & FRAME_RESIZABLE) {
		if (c->state & STATE_SHADED) {
			c->resize_s_geom.x = 0;
			c->resize_s_geom.y = c->border_width + buts - 1;
			c->resize_s_geom.w = c->border_width + c->geom.w +
			    c->border_width;
			c->resize_s_geom.h = c->border_width;
		} else {
			c->resize_s_geom.x = c->resize_n_geom.x;
			c->resize_s_geom.y = c->resize_se_geom.y + buts;
			c->resize_s_geom.w = c->resize_n_geom.w;
			c->resize_s_geom.h = c->border_width;
		}
	} else
		memset(&c->resize_s_geom, 0, sizeof(geom_t));

	if ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) {
		c->resize_sw_geom.x = 0;
		c->resize_sw_geom.y = c->resize_se_geom.y;
		c->resize_sw_geom.w = c->resize_se_geom.w;
		c->resize_sw_geom.h = c->resize_se_geom.h;
	} else
		memset(&c->resize_sw_geom, 0, sizeof(geom_t));

	c->resize_w_geom.x = 0;
	c->resize_w_geom.y = c->resize_e_geom.y;
	c->resize_w_geom.w = c->resize_e_geom.w;
	c->resize_w_geom.h = c->resize_e_geom.h;

	c->frame_geom.x = c->geom.x - c->border_width;
	c->frame_geom.y = c->geom.y - c->border_width -
	    ((c->frame_style & FRAME_TITLEBAR) ? buts : 0);
	c->frame_geom.w = c->geom.w + c->border_width + c->border_width;
	if (c->state & STATE_SHADED)
		c->frame_geom.h = c->border_width + buts + c->border_width - 1;
	else
		c->frame_geom.h = c->geom.h + c->border_width +
		    ((c->frame_style & FRAME_TITLEBAR) ? buts : 0) +
		    c->border_width;
}

int
set_wm_state(client_t *c, unsigned long state)
{
	return set_atoms(c->win, wm_state, wm_state, &state, 1);
}

void
check_states(client_t *c)
{
	Atom state;
	unsigned long read, left;
	int i;

	/* XXX: c->win is unmapped, we can't talk to it */
	if (c->state & STATE_ICONIFIED)
		return;

	c->state = STATE_NORMAL;
	c->frame_style = FRAME_ALL;

	for (i = 0; i < MAX_WIN_TYPE_ATOMS; i++) {
		if (get_atoms(c->win, net_wm_wintype, XA_ATOM, i,
		    &c->win_type[i], 1, &left)) {
#ifdef DEBUG
			dump_name(c, __func__, "wm_wintype", XGetAtomName(dpy,
			    c->win_type[i]));
#endif
			if (c->win_type[i] == net_wm_type_dock)
				c->state |= STATE_DOCK;
		}

		if (!left)
			break;

		if (left && i == MAX_WIN_TYPE_ATOMS - 1)
			warnx("client has too many _NET_WM_WINDOW_TYPE atoms");
	}

	if (get_wm_state(c->win) == IconicState) {
#ifdef DEBUG
		dump_name(c, __func__, "wm_state", "IconicState");
#endif
		c->state |= STATE_ICONIFIED;
		return;
	}

	for (i = 0, left = 1; left; i += read) {
		read = get_atoms(c->win, net_wm_state, XA_ATOM, i, &state, 1,
		    &left);
		if (!read)
			break;
#ifdef DEBUG
		dump_name(c, __func__, "net_wm_state", XGetAtomName(dpy,
		    state));
#endif
		if (state == net_wm_state_shaded)
			c->state |= STATE_SHADED;
		else if (state == net_wm_state_mh || state == net_wm_state_mv)
			c->state |= STATE_ZOOMED;
		else if (state == net_wm_state_fs)
			c->state |= STATE_FULLSCREEN;
		else if (state == net_wm_state_above)
			c->state |= STATE_ABOVE;
		else if (state == net_wm_state_below)
			c->state |= STATE_BELOW;
	}
}

/* If we frob the geom for some reason, we need to inform the client. */
void
send_config(client_t *c)
{
	XConfigureEvent ce;

	ce.type = ConfigureNotify;
	ce.event = c->win;
	ce.window = c->win;
	ce.x = c->geom.x;
	ce.y = c->geom.y;
	ce.width = c->geom.w;
	ce.height = c->geom.h;
	ce.border_width = 0;
	ce.above = None;
	ce.override_redirect = 0;

	XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
}

void
redraw_frame(client_t *c, Window only)
{
	XftColor *txft;
	XGlyphInfo extents;
	Pixmap *pm, *pm_mask;
	XpmAttributes *pm_attrs;
	int x, y, tw;

	if (!c || (c->frame_style == FRAME_NONE) || !c->frame)
		return;

	if (!IS_ON_CUR_DESK(c))
		return;

	if (c->state & STATE_ICONIFIED) {
		redraw_icon(c, only);
		return;
	}

#ifdef DEBUG
	dump_name(c, __func__, frame_name(c, only), c->name);
#endif

	recalc_frame(c);

	if (only == None) {
		if ((c->frame_style & FRAME_BORDER) &&
		    !(c->frame_style & FRAME_RESIZABLE)) {
			if (c == focused)
				XSetWindowBackground(dpy, c->frame, bg.pixel);
			else
				XSetWindowBackground(dpy, c->frame,
				    unfocused_bg.pixel);
			XClearWindow(dpy, c->frame);

			XSetForeground(dpy, DefaultGC(dpy, screen),
			    WhitePixel(dpy, screen));
			XDrawLine(dpy, c->frame, DefaultGC(dpy, screen),
			    c->border_width, c->border_width,
			    c->frame_geom.w - c->border_width,
			    c->border_width);
			XDrawLine(dpy, c->frame, DefaultGC(dpy, screen),
			    c->frame_geom.w - c->border_width - 1,
			    c->border_width,
			    c->frame_geom.w - c->border_width - 1,
			    c->border_width + c->titlebar_geom.h);
		} else
			XSetWindowBackground(dpy, c->frame,
			    BlackPixel(dpy, screen));

		XMoveResizeWindow(dpy, c->frame,
		    c->frame_geom.x, c->frame_geom.y,
		    c->frame_geom.w, c->frame_geom.h);

		if (c->state & STATE_SHADED)
			/* keep win just below our shaded frame */
			XMoveResizeWindow(dpy, c->win,
			    c->geom.x - c->frame_geom.x,
			    c->geom.y - c->frame_geom.y + c->border_width + 1,
			    c->geom.w, c->geom.h);
		else
			XMoveResizeWindow(dpy, c->win,
			    c->geom.x - c->frame_geom.x,
			    c->geom.y - c->frame_geom.y,
			    c->geom.w, c->geom.h);

		XSetForeground(dpy, DefaultGC(dpy, screen), border_fg.pixel);
		XDrawRectangle(dpy, c->frame, DefaultGC(dpy, screen),
		    0, 0, c->frame_geom.w - 1, c->frame_geom.h - 1);
	}

	if (only == None || only == c->titlebar) {
		if (c->frame_style & FRAME_TITLEBAR) {
			if (c == focused) {
				txft = &xft_fg;
				XSetWindowBackground(dpy, c->titlebar,
				    bg.pixel);
			} else {
				txft = &xft_fg_unfocused;
				XSetWindowBackground(dpy, c->titlebar,
				    unfocused_bg.pixel);
			}
			XMoveResizeWindow(dpy, c->titlebar,
			    c->titlebar_geom.x, c->titlebar_geom.y,
			    c->titlebar_geom.w, c->titlebar_geom.h);
			XMapWindow(dpy, c->titlebar);
			XClearWindow(dpy, c->titlebar);

			if (c->name && !has_win_type(c, net_wm_type_utility)) {
				XftTextExtentsUtf8(dpy, font,
				    (FcChar8 *)c->name, strlen(c->name),
				    &extents);
				tw = extents.xOff;
				x = opt_pad * 2;

				if (tw < (c->titlebar_geom.w - (opt_pad * 2)))
					/* center title */
					x = (c->titlebar_geom.w / 2) - (tw / 2);

				y = opt_pad + font->ascent;

				XftDrawStringUtf8(c->xftdraw, txft, font, x,
				    y, (unsigned char *)c->name,
				    strlen(c->name));
			}
			if (!(c->frame_style & FRAME_RESIZABLE) &&
			    (c->state & STATE_SHADED))
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    WhitePixel(dpy, screen));
			else
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    border_fg.pixel);
			XDrawLine(dpy, c->titlebar, DefaultGC(dpy, screen),
			    0, c->titlebar_geom.h - 1, c->titlebar_geom.w + 1,
			    c->titlebar_geom.h - 1);

			if ((c->frame_style & FRAME_BORDER) &&
			    !(c->frame_style & FRAME_RESIZABLE)) {
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    WhitePixel(dpy, screen));
				XDrawLine(dpy, c->titlebar,
				    DefaultGC(dpy, screen),
				    0, 0, c->titlebar_geom.w, 0);
				XDrawLine(dpy, c->titlebar,
				    DefaultGC(dpy, screen),
				    c->titlebar_geom.w - 1, 0,
				    c->titlebar_geom.w - 1,
				    c->titlebar_geom.h - 1);
			}
		} else
			XUnmapWindow(dpy, c->titlebar);
	}

	if (only == None || only == c->close) {
		if (c->frame_style & FRAME_CLOSE) {
			XSetWindowBackground(dpy, c->close, button_bg.pixel);
			XClearWindow(dpy, c->close);
			XMoveResizeWindow(dpy, c->close,
			    c->close_geom.x, c->close_geom.y, c->close_geom.w,
			    c->close_geom.h);
			XMapWindow(dpy, c->close);

			if (has_win_type(c, net_wm_type_utility)) {
				pm = &utility_close_pm;
				pm_mask = &utility_close_pm_mask;
				pm_attrs = &utility_close_pm_attrs;
			} else {
				pm = &close_pm;
				pm_mask = &close_pm_mask;
				pm_attrs = &close_pm_attrs;
			}

			x = (c->close_geom.w / 2) - (pm_attrs->width / 2);
			y = (c->close_geom.h / 2) - (pm_attrs->height / 2);
			XSetClipMask(dpy, pixmap_gc, *pm_mask);
			XSetClipOrigin(dpy, pixmap_gc, x, y);
			XCopyArea(dpy, *pm, c->close, pixmap_gc, 0, 0,
			    pm_attrs->width, pm_attrs->height, x, y);
			if (c->close_pressed)
				XCopyArea(dpy, c->close, c->close, invert_gc,
				    0, 0, c->close_geom.w, c->close_geom.h, 0,
				    0);
			else
				XCopyArea(dpy, c->close, c->close, pixmap_gc,
				    0, 0, c->close_geom.w, c->close_geom.h, 0,
				    0);

			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->close,
			    DefaultGC(dpy, screen),
			    0, 0, c->close_geom.w - 1,
			    c->close_geom.h - 1);

			if ((c->frame_style & FRAME_BORDER) &&
			    !(c->frame_style & FRAME_RESIZABLE)) {
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    WhitePixel(dpy, screen));
				XDrawLine(dpy, c->close,
				    DefaultGC(dpy, screen),
				    0, 0, c->close_geom.w, 0);
				XDrawLine(dpy, c->close,
				    DefaultGC(dpy, screen),
				    0, 0, 0, c->close_geom.h - 1);
			}
		} else
			XUnmapWindow(dpy, c->close);
	}

	if (only == None || only == c->iconify) {
		if (c->frame_style & FRAME_ICONIFY) {
			XSetWindowBackground(dpy, c->iconify, button_bg.pixel);
			XClearWindow(dpy, c->iconify);
			XMoveResizeWindow(dpy, c->iconify,
			    c->iconify_geom.x, c->iconify_geom.y,
			    c->iconify_geom.w,
			    c->iconify_geom.h);
			XMapWindow(dpy, c->iconify);
			x = (c->iconify_geom.w / 2) -
			    (iconify_pm_attrs.width / 2) - (opt_bevel / 2);
			y = (c->iconify_geom.h / 2) -
			    (iconify_pm_attrs.height / 2) - (opt_bevel / 2);
			if (c->iconify_pressed) {
				x += 2;
				y += 2;
			}
			XSetClipMask(dpy, pixmap_gc, iconify_pm_mask);
			XSetClipOrigin(dpy, pixmap_gc, x, y);
			XCopyArea(dpy, iconify_pm, c->iconify, pixmap_gc, 0, 0,
			    iconify_pm_attrs.width, iconify_pm_attrs.height, x,
			    y);
			bevel(c->iconify, c->iconify_geom, c->iconify_pressed);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->iconify, DefaultGC(dpy, screen),
			    0, 0, c->iconify_geom.w - 1, c->iconify_geom.h - 1);
		} else
			XUnmapWindow(dpy, c->iconify);
	}

	if (only == None || only == c->zoom) {
		if (c->frame_style & FRAME_ZOOM) {
			XMoveResizeWindow(dpy, c->zoom,
			    c->zoom_geom.x, c->zoom_geom.y, c->zoom_geom.w,
			    c->zoom_geom.h);
			XSetWindowBackground(dpy, c->zoom, button_bg.pixel);
			XClearWindow(dpy, c->zoom);
			XMapWindow(dpy, c->zoom);

			if (c->state & STATE_ZOOMED) {
				pm = &unzoom_pm;
				pm_mask = &unzoom_pm_mask;
				pm_attrs = &unzoom_pm_attrs;
			} else {
				pm = &zoom_pm;
				pm_mask = &zoom_pm_mask;
				pm_attrs = &zoom_pm_attrs;
			}

			x = (c->zoom_geom.w / 2) - (pm_attrs->width / 2) -
			    (opt_bevel / 2);
			y = (c->zoom_geom.h / 2) - (pm_attrs->height / 2) -
			    (opt_bevel / 2);
			if (c->zoom_pressed) {
				x += 2;
				y += 2;
			}
			XSetClipMask(dpy, pixmap_gc, *pm_mask);
			XSetClipOrigin(dpy, pixmap_gc, x, y);
			XCopyArea(dpy, *pm, c->zoom, pixmap_gc, 0, 0,
			    pm_attrs->width, pm_attrs->height, x, y);
			bevel(c->zoom, c->zoom_geom, c->zoom_pressed);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->zoom, DefaultGC(dpy, screen),
			    0, 0, c->zoom_geom.w - 1, c->zoom_geom.h - 1);
		} else
			XUnmapWindow(dpy, c->zoom);
	}

	if (only == None || only == c->resize_nw) {
		if (c->frame_style & FRAME_RESIZABLE) {
			XSetWindowBackground(dpy, c->resize_nw,
			    border_bg.pixel);
			XClearWindow(dpy, c->resize_nw);
			XMoveResizeWindow(dpy, c->resize_nw,
			    c->resize_nw_geom.x, c->resize_nw_geom.y,
			    c->resize_nw_geom.w, c->resize_nw_geom.h);
			XMapWindow(dpy, c->resize_nw);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_nw,
			    DefaultGC(dpy, screen), 0, 0,
			    c->resize_nw_geom.w - 1, c->resize_nw_geom.h - 1);

			if (!(c->frame_style & FRAME_CLOSE)) {
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    border_fg.pixel);
				XDrawRectangle(dpy, c->resize_nw,
				    DefaultGC(dpy, screen),
				    c->resize_n_geom.h - 1,
				    c->resize_n_geom.h - 1,
				    c->resize_nw_geom.w - 1,
				    c->resize_nw_geom.h - 1);
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    BlackPixel(dpy, screen));
				XFillRectangle(dpy, c->resize_nw,
				    DefaultGC(dpy, screen),
				    c->resize_n_geom.h,
				    c->resize_n_geom.h,
				    c->resize_nw_geom.h - 2,
				    c->resize_nw_geom.h - 2);
			}
		} else
			XUnmapWindow(dpy, c->resize_nw);
	}

	if (only == None || only == c->resize_n) {
		if (c->frame_style & FRAME_RESIZABLE) {
			XSetWindowBackground(dpy, c->resize_n, border_bg.pixel);
			XClearWindow(dpy, c->resize_n);
			XMoveResizeWindow(dpy, c->resize_n,
			    c->resize_n_geom.x, c->resize_n_geom.y,
			    c->resize_n_geom.w, c->resize_n_geom.h);
			XMapWindow(dpy, c->resize_n);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_n, DefaultGC(dpy, screen),
			    -1, 0, c->resize_n_geom.w + 1,
			    c->resize_n_geom.h - 1);
		} else
			XUnmapWindow(dpy, c->resize_n);
	}

	if (only == None || only == c->resize_ne) {
		if (c->frame_style & FRAME_RESIZABLE) {
			XSetWindowBackground(dpy, c->resize_ne,
			    border_bg.pixel);
			XMapWindow(dpy, c->resize_ne);
			XClearWindow(dpy, c->resize_ne);
			XMoveResizeWindow(dpy, c->resize_ne,
			    c->resize_ne_geom.x, c->resize_ne_geom.y,
			    c->resize_ne_geom.w, c->resize_ne_geom.h);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_ne,
			    DefaultGC(dpy, screen), 0, 0,
			    c->resize_ne_geom.w - 1, c->resize_ne_geom.h - 1);
			if (!(c->frame_style & (FRAME_ICONIFY | FRAME_ZOOM))) {
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    border_fg.pixel);
				XDrawRectangle(dpy, c->resize_ne,
				    DefaultGC(dpy, screen),
				    0, c->resize_n_geom.h - 1,
				    c->resize_ne_geom.w - c->resize_n_geom.h,
				    c->resize_ne_geom.h - 1);
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    BlackPixel(dpy, screen));
				XFillRectangle(dpy, c->resize_ne,
				    DefaultGC(dpy, screen),
				    0, c->resize_n_geom.h,
				    c->resize_ne_geom.w - c->resize_n_geom.h,
				    c->resize_ne_geom.h - 2);
			}
		} else
			XUnmapWindow(dpy, c->resize_ne);
	}

	if (only == None || only == c->resize_e) {
		if ((c->frame_style & FRAME_RESIZABLE) &&
		    !(c->state & STATE_SHADED)) {
			XSetWindowBackground(dpy, c->resize_e, border_bg.pixel);
			XMapWindow(dpy, c->resize_e);
			XClearWindow(dpy, c->resize_e);
			XMoveResizeWindow(dpy, c->resize_e,
			    c->resize_e_geom.x, c->resize_e_geom.y,
			    c->resize_e_geom.w, c->resize_e_geom.h);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_e, DefaultGC(dpy, screen),
			    0, -1, c->resize_e_geom.w - 1,
			    c->resize_e_geom.h + 1);
		} else
			XUnmapWindow(dpy, c->resize_e);
	}

	if (only == None || only == c->resize_se) {
		if ((c->frame_style & FRAME_RESIZABLE) &&
		    !(c->state & STATE_SHADED)) {
			XSetWindowBackground(dpy, c->resize_se,
			    border_bg.pixel);
			XClearWindow(dpy, c->resize_se);
			XMoveResizeWindow(dpy, c->resize_se,
			    c->resize_se_geom.x, c->resize_se_geom.y,
			    c->resize_se_geom.w, c->resize_se_geom.h);
			XMapWindow(dpy, c->resize_se);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_se,
			    DefaultGC(dpy, screen), 0, 0,
			    c->resize_se_geom.w - 1,
			    c->resize_se_geom.h - 1);
			XDrawRectangle(dpy, c->resize_se,
			    DefaultGC(dpy, screen), 0, 0,
			    c->resize_se_geom.w - c->resize_s_geom.h,
			    c->resize_se_geom.h - c->resize_s_geom.h);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    BlackPixel(dpy, screen));
			XFillRectangle(dpy, c->resize_se,
			    DefaultGC(dpy, screen), 0, 0,
			    c->resize_se_geom.w - c->resize_s_geom.h,
			    c->resize_se_geom.h - c->resize_s_geom.h);
		} else
			XUnmapWindow(dpy, c->resize_se);
	}

	if (only == None || only == c->resize_s) {
		if (c->frame_style & FRAME_RESIZABLE) {
			XSetWindowBackground(dpy, c->resize_s, border_bg.pixel);
			XClearWindow(dpy, c->resize_s);
			XMoveResizeWindow(dpy, c->resize_s,
			    c->resize_s_geom.x, c->resize_s_geom.y,
			    c->resize_s_geom.w, c->resize_s_geom.h);
			XMapWindow(dpy, c->resize_s);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_s, DefaultGC(dpy, screen),
			    0, 0, c->resize_s_geom.w,
			    c->resize_s_geom.h - 1);

			if (c->state & STATE_SHADED) {
				XSetForeground(dpy, DefaultGC(dpy, screen),
				    border_bg.pixel);
				XDrawLine(dpy, c->resize_s,
				    DefaultGC(dpy, screen), 1, 0,
				    c->resize_s_geom.h - 1, 0);
				XDrawLine(dpy, c->resize_s,
				    DefaultGC(dpy, screen),
				    c->resize_s_geom.w - c->resize_s_geom.h + 1,
				    0, c->resize_s_geom.w - 1, 0);

				XSetForeground(dpy, DefaultGC(dpy, screen),
				    border_fg.pixel);
				XDrawLine(dpy, c->resize_s,
				    DefaultGC(dpy, screen),
				    c->resize_sw_geom.w, 0,
				    c->resize_sw_geom.w, c->resize_s_geom.h);
				XDrawLine(dpy, c->resize_s,
				    DefaultGC(dpy, screen),
				    c->resize_s_geom.w - c->resize_sw_geom.w -
				    1, 0,
				    c->resize_s_geom.w - c->resize_sw_geom.w -
				    1, c->resize_s_geom.h);
			}
		} else
			XUnmapWindow(dpy, c->resize_s);
	}

	if (only == None || only == c->resize_sw) {
		if ((c->frame_style & FRAME_RESIZABLE) &&
		    !(c->state & STATE_SHADED)) {
			XSetWindowBackground(dpy, c->resize_sw,
			    border_bg.pixel);
			XClearWindow(dpy, c->resize_sw);
			XMoveResizeWindow(dpy, c->resize_sw,
			    c->resize_sw_geom.x, c->resize_sw_geom.y,
			    c->resize_sw_geom.w, c->resize_sw_geom.h);
			XMapWindow(dpy, c->resize_sw);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_sw,
			    DefaultGC(dpy, screen), 0, 0, c->resize_sw_geom.w,
			    c->resize_sw_geom.h - 1);
			XDrawRectangle(dpy, c->resize_sw,
			    DefaultGC(dpy, screen),
			    c->resize_w_geom.w - 1, 0,
			    c->resize_sw_geom.w,
			    c->resize_sw_geom.h - c->resize_s_geom.h);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    BlackPixel(dpy, screen));
			XFillRectangle(dpy, c->resize_sw,
			    DefaultGC(dpy, screen), c->resize_w_geom.w, 0,
			    c->resize_sw_geom.w,
			    c->resize_sw_geom.h - c->resize_s_geom.h);
		} else
			XUnmapWindow(dpy, c->resize_sw);
	}

	if (only == None || only == c->resize_w) {
		if ((c->frame_style & FRAME_RESIZABLE) &&
		    !(c->state & STATE_SHADED)) {
			XSetWindowBackground(dpy, c->resize_w, border_bg.pixel);
			XClearWindow(dpy, c->resize_w);
			XMoveResizeWindow(dpy, c->resize_w,
			    c->resize_w_geom.x, c->resize_w_geom.y,
			    c->resize_w_geom.w, c->resize_w_geom.h);
			XMapWindow(dpy, c->resize_w);
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    border_fg.pixel);
			XDrawRectangle(dpy, c->resize_w, DefaultGC(dpy, screen),
			    0, -1, c->resize_w_geom.w - 1,
			    c->resize_w_geom.h + 1);
		} else
			XUnmapWindow(dpy, c->resize_w);
	}
}

static void
bevel(Window win, geom_t geom, int pressed)
{
	int x;

	XSetForeground(dpy, DefaultGC(dpy, screen), bevel_dark.pixel);

	if (pressed) {
		for (x = 0; x < opt_bevel - 1; x++) {
			XDrawLine(dpy, win, DefaultGC(dpy, screen),
			    x, x, geom.w - x, x);
			XDrawLine(dpy, win, DefaultGC(dpy, screen),
			    x, x, x, geom.h - x);
		}
	} else {
		for (x = 1; x <= opt_bevel; x++) {
			XDrawLine(dpy, win, DefaultGC(dpy, screen),
			    geom.w - 1 - x, x,
			    geom.w - 1 - x, geom.h - 1);
			XDrawLine(dpy, win, DefaultGC(dpy, screen),
			    x, geom.h - 1 - x,
			    geom.w - 1, geom.h - x - 1);
		}

		XSetForeground(dpy, DefaultGC(dpy, screen), bevel_light.pixel);
		for (x = 1; x <= opt_bevel - 1; x++) {
			XDrawLine(dpy, win, DefaultGC(dpy, screen),
			    1, x,
			    geom.w - 1 - x, x);
			XDrawLine(dpy, win, DefaultGC(dpy, screen),
			    x, 1,
			    x, geom.h - 1 - x);
		}
	}
}

void
get_client_icon(client_t *c)
{
	Window junkw;
	unsigned long w, h;
	unsigned int depth;
	int junki;

	/* try through atom */
	if (get_atoms(c->win, net_wm_icon, XA_CARDINAL, 0, &w, 1, NULL) &&
	    get_atoms(c->win, net_wm_icon, XA_CARDINAL, 1, &h, 1, NULL)) {
	    	/* TODO */
	}

	if (c->icon_managed) {
		if (c->icon_pixmap)
			XFreePixmap(dpy, c->icon_pixmap);
		if (c->icon_mask)
			XFreePixmap(dpy, c->icon_mask);
		c->icon_managed = 0;
	}

	/* fallback to WMHints */
	if (c->wm_hints)
		XFree(c->wm_hints);
	c->wm_hints = XGetWMHints(dpy, c->win);
	if (!c->wm_hints || !(c->wm_hints->flags & IconPixmapHint)) {
		c->icon_pixmap = default_icon_pm;
		c->icon_depth = DefaultDepth(dpy, screen);
		c->icon_mask = default_icon_pm_mask;
		return;
	}

	XGetGeometry(dpy, c->wm_hints->icon_pixmap, &junkw, &junki, &junki,
	    (unsigned int *)&c->icon_geom.w, (unsigned int *)&c->icon_geom.h,
	    (unsigned int *)&junki, &depth);
	c->icon_pixmap = c->wm_hints->icon_pixmap;
	c->icon_depth = depth;

	if (c->wm_hints->flags & IconMaskHint)
		c->icon_mask = c->wm_hints->icon_mask;
	else
		c->icon_mask = None;

#ifdef USE_GDK_PIXBUF
	if (c->icon_geom.w > icon_size || c->icon_geom.h > icon_size) {
		GdkPixbuf *gp, *mask, *scaled;
		int sh, sw;

		if (c->icon_geom.w > c->icon_geom.h) {
			sw = icon_size;
			sh = (icon_size / (double)c->icon_geom.w) *
			    c->icon_geom.h;
		} else {
			sh = icon_size;
			sw = (icon_size / (double)c->icon_geom.h) *
			    c->icon_geom.w;
		}

		gp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
		    c->icon_geom.w, c->icon_geom.h);

		if (!gdk_pixbuf_xlib_get_from_drawable(gp, c->icon_pixmap,
		    c->cmap, DefaultVisual(dpy, screen), 0, 0, 0, 0,
		    c->icon_geom.w, c->icon_geom.h)) {
			warnx("failed to load pixmap with gdk pixbuf");
			g_object_unref(gp);
			return;
		}

		/* manually mask image, ugh */
		if (c->icon_mask != None) {
			guchar *px, *pxm;
			int rs, rsm, dm, ch, x, y;

			mask = gdk_pixbuf_xlib_get_from_drawable(NULL,
			    c->icon_mask, c->cmap, DefaultVisual(dpy, screen),
			    0, 0, 0, 0, c->icon_geom.w, c->icon_geom.h);
			if (!mask) {
				warnx("failed to load mask with gdk pixbuf");
				g_object_unref(gp);
				return;
			}

			px = gdk_pixbuf_get_pixels(gp);
			pxm = gdk_pixbuf_get_pixels(mask);
			rs = gdk_pixbuf_get_rowstride(gp);
			rsm = gdk_pixbuf_get_rowstride(mask);
			dm = gdk_pixbuf_get_bits_per_sample(mask);
			ch = gdk_pixbuf_get_n_channels(mask);

			for (y = 0; y < c->icon_geom.h; y++) {
				guchar *tr = px + (y * rs);
				guchar *trm = pxm + (y * rsm);
				for (x = 0; x < c->icon_geom.w; x++) {
					guchar al = 0xff;
					switch (dm) {
					case 1:
						al = trm[x * ch / 8];
						al >>= (x % 8);
						al = al ? 0xff : 0;
						break;
					case 8:
						al = (trm[(x * ch) + 2]) ? 0xff
						    : 0;
						break;
					}

					tr[(x * 4) + 3] = al;
				}
			}

			g_object_unref(mask);
		}

		scaled = gdk_pixbuf_scale_simple(gp, sw, sh,
		    GDK_INTERP_BILINEAR);
		if (!scaled) {
			warnx("failed to scale icon with gdk pixbuf");
			g_object_unref(gp);
			return;
		}

		if (c->icon_managed) {
			if (c->icon_pixmap)
				XFreePixmap(dpy, c->icon_pixmap);
			if (c->icon_mask)
				XFreePixmap(dpy, c->icon_mask);
		}

		gdk_pixbuf_xlib_render_pixmap_and_mask(scaled,
		    &c->icon_pixmap, &c->icon_mask, 1);
		c->icon_geom.w = sw;
		c->icon_geom.h = sh;
		c->icon_managed = 1;

		g_object_unref(scaled);
		g_object_unref(gp);
	}
#endif
}

void
redraw_icon(client_t *c, Window only)
{
	XftColor *txft;
	void *xft_lines;
	int label_pad = 2 * opt_scale;
	int nlines, x;

#ifdef DEBUG
	dump_name(c, __func__, frame_name(c, only), c->name);
#endif

	if (only == None || only == c->icon) {
		XClearWindow(dpy, c->icon);
		XSetWindowBackground(dpy, c->icon, WhitePixel(dpy, screen));
		XMoveResizeWindow(dpy, c->icon,
		    c->icon_geom.x + ((icon_size - c->icon_geom.w) / 2),
		    c->icon_geom.y + ((icon_size - c->icon_geom.h) / 2),
		    c->icon_geom.w, c->icon_geom.h);
		if (c->icon_mask) {
			XShapeCombineMask(dpy, c->icon, ShapeBounding, 0, 0,
			    c->icon_mask, ShapeSet);
			XSetClipMask(dpy, c->icon_gc, c->icon_mask);
			XSetClipOrigin(dpy, c->icon_gc, 0, 0);
		}
		if (c->icon_depth == DefaultDepth(dpy, screen))
			XCopyArea(dpy, c->icon_pixmap, c->icon, c->icon_gc,
			    0, 0, c->icon_geom.w, c->icon_geom.h, 0, 0);
		else {
			XSetBackground(dpy, c->icon_gc,
			    BlackPixel(dpy, screen));
			XSetForeground(dpy, c->icon_gc,
			    WhitePixel(dpy, screen));
			XCopyPlane(dpy, c->icon_pixmap, c->icon, c->icon_gc,
			    0, 0, c->icon_geom.w, c->icon_geom.h, 0, 0, 1);
		}
	}

	if (only != None && only != c->icon_label)
		return;

	if (c == focused) {
		txft = &xft_fg;
		XSetWindowBackground(dpy, c->icon_label, bg.pixel);
		XSetForeground(dpy, DefaultGC(dpy, screen), fg.pixel);
	} else {
		txft = &xft_fg_unfocused;
		XSetWindowBackground(dpy, c->icon_label, unfocused_bg.pixel);
		XSetForeground(dpy, DefaultGC(dpy, screen),
		    unfocused_fg.pixel);
	}

	XClearWindow(dpy, c->icon_label);

	if (!c->icon_name)
		c->icon_name = strdup("(Unknown)");

	xft_lines = word_wrap_xft(c->icon_name, ' ', iconfont,
	    (icon_size * 2) - (label_pad * 2), &nlines);

	c->icon_label_geom.y = c->icon_geom.y + icon_size + 10;
	c->icon_label_geom.h = label_pad;
	c->icon_label_geom.w = label_pad;

	for (x = 0; x < nlines; x++) {
		struct xft_line_t *line = xft_lines +
		    (sizeof(struct xft_line_t) * x);
		int w = label_pad + line->xft_width + label_pad;
		if (w > c->icon_label_geom.w)
			c->icon_label_geom.w = w;
		c->icon_label_geom.h += iconfont->ascent + iconfont->descent;
	}

	c->icon_label_geom.h += label_pad;
	c->icon_label_geom.x = c->icon_geom.x -
	    ((c->icon_label_geom.w - icon_size) / 2);

	XMoveResizeWindow(dpy, c->icon_label,
	    c->icon_label_geom.x, c->icon_label_geom.y,
	    c->icon_label_geom.w, c->icon_label_geom.h);

	int ly = label_pad;
	for (x = 0; x < nlines; x++) {
		struct xft_line_t *line = xft_lines +
		    (sizeof(struct xft_line_t) * x);
		int lx = ((c->icon_label_geom.w - line->xft_width) / 2);

		ly += iconfont->ascent;
		XftDrawStringUtf8(c->icon_xftdraw, txft, iconfont, lx, ly,
		    (FcChar8 *)line->str, line->len);
		ly += iconfont->descent;
	}

	free(xft_lines);
}

void
collect_struts(client_t *c, strut_t *s)
{
	client_t *p;
	XWindowAttributes attr;
	strut_t temp;

	for (p = focused; p; p = p->next) {
		if (!IS_ON_CUR_DESK(p) || p == c)
			continue;

		ignore_xerrors++;
		XGetWindowAttributes(dpy, p->win, &attr);
		ignore_xerrors--;
		if (attr.map_state == IsViewable && get_strut(p->win, &temp)) {
			if (temp.left > s->left)
				s->left = temp.left;
			if (temp.right > s->right)
				s->right = temp.right;
			if (temp.top > s->top)
				s->top = temp.top;
			if (temp.bottom > s->bottom)
				s->bottom = temp.bottom;
		}
	}
}

/*
 * Well, the man pages for the shape extension say nothing, but I was able to
 * find a shape.PS.Z on the x.org FTP site. What we want to do here is make the
 * window shape be a boolean OR (or union) of the client's shape and our bar
 * for the name. The bar requires both a bound and a clip because it has a
 * border; the server will paint the border in the region between the two.
 */
void
set_shape(client_t *c)
{
	int n, order;
	XRectangle temp, *rects;

	rects = XShapeGetRectangles(dpy, c->win, ShapeBounding, &n, &order);

	if (n > 1) {
		/* window contents */
		XShapeCombineShape(dpy, c->frame, ShapeBounding,
		    c->resize_w_geom.w,
		    c->resize_n_geom.h + c->titlebar_geom.h,
		    c->win, ShapeBounding, ShapeSet);

		/* titlebar */
		temp.x = 0;
		temp.y = 0;
		temp.width = c->frame_geom.w;
		temp.height = c->resize_n_geom.h + c->titlebar_geom.h;
		XShapeCombineRectangles(dpy, c->frame, ShapeBounding,
		    0, 0, &temp, 1, ShapeUnion, YXBanded);

		/* bottom border */
		temp.height = c->resize_s_geom.h;
		XShapeCombineRectangles(dpy, c->frame, ShapeBounding,
		    0, c->frame_geom.h - c->resize_s_geom.h, &temp, 1,
		    ShapeUnion, YXBanded);

		/* left border */
		temp.width = c->resize_w_geom.w;
		temp.height = c->frame_geom.h;
		XShapeCombineRectangles(dpy, c->frame, ShapeBounding,
		    0, 0, &temp, 1, ShapeUnion, YXBanded);
		/* right border */
		XShapeCombineRectangles(dpy, c->frame, ShapeBounding,
		    c->frame_geom.w - c->resize_e_geom.w, 0, &temp, 1,
		    ShapeUnion, YXBanded);

		c->shaped = 1;
	} else
		c->shaped = 0;

	XFree(rects);
}

/*
 * I've decided to carefully ignore any errors raised by this function, rather
 * that attempt to determine asychronously if a window is "valid". Xlib calls
 * should only fail here if that a window has removed itself completely before
 * the Unmap and Destroy events get through the queue to us. It's not pretty.
 *
 * The mode argument specifes if the client is actually destroying itself or
 * being destroyed by us, or if we are merely cleaning up its data structures
 * when we exit mid-session.
 */
void
del_client(client_t *c, int mode)
{
	client_t *next;

	XSync(dpy, False);
	XGrabServer(dpy);
	ignore_xerrors++;

#ifdef DEBUG
	dump_name(c, __func__, mode == DEL_WITHDRAW ? "withdraw" : "", c->name);
	dump_removal(c, mode);
#endif

	if (mode == DEL_WITHDRAW) {
		set_wm_state(c, WithdrawnState);
		XUnmapWindow(dpy, c->frame);
	} else {
		if (c->state & STATE_ZOOMED) {
			c->geom.x = c->save.x;
			c->geom.y = c->save.y;
			c->geom.w = c->save.w;
			c->geom.h = c->save.h;
			XResizeWindow(dpy, c->win, c->geom.w, c->geom.h);
		}
		XMapWindow(dpy, c->win);
		XSetWindowBorderWidth(dpy, c->win, c->old_bw);
	}

	remove_atom(root, net_client_list, XA_WINDOW, c->win);
	remove_atom(root, net_client_stack, XA_WINDOW, c->win);

	if (c->xftdraw)
		XftDrawDestroy(c->xftdraw);

	XReparentWindow(dpy, c->win, root, c->geom.x, c->geom.y);
	XRemoveFromSaveSet(dpy, c->win);
	XDestroyWindow(dpy, c->frame);

	if (c->icon_xftdraw)
		XftDrawDestroy(c->icon_xftdraw);
	if (c->icon) {
		XDestroyWindow(dpy, c->icon);
		if (c->icon_label)
			XDestroyWindow(dpy, c->icon_label);
	}
	if (c->icon_gc)
		XFreeGC(dpy, c->icon_gc);
	if (c->icon_managed) {
		if (c->icon_pixmap)
			XFreePixmap(dpy, c->icon_pixmap);
		if (c->icon_mask)
			XFreePixmap(dpy, c->icon_mask);
		c->icon_managed = 0;
	}

	if (c->name)
		XFree(c->name);
	if (c->icon_name)
		XFree(c->icon_name);

	if (focused == c) {
		next = next_client_for_focus(focused);
		if (!next)
			next = focused->next;
		adjust_client_order(c, ORDER_OUT);
		if (next)
			focus_client(next, FOCUS_FORCE);
		else
			focused = NULL;
	} else
		adjust_client_order(c, ORDER_OUT);

	free(c);

	XSync(dpy, False);
	ignore_xerrors--;
	XUngrabServer(dpy);
}

void *
word_wrap_xft(char *str, char delim, XftFont *font, int width, int *nlines)
{
	XGlyphInfo extents;
	struct xft_line_t *lines = NULL;
	char *curstr;
	int x, lastdelim;
	int alloced = 10;
	int nline;

	lines = realloc(lines, alloced * sizeof(struct xft_line_t));
	if (lines == NULL)
		err(1, "realloc");

start_wrap:
	nline = 0;
	lastdelim = -1;
	curstr = str;

	for (x = 0; ; x++) {
		struct xft_line_t *line = &lines[nline];
		int tx;

		if (curstr[x] != delim && curstr[x] != '\n' &&
		    curstr[x] != '\0')
			continue;

		XftTextExtentsUtf8(dpy, font, (FcChar8 *)curstr, x, &extents);

		if (curstr[x] == delim && extents.xOff < width) {
			/* keep eating words */
			lastdelim = x;
			continue;
		}

		if (extents.xOff > width) {
			if (lastdelim == -1) {
				/*
				 * We can't break this long line, make
				 * our label width this wide and start
				 * over, since it may affect previous
				 * wrapping
				 */
				width = extents.xOff;
				goto start_wrap;
			}
			x = lastdelim;
		}

		/* trim leading and trailing spaces */
		tx = x;
		while (curstr[tx - 1] == ' ')
			tx--;
		while (curstr[0] == ' ') {
			curstr++;
			tx--;
		}
		XftTextExtentsUtf8(dpy, font, (FcChar8 *)curstr, tx, &extents);

		line->str = curstr;
		line->len = tx;
		line->xft_width = extents.xOff;

		if (curstr[x] == '\0')
			break;

		curstr = curstr + x + 1;
		x = 0;
		lastdelim = -1;
		nline++;

		if (nline == alloced) {
			alloced += 10;
			lines = realloc(lines,
			    alloced * sizeof(struct xft_line_t));
			if (lines == NULL)
				err(1, "realloc");
		}
	}

	*nlines = nline + 1;
	return lines;
}


================================================
FILE: events.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include "progman.h"
#include "atom.h"

#ifndef INFTIM
#define INFTIM (-1)
#endif

static void handle_button_press(XButtonEvent *);
static void handle_button_release(XButtonEvent *);
static void handle_configure_request(XConfigureRequestEvent *);
static void handle_circulate_request(XCirculateRequestEvent *);
static void handle_map_request(XMapRequestEvent *);
static void handle_destroy_event(XDestroyWindowEvent *);
static void handle_client_message(XClientMessageEvent *);
static void handle_property_change(XPropertyEvent *);
static void handle_enter_event(XCrossingEvent *);
static void handle_cmap_change(XColormapEvent *);
static void handle_expose_event(XExposeEvent *);
static void handle_shape_change(XShapeEvent *);

static XEvent ev;

void
event_loop(void)
{
	struct pollfd pfd[2];

	memset(&pfd, 0, sizeof(pfd));
	pfd[0].fd = ConnectionNumber(dpy);
	pfd[0].events = POLLIN;
	pfd[1].fd = exitmsg[0];
	pfd[1].events = POLLIN;

	for (;;) {
		if (!XPending(dpy)) {
			poll(pfd, 2, INFTIM);
			if (pfd[1].revents)
				/* exitmsg */
				break;

			if (!XPending(dpy))
				continue;
		}

		XNextEvent(dpy, &ev);
#ifdef DEBUG
		show_event(ev);
#endif
		switch (ev.type) {
		case ButtonPress:
			handle_button_press(&ev.xbutton);
			break;
		case ButtonRelease:
			handle_button_release(&ev.xbutton);
			break;
		case ConfigureRequest:
			handle_configure_request(&ev.xconfigurerequest);
			break;
		case CirculateRequest:
			handle_circulate_request(&ev.xcirculaterequest);
			break;
		case MapRequest:
			handle_map_request(&ev.xmaprequest);
			break;
		case UnmapNotify:
			handle_unmap_event(&ev.xunmap);
			break;
		case DestroyNotify:
			handle_destroy_event(&ev.xdestroywindow);
			break;
		case ClientMessage:
			handle_client_message(&ev.xclient);
			break;
		case ColormapNotify:
			handle_cmap_change(&ev.xcolormap);
			break;
		case PropertyNotify:
			handle_property_change(&ev.xproperty);
			break;
		case EnterNotify:
			handle_enter_event(&ev.xcrossing);
			break;
		case Expose:
			handle_expose_event(&ev.xexpose);
			break;
		case KeyPress:
		case KeyRelease:
			handle_key_event(&ev.xkey);
			break;
		default:
			if (shape_support && ev.type == shape_event)
				handle_shape_change((XShapeEvent *)&ev);
		}
	}
}

/*
 * Someone clicked a button. If they clicked on a window, we want the button
 * press, but if they clicked on the root, we're only interested in the button
 * release. Thus, two functions.
 *
 * If it was on the root, we get the click by default. If it's on a window
 * frame, we get it as well.
 *
 * If it's on a client window, it may still fall through to us if the client
 * doesn't select for mouse-click events. The upshot of this is that you should
 * be able to click on the blank part of a GTK window with Button2 to move
 * it.
 */
static void
handle_button_press(XButtonEvent *e)
{
	client_t *c = find_client(e->window, MATCH_ANY);
	int i;

	if (e->window == root) {
		client_t *fc;
		/*
		 * Clicking inside transparent icons may fall through to the
		 * root, so check for an iconified client here
		 */
		if ((fc = find_client_at_coords(e->window, e->x, e->y)) &&
		    (fc->state & STATE_ICONIFIED)) {
			c = fc;
			e->window = c->icon;
		}
	}

	if (c && (c->state & STATE_DOCK)) {
		/* pass button event through */
		XAllowEvents(dpy, ReplayPointer, CurrentTime);
	} else if (c) {
		if (opt_drag_button && e->button == opt_drag_button &&
		    (e->state & opt_drag_mod) &&
		    !(c->state & (STATE_FULLSCREEN | STATE_ZOOMED |
		    STATE_ICONIFIED))) {
			/* alt+click, begin moving */
			focus_client(c, FOCUS_NORMAL);
			move_client(c);
		} else if (find_client(e->window, MATCH_FRAME)) {
			/* raising our frame will also raise the window */
			focus_client(c, FOCUS_NORMAL);
			user_action(c, e->window, e->x, e->y, e->button, 1);
		} else {
			if (e->button == 1)
				focus_client(c, FOCUS_NORMAL);

			/* pass button event through */
			XAllowEvents(dpy, ReplayPointer, CurrentTime);
		}
	} else if (e->window == root) {
		for (i = 0; i < nkey_actions; i++) {
			if (key_actions[i].type == BINDING_TYPE_DESKTOP &&
			    key_actions[i].mod == e->state &&
			    key_actions[i].button == e->button) {
				take_action(&key_actions[i]);
				break;
			}
		}
	}
}

static void
handle_button_release(XButtonEvent *e)
{
	client_t *c = find_client(e->window, MATCH_ANY);

	if (e->window == root) {
		client_t *fc;
		if ((fc = find_client_at_coords(e->window, e->x, e->y)) &&
		    (fc->state & STATE_ICONIFIED)) {
			c = fc;
			e->window = c->icon;
		}
	}

	if (c) {
		if (find_client(e->window, MATCH_FRAME))
			user_action(c, e->window, e->x, e->y, e->button, 0);

		XAllowEvents(dpy, ReplayPointer, CurrentTime);
	}
}

/*
 * Because we are redirecting the root window, we get ConfigureRequest events
 * from both clients we're handling and ones that we aren't. For clients we
 * manage, we need to adjust the frame and the client window, and for unmanaged
 * windows we have to pass along everything unchanged.
 *
 * Most of the assignments here are going to be garbage, but only the ones that
 * are masked in by e->value_mask will be looked at by the X server.
 */
static void
handle_configure_request(XConfigureRequestEvent *e)
{
	client_t *c = NULL;
	XWindowChanges wc;

	if ((c = find_client(e->window, MATCH_WINDOW))) {
		recalc_frame(c);

		if (e->value_mask & CWX)
			c->geom.x = e->x + (c->geom.x - c->frame_geom.x);
		if (e->value_mask & CWY)
			c->geom.y = e->y + (c->geom.y - c->frame_geom.y);
		if (e->value_mask & CWWidth)
			c->geom.w = e->width;
		if (e->value_mask & CWHeight)
			c->geom.h = e->height;

		constrain_frame(c);

		wc.x = c->frame_geom.x;
		wc.y = c->frame_geom.y;
		wc.width = c->frame_geom.w;
		wc.height = c->frame_geom.h;
		wc.border_width = 0;
		wc.sibling = e->above;
		wc.stack_mode = e->detail;
#ifdef DEBUG
		dump_geom(c, c->frame_geom, "moving frame to");
#endif
		XConfigureWindow(dpy, c->frame, e->value_mask, &wc);
		if (e->value_mask & (CWWidth | CWHeight))
			set_shape(c);
		if ((c->state & STATE_ZOOMED) &&
		    (e->value_mask & (CWX | CWY | CWWidth | CWHeight))) {
#ifdef DEBUG
			dump_name(c, __func__, NULL,
			    "unzooming from XConfigureRequest");
#endif
			unzoom_client(c);
		} else {
			redraw_frame(c, None);
			send_config(c);
		}
	}

	if (c) {
		wc.x = c->geom.x - c->frame_geom.x;
		wc.y = c->geom.y - c->frame_geom.y;
		wc.width = c->geom.w;
		wc.height = c->geom.h;
	} else {
		wc.x = e->x;
		wc.y = e->y;
		wc.width = e->width;
		wc.height = e->height;
	}
	wc.sibling = e->above;
	wc.stack_mode = e->detail;
	XConfigureWindow(dpy, e->window, e->value_mask, &wc);

	/* top client may not be the focused one now */
	if ((c = top_client()) && IS_ON_CUR_DESK(c))
		focus_client(c, FOCUS_FORCE);
}

/*
 * The only window that we will circulate children for is the root (because
 * nothing else would make sense). After a client requests that the root's
 * children be circulated, the server will determine which window needs to be
 * raised or lowered, and so all we have to do is make it so.
 */
static void
handle_circulate_request(XCirculateRequestEvent *e)
{
	client_t *c;

	if (e->parent == root) {
		c = find_client(e->window, MATCH_ANY);

		if (e->place == PlaceOnBottom) {
			if (c) {
				adjust_client_order(c, ORDER_BOTTOM);
				if (focused)
					focus_client(focused, FOCUS_FORCE);
			} else
				XLowerWindow(dpy, e->window);
		} else {
			if (c && IS_ON_CUR_DESK(c))
				focus_client(c, FOCUS_FORCE);
			else if (c)
				adjust_client_order(c, ORDER_TOP);
			else
				XRaiseWindow(dpy, e->window);
		}
	}
}

/*
 * Two possibilities if a client is asking to be mapped. One is that it's a new
 * window, so we handle that if it isn't in our clients list anywhere. The
 * other is that it already exists and wants to de-iconify, which is simple to
 * take care of. Since we iconify all of a window's transients when iconifying
 * that window, de-iconify them here.
 */
static void
handle_map_request(XMapRequestEvent *e)
{
	client_t *c, *p;

	if ((c = find_client(e->window, MATCH_WINDOW))) {
		uniconify_client(c);
		for (p = focused; p; p = p->next)
			if (p->trans == c->win)
				uniconify_client(p);
	} else {
		c = new_client(e->window);
		map_client(c);
	}
}

/*
 * We don't get to intercept Unmap events, so this is post mortem. If we caused
 * the unmap ourselves earlier (explictly or by remapping), we will have
 * incremented c->ignore_unmap. If not, time to destroy the client.
 *
 * Because most clients unmap and destroy themselves at once, they're gone
 * before we even get the Unmap event, never mind the Destroy one. Therefore we
 * must be extra careful in del_client.
 */
void
handle_unmap_event(XUnmapEvent *e)
{
	client_t *c;

	if ((c = find_client(e->window, MATCH_WINDOW))) {
		if (c->ignore_unmap)
			c->ignore_unmap--;
		else
			del_client(c, DEL_WITHDRAW);
	}
}

/*
 * But a window can also go away when it's not mapped, in which case there is
 * no Unmap event.
 */
static void
handle_destroy_event(XDestroyWindowEvent *e)
{
	client_t *c;

	if ((c = find_client(e->window, MATCH_WINDOW)))
		del_client(c, DEL_WITHDRAW);
}

static void
handle_client_message(XClientMessageEvent *e)
{
	client_t *c;

	if (e->window == root) {
		if (e->message_type == net_cur_desk && e->format == 32)
			goto_desk(e->data.l[0]);
		else if (e->message_type == net_num_desks && e->format == 32) {
			if (e->data.l[0] < ndesks)
				/* TODO: move clients from deleted desks */
				return;
			ndesks = e->data.l[0];
		}
		return;
	}

	c = find_client(e->window, MATCH_WINDOW);
	if (!c)
		return;
	if (e->format != 32)
		return;

	if (e->message_type == wm_change_state && e->data.l[0] == IconicState)
		iconify_client(c);
	else if (e->message_type == net_close_window)
		send_wm_delete(c);
	else if (e->message_type == net_active_window) {
		c->desk = cur_desk;
		map_if_desk(c);
		if (c->state == STATE_ICONIFIED)
			uniconify_client(c);
		focus_client(c, FOCUS_NORMAL);
	} else if (e->message_type == net_wm_state &&
	    e->data.l[1] == net_wm_state_fs) {
		if (e->data.l[0] == net_wm_state_add ||
		    (e->data.l[0] == net_wm_state_toggle &&
		    c->state != STATE_FULLSCREEN))
			fullscreen_client(c);
		else
			unfullscreen_client(c);
	}
}

/*
 * If we have something copied to a variable, or displayed on the screen, make
 * sure it is up to date. If redrawing the name is necessary, clear the window
 * because Xft uses alpha rendering.
 */
static void
handle_property_change(XPropertyEvent *e)
{
	client_t *c;
#ifdef DEBUG
	char *atom;
#endif

	if (!(c = find_client(e->window, MATCH_WINDOW)))
		return;

#ifdef DEBUG
	atom = XGetAtomName(dpy, e->atom);
	dump_name(c, __func__, "", atom);
	XFree(atom);
#endif

	if (e->atom == XA_WM_NAME || e->atom == net_wm_name) {
		if (c->name)
			XFree(c->name);
		c->name = get_wm_name(c->win);
		if (c->frame_style & FRAME_TITLEBAR)
			redraw_frame(c, c->titlebar);
	} else if (e->atom == XA_WM_ICON_NAME || e->atom == net_wm_icon_name) {
		if (c->icon_name)
			XFree(c->icon_name);
		c->icon_name = get_wm_icon_name(c->win);
		if (c->state & STATE_ICONIFIED)
			redraw_icon(c, c->icon_label);
	} else if (e->atom == XA_WM_NORMAL_HINTS) {
		update_size_hints(c);
		fix_size(c);
		redraw_frame(c, None);
		send_config(c);
	} else if (e->atom == XA_WM_HINTS) {
		if (c->wm_hints)
			XFree(c->wm_hints);
		c->wm_hints = XGetWMHints(dpy, c->win);
		if (c->wm_hints &&
		    c->wm_hints->flags & (IconPixmapHint | IconMaskHint)) {
			get_client_icon(c);
			if (c->state & STATE_ICONIFIED)
				redraw_icon(c, c->icon);
		}
	} else if (e->atom == net_wm_state || e->atom == wm_state) {
		int was_state = c->state;
		check_states(c);
		if (was_state != c->state) {
			if (c->state & STATE_ICONIFIED)
				iconify_client(c);
			else if (c->state & STATE_ZOOMED)
				zoom_client(c);
			else if (c->state & STATE_FULLSCREEN)
				fullscreen_client(c);
			else {
				if (was_state & STATE_ZOOMED)
					unzoom_client(c);
				else if (was_state & STATE_ICONIFIED)
					uniconify_client(c);
				else if (was_state & STATE_FULLSCREEN)
					unfullscreen_client(c);
			}
		}
	} else if (e->atom == net_wm_desk) {
		if (get_atoms(c->win, net_wm_desk, XA_CARDINAL, 0,
			&c->desk, 1, NULL)) {
			if (c->desk == -1)
				c->desk = DESK_ALL;	/* FIXME */
			map_if_desk(c);
		}
	}
#ifdef DEBUG
	else {
		printf("%s: unknown atom %ld (%s)\n", __func__, (long)e->atom,
		    XGetAtomName(dpy, e->atom));
	}
#endif
}

/* Support click-to-focus policy. */
static void
handle_enter_event(XCrossingEvent *e)
{
	client_t *c;

	if ((c = find_client(e->window, MATCH_FRAME)))
		XGrabButton(dpy, Button1, AnyModifier, c->win, False,
		    ButtonMask, GrabModeSync, GrabModeSync, None, None);
}

/*
 * Colormap policy: when a client installs a new colormap on itself, set the
 * display's colormap to that. We do this even if it's not focused.
 */
static void
handle_cmap_change(XColormapEvent *e)
{
	client_t *c;

	if ((c = find_client(e->window, MATCH_WINDOW)) && e->new) {
		c->cmap = e->colormap;
		XInstallColormap(dpy, c->cmap);
	}
}

/*
 * If we were covered by multiple windows, we will usually get multiple expose
 * events, so ignore them unless e->count (the number of outstanding exposes)
 * is zero.
 */
static void
handle_expose_event(XExposeEvent *e)
{
	client_t *c;

	if ((c = find_client(e->window, MATCH_FRAME)) && e->count == 0)
		redraw_frame(c, e->window);
}

static void
handle_shape_change(XShapeEvent *e)
{
	client_t *c;

	if ((c = find_client(e->window, MATCH_WINDOW)))
		set_shape(c);
}

#ifdef DEBUG
void
show_event(XEvent e)
{
	char ev_type[128];
	Window w;
	client_t *c;

	switch (e.type) {
	SHOW_EV(ButtonPress, xbutton)
	SHOW_EV(ButtonRelease, xbutton)
	SHOW_EV(ClientMessage, xclient)
	SHOW_EV(ColormapNotify, xcolormap)
	SHOW_EV(ConfigureNotify, xconfigure)
	SHOW_EV(ConfigureRequest, xconfigurerequest)
	SHOW_EV(CirculateRequest, xcirculaterequest)
	SHOW_EV(CreateNotify, xcreatewindow)
	SHOW_EV(DestroyNotify, xdestroywindow)
	SHOW_EV(EnterNotify, xcrossing)
	SHOW_EV(Expose, xexpose)
	SHOW_EV(KeyPress, xkey)
	SHOW_EV(KeyRelease, xkey)
	SHOW_EV(NoExpose, xexpose)
	SHOW_EV(MapNotify, xmap)
	SHOW_EV(MapRequest, xmaprequest)
	SHOW_EV(MappingNotify, xmapping)
	SHOW_EV(PropertyNotify, xproperty)
	SHOW_EV(ReparentNotify, xreparent)
	SHOW_EV(ResizeRequest, xresizerequest)
	SHOW_EV(UnmapNotify, xunmap)
	SHOW_EV(MotionNotify, xmotion)
	default:
		if (shape_support && e.type == shape_event) {
			snprintf(ev_type, sizeof(ev_type), "ShapeNotify");
			w = ((XShapeEvent *) & e)->window;
			break;
		}
		snprintf(ev_type, sizeof(ev_type), "unknown event %d", e.type);
		w = None;
		break;
	}

	if ((c = find_client(w, MATCH_WINDOW)))
		dump_name(c, ev_type, "window", c->name);
	else if ((c = find_client(w, MATCH_FRAME))) {
		/*
		 * ConfigureNotify can only come from us (otherwise it'd be a
		 * ConfigureRequest) and NoExpose events are just not useful
		 */
		if (e.type != ConfigureNotify && e.type != NoExpose)
			dump_name(c, ev_type, frame_name(c, w), c->name);
	} else if (w == root)
		dump_name(NULL, ev_type, "root", "(root)");
}
#endif


================================================
FILE: icons/close.xpm
================================================
/* XPM */
static char * close_xpm[] = {
"14 14 4 1",
" 	c None",
".	c #000000",
"+	c #FFFFFF",
"@	c #87888F",
"              ",
"              ",
"              ",
"              ",
"              ",
"............. ",
".+++++++++++.@",
".............@",
" @@@@@@@@@@@@@",
"              ",
"              ",
"              ",
"              ",
"              "};


================================================
FILE: icons/default_icon.xpm
================================================
/* XPM */
static char * default_icon_xpm[] = {
"32 32 6 1",
" 	c None",
".	c #000000",
"+	c #808080",
"@	c #C0C0C0",
"#	c #00FFFF",
"$	c #FFFFFF",
"                                ",
"                                ",
"                                ",
"                                ",
"                                ",
"                                ",
"     .                          ",
"     .....                      ",
"     .####....                  ",
"     .....####....              ",
"     .++++....####.....         ",
"     .$$$$++++....####.         ",
"     .$$$$$$$$++++.....         ",
"     .$$$$$$$$$$$$++++.         ",
"     .$$$$$$$$$$$$$$$$.         ",
"     .$$$$$$$$$$$$$$$$.         ",
"     .$$$$$$$$$$$$$$$$.         ",
"     .$$$$$$$$$$$$$$$$.         ",
"     .$$$$$$$$$$$$$$$$.         ",
"     .$$$$$$$$$$$$$$$$.         ",
"     .$$$$$$$$$$$$$$$$.         ",
"     .$$$$$$$$$$$$$$$$.@@       ",
"     .$$$$$$$$$$$$$$$$.@@@@     ",
"     ....+$$$$$$$$$$$$.@@@@@@   ",
"         .....+$$$$$$$.@@@@     ",
"              ....+$$$.@@       ",
"                  .....         ",
"                                ",
"                                ",
"                                ",
"                                ",
"                                "};


================================================
FILE: icons/hidpi-close.xpm
================================================
/* XPM */
static char * hidpi_close_xpm[] = {
"22 7 4 1",
" 	c None",
".	c #000000",
"+	c #FFFFFF",
"@	c #87888F",
"....................  ",
".++++++++++++++++++.@@",
".++++++++++++++++++.@@",
".++++++++++++++++++.@@",
"....................@@",
"  @@@@@@@@@@@@@@@@@@@@",
"  @@@@@@@@@@@@@@@@@@@@"};


================================================
FILE: icons/hidpi-default_icon.xpm
================================================
/* XPM */
static char * hidpi_default_icon_xpm[] = {
"64 64 6 1",
" 	c None",
".	c #000000",
"+	c #00FFFF",
"@	c #808080",
"#	c #FFFFFF",
"$	c #C0C0C0",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"          ..                                                    ",
"          ..                                                    ",
"          ..........                                            ",
"          ..........                                            ",
"          ..++++++++........                                    ",
"          ..++++++++........                                    ",
"          ..........++++++++........                            ",
"          ..........++++++++........                            ",
"          ..@@@@@@@@........++++++++..........                  ",
"          ..@@@@@@@@........++++++++..........                  ",
"          ..########@@@@@@@@........++++++++..                  ",
"          ..########@@@@@@@@........++++++++..                  ",
"          ..################@@@@@@@@..........                  ",
"          ..################@@@@@@@@..........                  ",
"          ..########################@@@@@@@@..                  ",
"          ..########################@@@@@@@@..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..                  ",
"          ..################################..$$$$              ",
"          ..################################..$$$$              ",
"          ..################################..$$$$$$$$          ",
"          ..################################..$$$$$$$$          ",
"          ........@@########################..$$$$$$$$$$$$      ",
"          ........@@########################..$$$$$$$$$$$$      ",
"                  ..........@@##############..$$$$$$$$          ",
"                  ..........@@##############..$$$$$$$$          ",
"                            ........@@######..$$$$              ",
"                            ........@@######..$$$$              ",
"                                    ..........                  ",
"                                    ..........                  ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                ",
"                                                                "};


================================================
FILE: icons/hidpi-iconify.xpm
================================================
/* XPM */
static char * hidpi_iconify_xpm[] = {
"13 9 2 1",
" 	c None",
".	c #000000",
"             ",
"             ",
".............",
" ........... ",
"  .........  ",
"   .......   ",
"    .....    ",
"     ...     ",
"      .      "};


================================================
FILE: icons/hidpi-unzoom.xpm
================================================
/* XPM */
static char * hidpi_unzoom_xpm[] = {
"13 18 2 1",
" 	c None",
".	c #000000",
"      .      ",
"     ...     ",
"    .....    ",
"   .......   ",
"  .........  ",
" ........... ",
".............",
"             ",
"             ",
"             ",
"             ",
".............",
" ........... ",
"  .........  ",
"   .......   ",
"    .....    ",
"     ...     ",
"      .      "};


================================================
FILE: icons/hidpi-utility_close.xpm
================================================
/* XPM */
static char * hidpi_utility_close_xpm[] = {
"14 14 4 1",
" 	c None",
".	c #000000",
"+	c #FFFFFF",
"@	c #87888F",
"              ",
"              ",
"              ",
"              ",
"              ",
"  ..........  ",
"  .++++++++.@@",
"  .++++++++.@@",
"  ..........@@",
"    @@@@@@@@@@",
"    @@@@@@@@@@",
"              ",
"              ",
"              "};


================================================
FILE: icons/hidpi-zoom.xpm
================================================
/* XPM */
static char * hidpi_zoom_xpm[] = {
"13 9 2 1",
" 	c None",
".	c #000000",
"      .      ",
"     ...     ",
"    .....    ",
"   .......   ",
"  .........  ",
" ........... ",
".............",
"             ",
"             "};


================================================
FILE: icons/iconify.xpm
================================================
/* XPM */
static char * iconify_xpm[] = {
"9 6 2 1",
" 	c None",
".	c #000000",
"         ",
".........",
" ....... ",
"  .....  ",
"   ...   ",
"    .    "};


================================================
FILE: icons/unzoom.xpm
================================================
/* XPM */
static char * unzoom_xpm[] = {
"9 12 2 1",
" 	c None",
".	c #000000",
"    .    ",
"   ...   ",
"  .....  ",
" ....... ",
".........",
"         ",
"         ",
".........",
" ....... ",
"  .....  ",
"   ...   ",
"    .    "};


================================================
FILE: icons/utility_close.xpm
================================================
/* XPM */
static char * utility_close_xpm[] = {
"7 7 4 1",
" 	c None",
".	c #000000",
"+	c #FFFFFF",
"@	c #87888F",
"       ",
"       ",
"...... ",
".++++.@",
"......@",
" @@@@@@",
"       "};


================================================
FILE: icons/zoom.xpm
================================================
/* XPM */
static char * zoom_xpm[] = {
"9 6 2 1",
" 	c None",
".	c #000000",
"    .    ",
"   ...   ",
"  .....  ",
" ....... ",
".........",
"         "};


================================================
FILE: keyboard.c
================================================
/*
 * Copyright (c) 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <strings.h>
#include "progman.h"

action_t *key_actions = NULL;
int nkey_actions = 0;
static int cycle_key = 0;

action_t *
bind_key(int type, char *key, char *action)
{
	char *tkey, *sep;
	KeySym k = 0;
	action_t *taction;
	int x, mod = 0, iaction = -1, button = 0, overwrite, aidx;

	tkey = strdup(key);

	/* key can be "shift+alt+f1" or "Super+Space" or just "ampersand" */
	while ((sep = strchr(tkey, '+'))) {
		*sep = '\0';
		if (strcasecmp(tkey, "shift") == 0)
			mod |= ShiftMask;
		else if (strcasecmp(tkey, "control") == 0 ||
		    strcasecmp(tkey, "ctrl") == 0 ||
		    strcasecmp(tkey, "ctl") == 0)
			mod |= ControlMask;
		else if (strcasecmp(tkey, "alt") == 0 ||
		    strcasecmp(tkey, "meta") == 0 ||
		    strcasecmp(tkey, "mod1") == 0)
			mod |= Mod1Mask;
		else if (strcasecmp(tkey, "mod2") == 0)
			mod |= Mod2Mask;
		else if (strcasecmp(tkey, "mod3") == 0)
			mod |= Mod3Mask;
		else if (strcasecmp(tkey, "super") == 0 ||
		    strcasecmp(tkey, "win") == 0 ||
		    strcasecmp(tkey, "mod4") == 0)
			mod |= Mod4Mask;
		else {
			warnx("failed parsing modifier \"%s\" in \"%s\", "
			    "skipping", tkey, key);
			return NULL;
		}

		tkey = sep + 1;
	}

	/* modifiers have been parsed, only the key or button should remain */
	if (strncasecmp(tkey, "mouse", 5) == 0 &&
	    tkey[5] > '0' && tkey[5] <= '9')
		button = tkey[5] - '0';
	else if (tkey[0] != '\0') {
		if (tkey[1] == '\0') {
			/*
			 * Assume a single-character key is meant to be used as
			 * its lower-case key, e.g., "Win+T" is mod4+t, not
			 * mod4+T, and if the user wanted it a capital t, they
			 * would specify it as "Win+Shift+T"
			 */
			tkey[0] = tolower(tkey[0]);
		}

		k = XStringToKeysym(tkey);
		if (k == 0) {
			warnx("failed parsing key \"%s\", skipping\n", tkey);
			return NULL;
		}
	}

	/* action can be e.g., "cycle" or "exec xterm -g 80x50" */
	taction = parse_action(key, action);
	if (taction == NULL)
		return NULL;

	/* if we're overriding an existing config, replace it in key_actions */
	overwrite = 0;
	for (x = 0; x < nkey_actions; x++) {
		if (key_actions[x].type == type &&
		    key_actions[x].key == k &&
		    key_actions[x].mod == mod &&
		    key_actions[x].button == button) {
			overwrite = 1;
			aidx = x;
			break;
		}
	}

	if (!overwrite) {
		key_actions = realloc(key_actions,
		    (nkey_actions + 1) * sizeof(action_t));
		if (key_actions == NULL)
			err(1, "realloc");

		aidx = nkey_actions;
	}

	if (taction->action == ACTION_NONE) {
		key_actions[aidx].key = -1;
		key_actions[aidx].mod = -1;
		key_actions[aidx].button = 0;
	} else {
		key_actions[aidx].key = k;
		key_actions[aidx].mod = mod;
		key_actions[aidx].button = button;
	}
	key_actions[aidx].type = type;
	key_actions[aidx].action = taction->action;
	key_actions[aidx].iarg = taction->iarg;
	if (overwrite && key_actions[aidx].sarg)
		free(key_actions[aidx].sarg);
	key_actions[aidx].sarg = taction->sarg;

	/* retain taction->sarg */
	free(taction);

	if (!overwrite)
		nkey_actions++;

#ifdef DEBUG
	if (key_actions[aidx].action == ACTION_NONE)
		printf("%s(%s): unbinding key %ld/button %d with "
		    "mod mask 0x%x\n", __func__, key, k, button, mod);
	else
		printf("%s(%s): binding key %ld/button %d with mod mask 0x%x "
		    "to action \"%s\"\n", __func__, key, k, button, mod,
		    action);
#endif

	if (type == BINDING_TYPE_KEYBOARD) {
		if (overwrite && iaction == ACTION_NONE)
			XUngrabKey(dpy, XKeysymToKeycode(dpy, k), mod, root);
		else if (!overwrite)
			XGrabKey(dpy, XKeysymToKeycode(dpy, k), mod, root,
			    False, GrabModeAsync, GrabModeAsync);
	}

	return &key_actions[aidx];
}

void
handle_key_event(XKeyEvent *e)
{
#ifdef DEBUG
	char buf[64];
#endif
	KeySym kc;
	action_t *action = NULL;
	int i;

	kc = XLookupKeysym(e, 0);

#ifdef DEBUG
	snprintf(buf, sizeof(buf), "%c:%ld", e->type == KeyRelease ? 'U' : 'D',
	    kc);
	dump_name(focused, __func__, buf, NULL);
#endif

	if (cycle_key && kc != cycle_key && e->type == KeyRelease) {
		/*
		 * If any key other than the non-modifier(s) of our cycle
		 * binding was released, consider the cycle over.
		 */
		cycle_key = 0;
		XUngrabKeyboard(dpy, CurrentTime);
		XAllowEvents(dpy, ReplayKeyboard, e->time);
		XFlush(dpy);

		if (cycle_head) {
			cycle_head = NULL;
			if (focused && focused->state & STATE_ICONIFIED)
				uniconify_client(focused);
		}

		return;
	}

	for (i = 0; i < nkey_actions; i++) {
		if (key_actions[i].type == BINDING_TYPE_KEYBOARD &&
		    key_actions[i].key == kc &&
		    key_actions[i].mod == e->state) {
			action = &key_actions[i];
			break;
		}
	}

	if (!action)
		return;

	if (e->type != KeyPress)
		return;

	switch (key_actions[i].action) {
	case ACTION_CYCLE:
	case ACTION_REVERSE_CYCLE:
		/*
		 * Keep watching input until the modifier is released, but the
		 * keycode will be the modifier key
		 */
		XGrabKeyboard(dpy, root, False, GrabModeAsync, GrabModeAsync,
		    e->time);
		cycle_key = key_actions[i].key;
		take_action(&key_actions[i]);
		break;
	default:
		take_action(&key_actions[i]);
	}
}


================================================
FILE: launcher.c
================================================
/*
 * Copyright (c) 2021 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#include <err.h>
#include "parser.h"
#include "progman.h"

struct program {
	char *name;
	action_t *action;
	struct program *next;
};

Window launcher_win;
XftDraw *launcher_xftdraw;
struct program *program_head = NULL, *program_tail = NULL;
int launcher_width = 0, launcher_height = 0, launcher_highlighted = 0,
    launcher_item_height = 0, launcher_item_padding = 0;

void launcher_reload(void);
void launcher_redraw(void);

void
launcher_setup(void)
{
	XTextProperty name;
	char *title = "Programs";
	XSizeHints *hints;

	launcher_reload();

	launcher_win = XCreateWindow(dpy, root, 0, 0, launcher_width,
	    launcher_height, 0, DefaultDepth(dpy, screen), CopyFromParent,
	    DefaultVisual(dpy, screen), 0, NULL);
	if (!launcher_win)
		err(1, "XCreateWindow");

	hints = XAllocSizeHints();
	if (!hints)
		err(1, "XAllocSizeHints");

	hints->flags = PMinSize | PMaxSize;
	hints->min_width = launcher_width;
	hints->min_height = launcher_height;
	hints->max_width = launcher_width;
	hints->max_height = launcher_height;

	XSetWMNormalHints(dpy, launcher_win, hints);

	XFree(hints);

	if (!XStringListToTextProperty(&title, 1, &name))
		err(1, "XStringListToTextProperty");
	XSetWMName(dpy, launcher_win, &name);

	XSetWindowBackground(dpy, launcher_win, launcher_bg.pixel);

	set_atoms(launcher_win, net_wm_state, XA_ATOM, &net_wm_state_above, 1);
	set_atoms(launcher_win, net_wm_wintype, XA_ATOM, &net_wm_type_utility,
	    1);

	launcher_xftdraw = XftDrawCreate(dpy, launcher_win,
	    DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
}

void
launcher_reload(void)
{
	FILE *ini;
	struct program *program = NULL;
	XGlyphInfo extents;
	action_t *action;
	char *key, *val;
	int tw;

	launcher_programs_free();

	launcher_width = 0;
	launcher_height = 0;
	launcher_item_padding = opt_pad * 2;
	launcher_item_height = font->ascent + (launcher_item_padding * 2);

	ini = open_ini(opt_config_file);

	if (!find_ini_section(ini, "launcher"))
		goto done;

	while (get_ini_kv(ini, &key, &val)) {
		action = parse_action(key, val);
		if (action == NULL)
			continue;

		program = malloc(sizeof(struct program));
		if (!program)
			err(1, "malloc");

		program->next = NULL;

		if (program_tail) {
			program_tail->next = program;
			program_tail = program;
		} else {
			program_head = program;
			program_tail = program;
		}

		XftTextExtentsUtf8(dpy, font, (FcChar8 *)key, strlen(key),
		    &extents);
		tw = extents.xOff + (launcher_item_padding * 2);
		if (tw > launcher_width)
			launcher_width = tw;
		launcher_height += launcher_item_height;

		program->name = strdup(key);
		program->action = action;

		free(key);
		free(val);
	}

done:
	fclose(ini);
}

void
launcher_show(XButtonEvent *e)
{
	client_t *c;
	XEvent ev;
	struct program *program;
	int x, y, mx, my, prev_highlighted;

	if (e) {
		x = e->x_root;
		y = e->y_root;
	} else
		get_pointer(&x, &y);

	XMoveResizeWindow(dpy, launcher_win, x, y, launcher_width,
	    launcher_height);

	c = new_client(launcher_win);
	c->placed = 1;
	c->desk = cur_desk;
	map_client(c);
	map_if_desk(c);

	x = c->geom.x;
	y = c->geom.y;

	launcher_highlighted = prev_highlighted = -1;
	launcher_redraw();

	/*
	 * If we launched from a mouse button down event, grab the pointer to
	 * watch for it to be released, otherwise we launched from the keyboard
	 * and we'll dismiss on click
	 */
	if (XGrabPointer(dpy, root, False, MouseMask, GrabModeAsync,
	    GrabModeAsync, root, None, CurrentTime) != GrabSuccess) {
		warnx("failed grabbing pointer");
		goto close_launcher;
	}

	for (;;) {
		XMaskEvent(dpy, PointerMotionMask | ButtonPressMask |
		    ButtonReleaseMask, &ev);

		switch (ev.type) {
		case MotionNotify: {
			XMotionEvent *xmv = (XMotionEvent *)&ev;
			mx = xmv->x - x;
			my = xmv->y - y;

			if (mx < 0 || mx > launcher_width ||
			    my < 0 || my > launcher_height)
				launcher_highlighted = -1;
			else
				launcher_highlighted = (my /
				    launcher_item_height);

			if (launcher_highlighted != prev_highlighted)
				launcher_redraw();

			prev_highlighted = launcher_highlighted;
			break;
		}
		case ButtonPress:
			break;
		case ButtonRelease:
			goto close_launcher;
			break;
		}
	}

close_launcher:
	XUngrabPointer(dpy, CurrentTime);
	XUnmapWindow(dpy, launcher_win);
	del_client(c, DEL_WITHDRAW);

	if (launcher_highlighted < 0)
		return;

	for (x = 0, program = program_head; program;
	    program = program->next, x++) {
		if (x != launcher_highlighted)
			continue;

		take_action(program->action);
		break;
	}
}

void
launcher_programs_free(void)
{
	struct program *program;

	for (program = program_head; program;) {
		struct program *t = program;

		if (program->name)
			free(program->name);
		if (program->action) {
			if (program->action->sarg)
				free(program->action->sarg);
			free(program->action);
		}
		program = program->next;
		free(t);
	}

	program_head = program_tail = NULL;
}

void
launcher_redraw(void)
{
	struct program *program;
	XftColor *color;
	int i;

	XClearWindow(dpy, launcher_win);

	for (i = 0, program = program_head; program;
	    program = program->next, i++) {
		if (launcher_highlighted == i) {
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    launcher_fg.pixel);
			XFillRectangle(dpy, launcher_win,
			    DefaultGC(dpy, screen),
			    0, launcher_item_height * i,
			    launcher_width, launcher_item_height);
			color = &xft_launcher_highlighted;
		} else {
			XSetForeground(dpy, DefaultGC(dpy, screen),
			    launcher_fg.pixel);
			color = &xft_launcher;
		}


		XftDrawStringUtf8(launcher_xftdraw, color, font,
		    launcher_item_padding,
		    (launcher_item_height * (i + 1)) - launcher_item_padding,
		    (unsigned char *)program->name,
		    strlen(program->name));
	}
}


================================================
FILE: manage.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include "progman.h"
#include "atom.h"

static void do_iconify(client_t *);
static void do_shade(client_t *);
static void maybe_toolbar_click(client_t *, Window);
static void monitor_toolbar_click(client_t *, geom_t, int, int, int, int,
    strut_t *, void *);

static struct {
	struct timespec tv;
	client_t *c;
	Window win;
	int button;
} last_click = { { 0, 0 }, NULL, 0 };

void
user_action(client_t *c, Window win, int x, int y, int button, int down)
{
	struct timespec now;
	long long tdiff;
	int double_click = 0;

	if (!down) {
		clock_gettime(CLOCK_MONOTONIC, &now);

		if (last_click.button == button && c == last_click.c &&
		    win == last_click.win) {
			tdiff = (((now.tv_sec * 1000000000) + now.tv_nsec) -
			    ((last_click.tv.tv_sec * 1000000000) +
			    last_click.tv.tv_nsec)) / 1000000;
			if (tdiff <= DOUBLE_CLICK_MSEC)
				double_click = 1;
		}

		last_click.button = button;
		last_click.c = c;
		last_click.win = win;
		memcpy(&last_click.tv, &now, sizeof(now));
	}

#ifdef DEBUG
	printf("%s(\"%s\", %lx, %d, %d, %d, %d) double:%d, c state %d\n",
	    __func__, c->name, win, x, y, button, down, double_click, c->state);
	dump_info(c);
#endif

	if (c->state & STATE_ICONIFIED &&
	    (win == c->icon || win == c->icon_label)) {
	    	if (down && !double_click) {
			focus_client(c, FOCUS_NORMAL);
			move_client(c);
			get_pointer(&x, &y);
			user_action(c, win, x, y, button, 0);
		} else if (!down && double_click)
			uniconify_client(c);
	} else if (win == c->titlebar) {
		if (button == 1 && down) {
			if (!(c->state & (STATE_ZOOMED | STATE_FULLSCREEN))) {
				move_client(c);
				/* sweep() eats the ButtonRelease event */
				get_pointer(&x, &y);
				user_action(c, win, x, y, button, 0);
			}
		} else if (button == 1 && !down && double_click) {
			if (c->state & STATE_ZOOMED)
				unzoom_client(c);
			else
				zoom_client(c);
		} else if (button == 3 && !down) {
			if (c->state & STATE_SHADED)
				unshade_client(c);
			else
				shade_client(c);
		}
	} else if (win == c->close) {
		if (button == 1 && down) {
			maybe_toolbar_click(c, win);
			if (!c->close_pressed)
				return;

			c->close_pressed = False;
			redraw_frame(c, c->close);

			get_pointer(&x, &y);
			user_action(c, win, x, y, button, 0);
		}

		if (double_click)
			send_wm_delete(c);
	} else if (IS_RESIZE_WIN(c, win)) {
		if (button == 1 && down && !(c->state & STATE_SHADED))
			resize_client(c, win);
	} else if (win == c->iconify) {
		if (button == 1 && down) {
			maybe_toolbar_click(c, win);
			if (c->iconify_pressed) {
				c->iconify_pressed = False;
				redraw_frame(c, c->iconify);
				if (c->state & STATE_ICONIFIED)
					uniconify_client(c);
				else
					iconify_client(c);
			}
		}
	} else if (win == c->zoom) {
		if (button == 1 && down) {
			maybe_toolbar_click(c, win);
			if (c->zoom_pressed) {
				c->zoom_pressed = False;
				redraw_frame(c, c->zoom);
				if (c->state & STATE_ZOOMED)
					unzoom_client(c);
				else
					zoom_client(c);
			}
		}
	}

	if (double_click)
		/* don't let a 3rd click keep counting as a double click */
		last_click.tv.tv_sec = last_click.tv.tv_nsec = 0;
}

Cursor
cursor_for_resize_win(client_t *c, Window win)
{
	if (win == c->resize_nw)
		return resize_nw_curs;
	else if (win == c->resize_w)
		return resize_w_curs;
	else if (win == c->resize_sw)
		return resize_sw_curs;
	else if (win == c->resize_s)
		return resize_s_curs;
	else if (win == c->resize_se)
		return resize_se_curs;
	else if (win == c->resize_e)
		return resize_e_curs;
	else if (win == c->resize_ne)
		return resize_ne_curs;
	else if (win == c->resize_n)
		return resize_n_curs;
	else
		return None;
}

/* This can't do anything dangerous. */
void
focus_client(client_t *c, int style)
{
	client_t *prevfocused = NULL;
	client_t *trans[10] = { NULL };
	client_t *p;
	int transcount = 0;

	if (!c) {
		warnx("%s with no c", __func__);
		abort();
	}

	if (focused == c && style != FOCUS_FORCE)
		return;

#ifdef DEBUG
	dump_name(c, __func__, NULL, c->name);
#endif

	if (c->state & STATE_ICONIFIED) {
		set_atoms(root, net_active_window, XA_WINDOW, &c->icon, 1);
		XSetInputFocus(dpy, c->icon, RevertToPointerRoot, CurrentTime);
	} else {
		set_atoms(root, net_active_window, XA_WINDOW, &c->win, 1);
		XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
		XInstallColormap(dpy, c->cmap);
	}

	if (focused && focused != c) {
		prevfocused = focused;

		if (focused->state & STATE_ICONIFIED)
			adjust_client_order(focused,
			    ORDER_ICONIFIED_TOP);
		else
			adjust_client_order(focused, ORDER_TOP);
	}

	adjust_client_order(c, ORDER_TOP);

	/* raise any transients of this window */
	for (p = focused; p; p = p->next) {
		if (p->trans == c->win) {
			trans[transcount++] = p;
			if (transcount == sizeof(trans) / (sizeof(trans[0])))
				break;
		}
	}
	if (transcount != 0) {
		for (transcount--; transcount >= 0; transcount--) {
#ifdef DEBUG
			dump_name(c, __func__, "transient",
			    trans[transcount]->name);
#endif
			adjust_client_order(trans[transcount], ORDER_TOP);
		}
	}

	restack_clients();

	if (prevfocused)
		redraw_frame(prevfocused, None);

	redraw_frame(c, None);
}

void
move_client(client_t *c)
{
	strut_t s = { 0 };

	if (c->state & (STATE_ZOOMED | STATE_FULLSCREEN | STATE_DOCK))
		return;

	collect_struts(c, &s);
	dragging = c;
	sweep(c, move_curs, recalc_move, NULL, &s);
	dragging = NULL;

	if (!(c->state & STATE_ICONIFIED))
		redraw_frame(c, None);

	send_config(c);
	flush_expose_client(c);
}

/*
 * If we are resizing a client that was zoomed, we have to put it in an
 * unzoomed state, but we need to start sweeping from the effective geometry
 * rather than the "real" geometry that unzooming will restore. We get around
 * this by blatantly cheating.
 */
void
resize_client(client_t *c, Window resize_win)
{
	strut_t hold = { 0, 0, 0, 0 };
	XEvent junk;

	if (c->state & STATE_ZOOMED) {
		c->save = c->geom;
		unzoom_client(c);
	}

	sweep(c, cursor_for_resize_win(c, resize_win), recalc_resize,
	    &resize_win, &hold);

	if (c->shaped) {
		/* flush ShapeNotify events */
		while (XCheckTypedWindowEvent(dpy, c->win, shape_event, &junk))
			;
	}
}

/*
 * The user has clicked on a toolbar button but may mouse off of it and then
 * let go, so only consider it a click if the mouse is still there when the
 * mouse button is released.
 */
void
maybe_toolbar_click(client_t *c, Window win)
{
	if (win == c->iconify)
		c->iconify_pressed = True;
	else if (win == c->zoom)
		c->zoom_pressed = True;
	else if (win == c->close)
		c->close_pressed = True;
	else
		return;

	redraw_frame(c, win);
	sweep(c, None, monitor_toolbar_click, &win, NULL);
	redraw_frame(c, win);
}

void
monitor_toolbar_click(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,
    strut_t *s, void *arg)
{
	Window win = *(Window *)arg;
	geom_t *geom;
	Bool was, *pr;

	if (win == c->iconify) {
		geom = &c->iconify_geom;
		pr = &c->iconify_pressed;
	} else if (win == c->zoom) {
		geom = &c->zoom_geom;
		pr = &c->zoom_pressed;
	} else if (win == c->close) {
		geom = &c->close_geom;
		pr = &c->close_pressed;
	} else
		return;

	was = *pr;

	if (x1 >= (c->frame_geom.x + geom->x) &&
	    x1 <= (c->frame_geom.x + geom->x + geom->w) &&
	    y1 >= (c->frame_geom.y + geom->y) &&
	    y1 <= (c->frame_geom.y + geom->y + geom->h))
		*pr = True;
	else
		*pr = False;

	if (was != *pr)
		redraw_frame(c, win);
}


/* Transients will be iconified when their owner is iconified. */
void
iconify_client(client_t *c)
{
	client_t *p;

	for (p = focused; p; p = p->next)
		if (p->trans == c->win)
			do_iconify(p);

	do_iconify(c);

	focus_client(focused, FOCUS_FORCE);
}

void
do_iconify(client_t *c)
{
	XSetWindowAttributes attrs = { 0 };
	XGCValues gv;

	adjust_client_order(c, ORDER_ICONIFIED_TOP);

	if (!c->ignore_unmap)
		c->ignore_unmap++;
	XUnmapWindow(dpy, c->frame);
	XUnmapWindow(dpy, c->win);
	c->state |= STATE_ICONIFIED;
	set_wm_state(c, IconicState);

	get_client_icon(c);

	if (c->icon_name)
		XFree(c->icon_name);
	c->icon_name = get_wm_icon_name(c->win);

	if (c->icon_geom.w < 1)
		c->icon_geom.w = icon_size;
	if (c->icon_geom.h < 1)
		c->icon_geom.h = icon_size;

	attrs.background_pixel = BlackPixel(dpy, screen);
	attrs.event_mask = ButtonPressMask | ButtonReleaseMask |
	    VisibilityChangeMask | ExposureMask | KeyPressMask |
	    EnterWindowMask | FocusChangeMask;

	place_icon(c);

	c->icon = XCreateWindow(dpy, root, c->icon_geom.x, c->icon_geom.h,
	    c->icon_geom.w, c->icon_geom.h, 0, CopyFromParent, CopyFromParent,
	    CopyFromParent, CWBackPixel | CWEventMask, &attrs);
	set_atoms(c->icon, net_wm_wintype, XA_ATOM, &net_wm_type_desk, 1);
	XMapWindow(dpy, c->icon);

	c->icon_label = XCreateWindow(dpy, root, 0, 0, c->icon_geom.w,
	    c->icon_geom.h, 0, CopyFromParent, CopyFromParent, CopyFromParent,
	    CWBackPixel | CWEventMask, &attrs);
	set_atoms(c->icon_label, net_wm_wintype, XA_ATOM, &net_wm_type_desk, 1);
	XMapWindow(dpy, c->icon_label);
	c->icon_xftdraw = XftDrawCreate(dpy, (Drawable)c->icon_label,
	    DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));

	c->icon_gc = XCreateGC(dpy, c->icon, 0, &gv);

	redraw_icon(c, None);
	flush_expose_client(c);
}

void
uniconify_client(client_t *c)
{
	if (c->desk != cur_desk) {
		c->desk = cur_desk;
		set_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk, 1);
	}

	XMapWindow(dpy, c->win);
	XMapRaised(dpy, c->frame);
	c->state &= ~STATE_ICONIFIED;
	set_wm_state(c, NormalState);

	c->ignore_unmap++;
	XDestroyWindow(dpy, c->icon);
	c->icon = None;
	c->ignore_unmap++;
	if (c->icon_xftdraw) {
		XftDrawDestroy(c->icon_xftdraw);
		c->icon_xftdraw = None;
	}
	XDestroyWindow(dpy, c->icon_label);
	c->icon_label = None;

	focus_client(c, FOCUS_FORCE);
}

void
place_icon(client_t *c)
{
	strut_t s = { 0 };
	client_t *p;
	int x, y, isize;

	collect_struts(c, &s);

	s.right = DisplayWidth(dpy, screen) - s.right;
	s.bottom = DisplayHeight(dpy, screen) - s.bottom;

	isize = icon_size * 2.25;

	for (y = s.bottom - isize; y >= s.top; y -= isize) {
		for (x = s.left + icon_size; x < s.right - isize; x += isize) {
			int overlap = 0;

			for (p = focused; p; p = p->next) {
				if (p == c || !(p->state & STATE_ICONIFIED))
					continue;

				if ((p->icon_geom.x + icon_size >= x &&
				    p->icon_geom.x <= x) &&
				    (p->icon_geom.y + icon_size >= y &&
				    p->icon_geom.y <= y)) {
					overlap = 1;
					break;
				}
			}

			if (overlap)
				continue;

			c->icon_geom.x = x;
			c->icon_geom.y = y;
#ifdef DEBUG
			dump_geom(c, c->icon_geom, "place_icon");
#endif
			return;
		}
	}

	/* shrug */
	c->icon_geom.x = s.left;
	c->icon_geom.y = s.top;
}

void
shade_client(client_t *c)
{
	if (c->state != STATE_NORMAL || (c->frame_style == FRAME_NONE))
		return;

	c->state |= STATE_SHADED;
	append_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_shaded, 1);
	do_shade(c);
}

void
unshade_client(client_t *c)
{
	if (!(c->state & STATE_SHADED))
		return;

	c->state &= ~(STATE_SHADED);
	remove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_shaded);
	do_shade(c);
}

static void
do_shade(client_t *c)
{
	if (c->frame) {
		redraw_frame(c, None);

		if (c->state & STATE_SHADED) {
			XUndefineCursor(dpy, c->resize_nw);
			XUndefineCursor(dpy, c->resize_n);
			XUndefineCursor(dpy, c->resize_ne);
			XUndefineCursor(dpy, c->resize_s);
		} else {
			XDefineCursor(dpy, c->resize_nw, resize_nw_curs);
			XDefineCursor(dpy, c->resize_n, resize_n_curs);
			XDefineCursor(dpy, c->resize_ne, resize_ne_curs);
			XDefineCursor(dpy, c->resize_s, resize_s_curs);
		}
	}
	send_config(c);
	flush_expose_client(c);
}

void
fullscreen_client(client_t *c)
{
	int screen_x = DisplayWidth(dpy, screen);
	int screen_y = DisplayHeight(dpy, screen);

#ifdef DEBUG
	dump_name(c, __func__, NULL, c->name);
#endif

	if (c->state & (STATE_FULLSCREEN | STATE_DOCK))
		return;

	if (c->state & STATE_SHADED)
		unshade_client(c);

	c->save = c->geom;
	c->geom.x = 0;
	c->geom.y = 0;
	c->geom.w = screen_x;
	c->geom.h = screen_y;
	c->state |= STATE_FULLSCREEN;
	redraw_frame(c, None);
	send_config(c);
	flush_expose_client(c);
}

void
unfullscreen_client(client_t *c)
{
#ifdef DEBUG
	dump_name(c, __func__, NULL, c->name);
#endif

	if (!(c->state & STATE_FULLSCREEN))
		return;

	c->geom = c->save;
	c->state &= ~STATE_FULLSCREEN;

	recalc_frame(c);
	redraw_frame(c, None);
	send_config(c);
	flush_expose_client(c);
}

/*
 * When zooming a window, the old geom gets stuffed into c->save. Once we
 * unzoom, this should be considered garbage. Despite the existence of vertical
 * and horizontal hints, we only provide both at once.
 *
 * Zooming implies unshading, but the inverse is not true.
 */
void
zoom_client(client_t *c)
{
	strut_t s = { 0 };

	if (c->state & STATE_DOCK)
		return;

	if (c->state & STATE_SHADED)
		unshade_client(c);

	c->save = c->geom;
	c->state |= STATE_ZOOMED;

	collect_struts(c, &s);
	recalc_frame(c);

	c->geom.x = s.left;
	c->geom.y = s.top + c->titlebar_geom.h;
	c->geom.w = DisplayWidth(dpy, screen) - s.left - s.right;
	c->geom.h = DisplayHeight(dpy, screen) - s.top - s.bottom - c->geom.y;

	append_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_mv, 1);
	append_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_mh, 1);
	redraw_frame(c, None);
	send_config(c);
	flush_expose_client(c);
}

void
unzoom_client(client_t *c)
{
	if (!(c->state & STATE_ZOOMED))
		return;

	c->geom = c->save;
	c->state &= ~STATE_ZOOMED;

	remove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_mv);
	remove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_mh);
	redraw_frame(c, None);
	send_config(c);
	flush_expose_client(c);
}

/*
 * The name of this function is a little misleading: if the client doesn't
 * listen to WM_DELETE then we just terminate it with extreme prejudice.
 */
void
send_wm_delete(client_t *c)
{
	int i, n, found = 0;
	Atom *protocols;

	if (XGetWMProtocols(dpy, c->win, &protocols, &n)) {
		for (i = 0; i < n; i++)
			if (protocols[i] == wm_delete)
				found++;
		XFree(protocols);
	}
	if (found)
		send_xmessage(c->win, c->win, wm_protos, wm_delete,
		    NoEventMask);
	else
		XKillClient(dpy, c->win);
}

void
goto_desk(int new_desk)
{
	client_t *c, *newfocus = NULL;

	if (new_desk >= ndesks || new_desk < 0)
		return;

	cur_desk = new_desk;
	set_atoms(root, net_cur_desk, XA_CARDINAL, &cur_desk, 1);

	for (c = focused; c; c = c->next) {
		if (dragging == c) {
			c->desk = cur_desk;
			set_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk,
			    1);
		}

		if (IS_ON_CUR_DESK(c)) {
			if (c->state & STATE_ICONIFIED) {
				XMapWindow(dpy, c->icon);
				XMapWindow(dpy, c->icon_label);
			} else {
				if (!newfocus && !(c->state & STATE_DOCK))
					newfocus = c;

				XMapWindow(dpy, c->frame);
			}
		} else {
			if (c->state & STATE_ICONIFIED) {
				XUnmapWindow(dpy, c->icon);
				XUnmapWindow(dpy, c->icon_label);
			} else
				XUnmapWindow(dpy, c->frame);
		}

		send_config(c);
	}

	restack_clients();

	if (newfocus)
		focus_client(newfocus, FOCUS_FORCE);
}

void
map_if_desk(client_t *c)
{
	if (IS_ON_CUR_DESK(c) && get_wm_state(c->win) == NormalState)
		XMapWindow(dpy, c->frame);
	else
		XUnmapWindow(dpy, c->frame);
}

static XEvent sweepev;
void
sweep(client_t *c, Cursor curs, sweep_func cb, void *cb_arg, strut_t *s)
{
	geom_t orig = (c->state & STATE_ICONIFIED ? c->icon_geom : c->geom);
	client_t *ec;
	strut_t as = { 0 };
	int x0, y0, done = 0;

	get_pointer(&x0, &y0);
	collect_struts(c, &as);
	recalc_frame(c);

	if (XGrabPointer(dpy, root, False, MouseMask, GrabModeAsync,
	    GrabModeAsync, root, curs, CurrentTime) != GrabSuccess)
		return;

	cb(c, orig, x0, y0, x0, y0, s, cb_arg);

	while (!done) {
		XMaskEvent(dpy, ExposureMask | MouseMask | PointerMotionMask |
		    StructureNotifyMask | SubstructureNotifyMask |
		    KeyPressMask | KeyReleaseMask, &sweepev);
#ifdef DEBUG
		show_event(sweepev);
#endif
		switch (sweepev.type) {
		case Expose:
			if ((ec = find_client(sweepev.xexpose.window,
			    MATCH_FRAME)))
				redraw_frame(ec, sweepev.xexpose.window);
			break;
		case MotionNotify:
			cb(c, orig, x0, y0, sweepev.xmotion.x,
			    sweepev.xmotion.y, s, cb_arg);
			break;
		case ButtonRelease:
			done = 1;
			break;
		case UnmapNotify:
			if (c->win == sweepev.xunmap.window) {
				done = 1;
				XPutBackEvent(dpy, &sweepev);
				break;
			}
			handle_unmap_event(&sweepev.xunmap);
			break;
		case KeyPress:
		case KeyRelease:
			/* to allow switching desktops while dragging */
			handle_key_event(&sweepev.xkey);
			break;
		}
	}

	XUngrabPointer(dpy, CurrentTime);
}

/*
 * This is simple and dumb: if the cursor is in the center of the screen,
 * center the window on the available space. If it's at the top left, then at
 * the top left. As you go between, and to other edges, scale it.
 */
void
recalc_map(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,
    strut_t *s, void *arg)
{
	int screen_x = DisplayWidth(dpy, screen);
	int screen_y = DisplayHeight(dpy, screen);
	int wmax = screen_x - s->left - s->right;
	int hmax = screen_y - s->top - s->bottom;

	c->geom.x = s->left + ((float) x1 / (float) screen_x) *
	    (wmax + 1 - c->geom.w - (2 * c->resize_nw_geom.w));
	c->geom.y = s->top + ((float) y1 / (float) screen_y) *
	    (hmax + 1 - c->geom.h - c->titlebar_geom.h -
	    (2 * c->resize_w_geom.w));
}

void
recalc_move(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,
    strut_t *s, void *arg)
{
	int newx = orig.x + x1 - x0;
	int newy = orig.y + y1 - y0;
	int sw = DisplayWidth(dpy, screen);
	int sh = DisplayHeight(dpy, screen);
	geom_t tg;

	if (c->state & STATE_ICONIFIED) {
		int xd = newx - c->icon_geom.x;
		int yd = newy - c->icon_geom.y;

		c->icon_geom.x = newx;
		c->icon_geom.y = newy;
		c->icon_label_geom.x += xd;
		c->icon_label_geom.y += yd;

		XMoveWindow(dpy, c->icon,
		    c->icon_geom.x + ((icon_size - c->icon_geom.w) / 2),
		    c->icon_geom.y + ((icon_size - c->icon_geom.h) / 2));
		XMoveWindow(dpy, c->icon_label, c->icon_label_geom.x,
		    c->icon_label_geom.y);
		send_config(c);
		flush_expose_client(c);
		return;
	}

	sw -= s->right;
	sh -= s->bottom;
	memcpy(&tg, &c->frame_geom, sizeof(c->frame_geom));

	/* provide some resistance at screen edges */
	if (x1 < x0) {
		/* left edge */
		if (newx - c->resize_w_geom.w >= (long)s->left ||
		    newx - c->resize_w_geom.w < (long)s->left -
		    opt_edge_resist) {
			c->geom.x = newx;
		} else {
			c->geom.x = (long)s->left + c->resize_w_geom.w;
		}
	} else {
		/* right edge */
		if (newx + c->geom.w + c->resize_e_geom.w <= sw ||
		    newx + c->geom.w + c->resize_e_geom.w > sw +
		    opt_edge_resist) {
			c->geom.x = newx;
		} else {
			c->geom.x = sw - c->geom.w - c->resize_e_geom.w;
		}
	}

	if (y1 < y0) {
		/* top edge */
		if (newy - c->resize_n_geom.h - c->titlebar_geom.h >=
		    (long)s->top ||
		    newy - c->resize_n_geom.h - c->titlebar_geom.h <
		    (long)s->top - opt_edge_resist) {
			c->geom.y = newy;
		} else {
			c->geom.y = (long)s->top + c->resize_n_geom.h +
				    c->titlebar_geom.h;
		}
	} else {
		/* bottom edge */
		if (newy + c->geom.h + c->resize_s_geom.h <= sh ||
		    newy + c->geom.h + c->resize_s_geom.h > sh +
		    opt_edge_resist) {
			c->geom.y = newy;
		} else {
			c->geom.y = sh - c->geom.h - c->resize_s_geom.h;
		}
	}

	recalc_frame(c);

	if (c->frame_geom.x == tg.x && c->frame_geom.y == tg.y)
		return;

	XMoveWindow(dpy, c->frame, c->frame_geom.x, c->frame_geom.y);
}

void
recalc_resize(client_t *c, geom_t orig, int x0, int y0, int x1, int y1,
    strut_t *move, void *arg)
{
	Window resize_pos = *(Window *)arg;
	geom_t now = { c->geom.x, c->geom.y, c->geom.w, c->geom.h };

	if (resize_pos == c->resize_nw)
		move->top = move->left = 1;
	else if (resize_pos == c->resize_n)
		move->top = 1;
	else if (resize_pos == c->resize_ne)
		move->top = move->right = 1;
	else if (resize_pos == c->resize_e)
		move->right = 1;
	else if (resize_pos == c->resize_se)
		move->right = move->bottom = 1;
	else if (resize_pos == c->resize_s)
		move->bottom = 1;
	else if (resize_pos == c->resize_sw)
		move->bottom = move->left = 1;
	else if (resize_pos == c->resize_w)
		move->left = 1;

	if (move->left)
		c->geom.w = orig.w + (x0 - x1);
	if (move->top)
		c->geom.h = orig.h + (y0 - y1);
	if (move->right) {
		c->geom.w = orig.w - (x0 - x1);
		c->geom.x = orig.x - (x0 - x1);
	}
	if (move->bottom) {
		c->geom.h = orig.h - (y0 - y1);
		c->geom.y = orig.y - (y0 - y1);
	}

	fix_size(c);

	if (move->left)
		c->geom.x = orig.x + orig.w - c->geom.w;
	if (move->top)
		c->geom.y = orig.y + orig.h - c->geom.h;
	if (move->right)
		c->geom.x = orig.x;
	if (move->bottom)
		c->geom.y = orig.y;

	fix_size(c);

	if (c->geom.w != now.w || c->geom.h != now.h) {
		redraw_frame(c, None);
		if (c->shaped)
			set_shape(c);
		send_config(c);
	}
}

/*
 * If the window in question has a ResizeInc hint, then it wants to be resized
 * in multiples of some (x,y). We constrain the values in c->geom based on that
 * and any min/max size hints.
 */
void
fix_size(client_t *c)
{
	int width_inc, height_inc;
	int base_width, base_height;

	if (c->size_hints.flags & PMinSize) {
		if (c->geom.w < c->size_hints.min_width)
			c->geom.w = c->size_hints.min_width;
		if (c->geom.h < c->size_hints.min_height)
			c->geom.h = c->size_hints.min_height;
	}
	if (c->size_hints.flags & PMaxSize) {
		if (c->geom.w > c->size_hints.max_width)
			c->geom.w = c->size_hints.max_width;
		if (c->geom.h > c->size_hints.max_height)
			c->geom.h = c->size_hints.max_height;
	}

	if (c->size_hints.flags & PResizeInc) {
		width_inc = c->size_hints.width_inc ?
		    c->size_hints.width_inc : 1;
		height_inc = c->size_hints.height_inc ?
		    c->size_hints.height_inc : 1;
		base_width = (c->size_hints.flags & PBaseSize) ?
		    c->size_hints.base_width :
		    ((c->size_hints.flags & PMinSize) ?
		    c->size_hints.min_width : 0);
		base_height = (c->size_hints.flags & PBaseSize) ?
		    c->size_hints.base_height :
		    (c->size_hints.flags & PMinSize) ?
		    c->size_hints.min_height : 0;
		c->geom.w -= (c->geom.w - base_width) % width_inc;
		c->geom.h -= (c->geom.h - base_height) % height_inc;
	}
}

/* make sure a frame fits on the screen */
void
constrain_frame(client_t *c)
{
	strut_t s = { 0 };
	int h, w, delta;

	if (c->state & STATE_FULLSCREEN)
		return;

#ifdef DEBUG
	dump_geom(c, c->geom, "constrain_frame initial");
#endif

	recalc_frame(c);
	fix_size(c);

	collect_struts(c, &s);

	if (c->frame_geom.x < s.left) {
		delta = s.left - c->frame_geom.x;
		c->frame_geom.x += delta;
		c->geom.x += delta;
	}
	if (c->frame_geom.y < s.top) {
		delta = s.top - c->frame_geom.y;
		c->frame_geom.y += delta;
		c->geom.y += delta;
	}

	h = DisplayHeight(dpy, screen) - s.top - s.bottom;
	if (c->frame_geom.y + c->frame_geom.h > h) {
		delta = c->frame_geom.y + c->frame_geom.h - h;
		c->frame_geom.h -= delta;
		c->geom.h -= delta;
	}

	w = DisplayWidth(dpy, screen) - s.left - s.right;
	if (c->frame_geom.x + c->frame_geom.w > w) {
		delta = c->frame_geom.x + c->frame_geom.w - w;
		c->frame_geom.w -= delta;
		c->geom.w -= delta;
	}

	/* TODO: fix_size() again but don't allow enlarging */

	recalc_frame(c);

#ifdef DEBUG
	dump_geom(c, c->geom, "constrain_frame final");
#endif
}

void
flush_expose_client(client_t *c)
{
	if (c->resize_nw)
		flush_expose(c->resize_nw);
	if (c->resize_n)
		flush_expose(c->resize_n);
	if (c->resize_ne)
		flush_expose(c->resize_ne);
	if (c->resize_e)
		flush_expose(c->resize_e);
	if (c->resize_se)
		flush_expose(c->resize_se);
	if (c->resize_s)
		flush_expose(c->resize_s);
	if (c->resize_sw)
		flush_expose(c->resize_sw);
	if (c->resize_w)
		flush_expose(c->resize_w);
	if (c->close)
		flush_expose(c->close);
	if (c->iconify)
		flush_expose(c->iconify);
	if (c->zoom)
		flush_expose(c->zoom);
	if (c->titlebar)
		flush_expose(c->titlebar);
	if (c->icon)
		flush_expose(c->icon);
	if (c->icon_label)
		flush_expose(c->icon_label);
}

/* remove expose events for a window from the event queue */
void
flush_expose(Window win)
{
	XEvent junk;
	while (XCheckTypedWindowEvent(dpy, win, Expose, &junk))
		;
}

int
overlapping_geom(geom_t a, geom_t b)
{
	if (a.x <= b.x + b.w && a.x + a.w >= b.x &&
	    a.y <= b.y + b.h && a.y + a.h >= b.y)
		return 1;

	return 0;
}

void
restack_clients(void)
{
	Window *wins = NULL;
	client_t *p;
	int twins = 0, nwins = 0;

	/* restack windows - ABOVE, normal, BELOW, ICONIFIED */
	for (p = focused, twins = 0; p; p = p->next)
		twins += 2;

	if (twins == 0)
		return;

	wins = realloc(wins, twins * sizeof(Window));
	if (wins == NULL)
		err(1, "realloc");

	/* STATE_ABOVE first */
	for (p = focused; p; p = p->next) {
		if (!IS_ON_CUR_DESK(p))
			continue;

		if ((p->state & STATE_ABOVE) && !(p->state & STATE_ICONIFIED))
			wins[nwins++] = p->frame;
	}

	/* then non-iconified windows */
	for (p = focused; p; p = p->next) {
		if (!IS_ON_CUR_DESK(p))
			continue;

		if (!(p->state & (STATE_ICONIFIED | STATE_BELOW | STATE_ABOVE |
		    STATE_DOCK)))
			wins[nwins++] = p->frame;
	}

	/* then BELOW windows */
	for (p = focused; p; p = p->next) {
		if (!IS_ON_CUR_DESK(p))
			continue;

		if (p->state & (STATE_BELOW | STATE_DOCK))
			wins[nwins++] = p->frame;
	}

	/* then icons, taking from all desks */
	for (p = focused; p; p = p->next) {
		if (p->state & STATE_ICONIFIED) {
			wins[nwins++] = p->icon;
			wins[nwins++] = p->icon_label;
		}
	}

	if (nwins > twins) {
		warnx("%s allocated for %d windows, used %d", __func__,
		    twins, nwins);
		abort();
	}

	XRestackWindows(dpy, wins, nwins);

	free(wins);

	/* TODO: update net_client_stack */
}

void
adjust_client_order(client_t *c, int where)
{
	client_t *p, *pp;

	if (c != NULL && focused == c && !c->next)
		return;

	switch (where) {
	case ORDER_TOP:
		for (p = focused; p && p->next; p = p->next) {
			if (p->next == c) {
				p->next = c->next;
				break;
			}
		}

		if (c != focused) {
			c->next = focused;
			focused = c;
		}
		break;
	case ORDER_ICONIFIED_TOP:
		/* remove first */
		if (focused == c)
			focused = c->next;
		else {
			for (p = focused; p && p->next; p = p->next)
				if (p->next == c) {
					p->next = c->next;
					break;
				}
		}
		c->next = NULL;

		p = focused; pp = NULL;
		while (p && p->next) {
			if (!(p->state & STATE_ICONIFIED)) {
				pp = p;
				p = p->next;
				continue;
			}

			if (pp)
				/* place ahead of this first iconfied client */
				pp->next = c;
			else
				/* no previous non-iconified clients */
				focused = c;

			if (c != p)
				c->next = p;
			break;
		}

		if (!c->next) {
			/* no iconified clients, place at the bottom */
			if (p)
				p->next = c;
			else
				focused = c;
			c->next = NULL;
		}
		break;
	case ORDER_BOTTOM:
		for (p = focused; p && p->next; p = p->next) {
			if (p->next == c)
				p->next = c->next;
			/* continue until p->next == NULL */
		}

		if (p)
			p->next = c;
		else
			focused = c;

		c->next = NULL;
		break;
	case ORDER_OUT:
		if (c == focused)
			focused = c->next;
		else {
			for (p = focused; p && p->next; p = p->next) {
				if (p->next == c) {
					p->next = c->next;
					break;
				}
			}
		}
		break;
	case ORDER_INVERT: {
		client_t *pp, *n, *tail;

		for (tail = focused; tail && tail->next; tail = tail->next)
			;

		for (p = focused, pp = NULL, n = tail; n; pp = p, p = n) {
			n = p->next;
			p->next = pp;
		}
		focused = tail;

		break;
	}
	default:
		printf("unknown client sort option %d\n", where);
	}
}

client_t *
next_client_for_focus(client_t *head)
{
	client_t *n;

	for (n = head->next; n; n = n->next)
		if (IS_ON_CUR_DESK(n) && !(n->state & STATE_DOCK))
			return n;

	return NULL;
}

#ifdef DEBUG
char *
state_name(client_t *c)
{
	int s = 30;
	char *res;

	res = malloc(s);
	if (res == NULL)
		err(1, "malloc");

	res[0] = '\0';

	if (c->state == STATE_NORMAL)
		strlcat(res, "normal", s);
	if (c->state & STATE_ZOOMED)
		strlcat(res, "| zoomed", s);
	if (c->state & STATE_ICONIFIED)
		strlcat(res, "| iconified", s);
	if (c->state & STATE_SHADED)
		strlcat(res, "| shaded", s);
	if (c->state & STATE_FULLSCREEN)
		strlcat(res, "| fs", s);
	if (c->state & STATE_DOCK)
		strlcat(res, "| dock", s);

	if (res[0] == '|')
		res = strdup(res + 2);

	return res;
}

const char *
frame_name(client_t *c, Window w)
{
	if (w == None)
		return "";
	if (w == c->frame)
		return "frame";
	if (w == c->resize_nw)
		return "resize_nw";
	if (w == c->resize_w)
		return "resize_w";
	if (w == c->resize_sw)
		return "resize_sw";
	if (w == c->resize_s)
		return "resize_s";
	if (w == c->resize_se)
		return "resize_se";
	if (w == c->resize_e)
		return "resize_e";
	if (w == c->resize_ne)
		return "resize_ne";
	if (w == c->resize_n)
		return "resize_n";
	if (w == c->titlebar)
		return "titlebar";
	if (w == c->close)
		return "close";
	if (w == c->iconify)
		return "iconify";
	if (w == c->zoom)
		return "zoom";
	if (w == c->icon)
		return "icon";
	if (w == c->icon_label)
		return "icon_label";
	return "unknown";
}

static const char *
show_grav(client_t *c)
{
	if (!(c->size_hints.flags & PWinGravity))
		return "no grav (NW)";

	switch (c->size_hints.win_gravity) {
	SHOW(UnmapGravity)
	SHOW(NorthWestGravity)
	SHOW(NorthGravity)
	SHOW(NorthEastGravity)
	SHOW(WestGravity)
	SHOW(CenterGravity)
	SHOW(EastGravity)
	SHOW(SouthWestGravity)
	SHOW(SouthGravity)
	SHOW(SouthEastGravity)
	SHOW(StaticGravity)
	default:
		return "unknown grav";
	}
}

void
dump_name(client_t *c, const char *label, const char *detail, const char *name)
{
	printf("%18.18s: %#010lx [%-9.9s] %-35.35s\n", label,
	    c ? c->win : 0, detail == NULL ? "" : detail,
	    name == NULL ? "" : name);
}

void
dump_info(client_t *c)
{
	char *s = state_name(c);

	printf("%31s[i] ignore_unmap %d, trans 0x%lx, focus %d, shape %d\n", "",
	    c->ignore_unmap, c->trans, focused == c ? 1 : 0, c->shaped ? 1 : 0);
	printf("%31s[i] desk %ld, state %s, %s\n", "",
	    c->desk, s, show_grav(c));

	free(s);
}

void
dump_geom(client_t *c, geom_t g, const char *label)
{
	printf("%31s[g] %s %ldx%ld+%ld+%ld\n", "",
	    label, g.w, g.h, g.x, g.y);
}

void
dump_removal(client_t *c, int mode)
{
	printf("%31s[r] %s, %d pending\n", "",
	    mode == DEL_WITHDRAW ? "withdraw" : "remap", XPending(dpy));
}

void
dump_clients(void)
{
	client_t *c;

	for (c = focused; c; c = c->next) {
		dump_name(c, __func__, NULL, c->name);
		dump_geom(c, c->geom, "current");
		dump_info(c);
	}
}
#endif


================================================
FILE: parser.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <err.h>
#include <stdlib.h>
#include <string.h>
#include "progman.h"
#include "parser.h"
#include "progman_ini.h"

/*
 * If the user specifies an ini file, return NULL immediately if it's not
 * found; otherwise, search for the usual suspects.
 */
FILE *
open_ini(char *inifile)
{
	FILE *ini = NULL;
	char buf[BUF_SIZE];

	if (inifile) {
		ini = fopen(inifile, "r");
		if (!ini)
			err(1, "can't open config file %s", inifile);

		return ini;
	}

	snprintf(buf, sizeof(buf), "%s/.config/progman/progman.ini",
	    getenv("HOME"));
	if ((ini = fopen(buf, "r")))
		return ini;

	/* load compiled-in defaults */
	ini = fmemopen(progman_ini, sizeof(progman_ini), "r");
	if (!ini || sizeof(progman_ini) == 0)
		errx(1, "no compiled-in default config file");

	return ini;
}

int
find_ini_section(FILE *stream, char *section)
{
	char buf[BUF_SIZE], marker[BUF_SIZE];

	snprintf(marker, sizeof(marker), "[%s]\n", section);

	fseek(stream, 0, SEEK_SET);
	while (fgets(buf, sizeof(buf), stream)) {
		if (buf[0] == '#' || buf[0] == '\n')
			continue;
		if (strncmp(buf, marker, strlen(marker)) == 0)
			return 1;
	}

	return 0;
}

int
get_ini_kv(FILE *stream, char **key, char **val)
{
	char buf[BUF_SIZE], *tval;
	long pos = ftell(stream);
	int len;

	buf[0] = '\0';

	/* find next non-comment, non-section line */
	while (fgets(buf, sizeof(buf), stream)) {
		if (buf[0] == '#' || buf[0] == '\n') {
			pos = ftell(stream);
			continue;
		}

		if (buf[0] == '[') {
			/* new section, rewind so find_ini_section can see it */
			fseek(stream, pos, SEEK_SET);
			return 0;
		}

		break;
	}

	if (!buf[0])
		return 0;

	tval = strchr(buf, '=');
	if (tval == NULL) {
		warnx("bad line in ini file: %s", buf);
		return 0;
	}

	tval[0] = '\0';
	tval++;

	/* trim trailing spaces from key */
	for (len = strlen(buf); len > 0 && buf[len - 1] == ' '; len--)
		;
	buf[len] = '\0';

	/* trim leading spaces from val */
	while (tval[0] == ' ')
		tval++;

	/* and trailing spaces and newlines from val */
	len = strlen(tval) - 1;
	while (len) {
		if (tval[len] == ' ' || tval[len] == '\r' || tval[len] == '\n')
			len--;
		else
			break;
	}
	tval[len + 1] = '\0';

	*key = strdup(buf);
	*val = strdup(tval);

	return 1;
}


================================================
FILE: parser.h
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef PROGMAN_PARSER_H
#define PROGMAN_PARSER_H

#include <stdio.h>

FILE *open_ini(char *);
int find_ini_section(FILE *, char *);
int get_ini_kv(FILE *, char **, char **);

#endif	/* PROGMAN_PARSER_H */


================================================
FILE: progman.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifdef __linux__
#define _GNU_SOURCE
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <locale.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/extensions/shape.h>
#ifdef USE_GDK_PIXBUF
#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
#endif
#include "progman.h"
#include "atom.h"
#include "parser.h"

#include "icons/close.xpm"
#include "icons/utility_close.xpm"
#include "icons/iconify.xpm"
#include "icons/zoom.xpm"
#include "icons/unzoom.xpm"
#include "icons/default_icon.xpm"
#include "icons/hidpi-close.xpm"
#include "icons/hidpi-utility_close.xpm"
#include "icons/hidpi-iconify.xpm"
#include "icons/hidpi-zoom.xpm"
#include "icons/hidpi-unzoom.xpm"
#include "icons/hidpi-default_icon.xpm"

#ifndef WAIT_ANY
#define WAIT_ANY (-1)
#endif

char *orig_argv0;
Display *dpy;
Window root;
client_t *cycle_head;
client_t *focused, *dragging;
int screen;
int ignore_xerrors = 0;
unsigned long ndesks = DEF_NDESKS;
unsigned long cur_desk = 0;
unsigned int focus_order = 0;
Bool shape_support;
int shape_event;
Window supporting_wm_win;

XftFont *font;
XftFont *iconfont;
XftColor xft_fg;
XftColor xft_fg_unfocused;
XftColor xft_launcher;
XftColor xft_launcher_highlighted;

Colormap def_cmap;
XColor fg;
XColor bg;
XColor unfocused_fg;
XColor unfocused_bg;
XColor button_bg;
XColor bevel_dark;
XColor bevel_light;
XColor border_fg;
XColor border_bg;
XColor launcher_fg;
XColor launcher_bg;
GC pixmap_gc;
GC invert_gc;
Pixmap close_pm;
Pixmap close_pm_mask;
XpmAttributes close_pm_attrs;
Pixmap utility_close_pm;
Pixmap utility_close_pm_mask;
XpmAttributes utility_close_pm_attrs;
Pixmap iconify_pm;
Pixmap iconify_pm_mask;
XpmAttributes iconify_pm_attrs;
Pixmap zoom_pm;
Pixmap zoom_pm_mask;
XpmAttributes zoom_pm_attrs;
Pixmap unzoom_pm;
Pixmap unzoom_pm_mask;
XpmAttributes unzoom_pm_attrs;
Pixmap default_icon_pm;
Pixmap default_icon_pm_mask;
XpmAttributes default_icon_pm_attrs;
Cursor map_curs;
Cursor move_curs;
Cursor resize_n_curs;
Cursor resize_s_curs;
Cursor resize_e_curs;
Cursor resize_w_curs;
Cursor resize_nw_curs;
Cursor resize_sw_curs;
Cursor resize_ne_curs;
Cursor resize_se_curs;

int exitmsg[2];

char *opt_config_file = NULL;
char *opt_font = DEF_FONT;
char *opt_iconfont = DEF_ICONFONT;
char *opt_fg = DEF_FG;
char *opt_bg = DEF_BG;
char *opt_unfocused_fg = DEF_UNFOCUSED_FG;
char *opt_unfocused_bg = DEF_UNFOCUSED_BG;
char *opt_button_bg = DEF_BUTTON_BG;
char *opt_bevel_dark = DEF_BEVEL_DARK;
char *opt_bevel_light = DEF_BEVEL_LIGHT;
char *opt_border_fg = DEF_BORDER_FG;
char *opt_border_bg = DEF_BORDER_BG;
char *opt_launcher_fg = DEF_LAUNCHER_FG;
char *opt_launcher_bg = DEF_LAUNCHER_BG;
char *opt_root_bg = DEF_ROOTBG;
int opt_bw = DEF_BW;
int opt_pad = DEF_PAD;
int opt_bevel = DEF_BEVEL;
int opt_edge_resist = DEF_EDGE_RES;
int opt_scale = DEF_SCALE;
int icon_size = ICON_SIZE_MULT * DEF_SCALE;
int opt_drag_button = 0;
int opt_drag_mod = 0;

void read_config(void);
void setup_display(void);
void scale_icon(void *xpm, void *hidpi_xpm, Pixmap *pm, Pixmap *pm_mask,
    XpmAttributes *xpm_attrs);

int
main(int argc, char **argv)
{
	struct sigaction act;
	int ch;

	orig_argv0 = strdup(argv[0]);

	setlocale(LC_ALL, "");

	while ((ch = getopt(argc, argv, "c:")) != -1) {
		switch (ch) {
		case 'c':
			if (opt_config_file)
				free(opt_config_file);
			opt_config_file = strdup(optarg);
			break;
		default:
			printf("usage: %s [-c <config file>]\n", argv[0]);
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

	/* parsing the config file may need dpy, so connect early */
	dpy = XOpenDisplay(NULL);
	if (!dpy)
		err(1, "can't open $DISPLAY \"%s\"", getenv("DISPLAY"));

	XSetErrorHandler(handle_xerror);
	screen = DefaultScreen(dpy);
	root = RootWindow(dpy, screen);

	read_config();

	if (pipe2(exitmsg, O_CLOEXEC) != 0)
		err(1, "pipe2");

	act.sa_handler = sig_handler;
	act.sa_flags = 0;
	sigaction(SIGTERM, &act, NULL);
	sigaction(SIGINT, &act, NULL);
	sigaction(SIGHUP, &act, NULL);
	sigaction(SIGCHLD, &act, NULL);

	setup_display();
	launcher_setup();
	event_loop();
	cleanup();

	return 0;
}

void
read_config(void)
{
	FILE *ini = NULL;
	char *key, *val;
	action_t *act;

	ini = open_ini(opt_config_file);

	if (find_ini_section(ini, "progman")) {
		while (get_ini_kv(ini, &key, &val)) {
			if (strcmp(key, "font") == 0)
				opt_font = strdup(val);
			else if (strcmp(key, "iconfont") == 0)
				opt_iconfont = strdup(val);
			else if (strcmp(key, "fgcolor") == 0)
				opt_fg = strdup(val);
			else if (strcmp(key, "bgcolor") == 0)
				opt_bg = strdup(val);
			else if (strcmp(key, "unfocused_fgcolor") == 0)
				opt_unfocused_fg = strdup(val);
			else if (strcmp(key, "unfocused_bgcolor") == 0)
				opt_unfocused_bg = strdup(val);
			else if (strcmp(key, "button_bgcolor") == 0)
				opt_button_bg = strdup(val);
			else if (strcmp(key, "border_fgcolor") == 0)
				opt_border_fg = strdup(val);
			else if (strcmp(key, "border_bgcolor") == 0)
				opt_border_bg = strdup(val);
			else if (strcmp(key, "launcher_fgcolor") == 0)
				opt_launcher_fg = strdup(val);
			else if (strcmp(key, "launcher_bgcolor") == 0)
				opt_launcher_bg = strdup(val);
			else if (strcmp(key, "root_bgcolor") == 0)
				opt_root_bg = strdup(val);
			else if (strcmp(key, "border_width") == 0) {
				opt_bw = atoi(val);
				if (opt_bw < 0) {
					warnx("invalid value for border_width");
					opt_bw = DEF_BW;
				}
			} else if (strcmp(key, "title_padding") == 0) {
				opt_pad = atoi(val);
				if (opt_pad < 0) {
					warnx("invalid value for "
					    "title_padding");
					opt_pad = DEF_PAD;
				}
			} else if (strcmp(key, "edgeresist") == 0) {
				opt_edge_resist = atoi(val);
				if (opt_edge_resist < 0) {
					warnx("invalid value for edgeresist");
					opt_edge_resist = DEF_EDGE_RES;
				}
			} else if (strcmp(key, "scale") == 0) {
				opt_scale = atoi(val);
				if (opt_scale < 0) {
					warnx("invalid value for scale");
					opt_scale = DEF_SCALE;
				}
			} else if (strcmp(key, "drag_combo") == 0) {
				act = bind_key(BINDING_TYPE_DRAG, val, "drag");
				if (act == NULL)
					warnx("invalid drag_combo \"%s\" in "
					    "ini", val);
				else {
					opt_drag_button = act->button;
					opt_drag_mod = act->mod;
				}
			} else
				warnx("unknown key \"%s\" and value \"%s\" in "
				    "ini", key, val);

			free(key);
			free(val);
		}
	}

	if (find_ini_section(ini, "keyboard"))
		while (get_ini_kv(ini, &key, &val))
			bind_key(BINDING_TYPE_KEYBOARD, key, val);

	if (find_ini_section(ini, "desktop"))
		while (get_ini_kv(ini, &key, &val))
			bind_key(BINDING_TYPE_DESKTOP, key, val);

	fclose(ini);
}

void
setup_display(void)
{
	XGCValues gv;
	XColor exact;
	XSetWindowAttributes sattr;
	XWindowAttributes attr;
	XIconSize *xis;
	XColor root_bg;
	Pixmap rootpx;
	int shape_err;
	Window qroot, qparent, *wins;
	unsigned int nwins, i;
	client_t *c;

	focused = NULL;
	dragging = NULL;

#ifdef USE_GDK_PIXBUF
	gdk_pixbuf_xlib_init(dpy, screen);
#endif

	map_curs = XCreateFontCursor(dpy, XC_dotbox);
	move_curs = XCreateFontCursor(dpy, XC_fleur);
	resize_n_curs = XCreateFontCursor(dpy, XC_top_side);
	resize_s_curs = XCreateFontCursor(dpy, XC_bottom_side);
	resize_e_curs = XCreateFontCursor(dpy, XC_right_side);
	resize_w_curs = XCreateFontCursor(dpy, XC_left_side);
	resize_nw_curs = XCreateFontCursor(dpy, XC_top_left_corner);
	resize_sw_curs = XCreateFontCursor(dpy, XC_bottom_left_corner);
	resize_ne_curs = XCreateFontCursor(dpy, XC_top_right_corner);
	resize_se_curs = XCreateFontCursor(dpy, XC_bottom_right_corner);

#define alloc_color(val, var, name) \
	if (!XAllocNamedColor(dpy, def_cmap, val, var, &exact)) \
		warnx("invalid %s value \"%s\"", name, val);

	def_cmap = DefaultColormap(dpy, screen);
	alloc_color(opt_fg, &fg, "fgcolor");
	alloc_color(opt_bg, &bg, "opt_fg");
	alloc_color(opt_unfocused_fg, &unfocused_fg, "unfocused_fgcolor");
	alloc_color(opt_unfocused_bg, &unfocused_bg, "unfocused_bgcolor");
	alloc_color(opt_button_bg, &button_bg, "button_bgcolor");
	alloc_color(opt_bevel_dark, &bevel_dark, "bevel_darkcolor");
	alloc_color(opt_bevel_light, &bevel_light, "bevel_lightcolor");
	alloc_color(opt_border_fg, &border_fg, "border_fgcolor");
	alloc_color(opt_border_bg, &border_bg, "border_bgcolor");
	alloc_color(opt_launcher_fg, &launcher_fg, "launcher_fgcolor");
	alloc_color(opt_launcher_bg, &launcher_bg, "launcher_bgcolor");

	XSetLineAttributes(dpy, DefaultGC(dpy, screen), 1, LineSolid, CapButt,
	    JoinBevel);
	XSetFillStyle(dpy, DefaultGC(dpy, screen), FillSolid);

#define create_xft_color(_xft, _pixel) \
	(_xft).color.red = (_pixel).red; \
	(_xft).color.green = (_pixel).green; \
	(_xft).color.blue = (_pixel).blue; \
	(_xft).color.alpha = 0xffff; \
	(_xft).pixel = (_pixel).pixel;

	create_xft_color(xft_fg, fg);
	create_xft_color(xft_fg_unfocused, unfocused_fg);
	create_xft_color(xft_launcher, launcher_fg);
	create_xft_color(xft_launcher_highlighted, launcher_bg);

	font = XftFontOpenName(dpy, screen, opt_font);
	if (!font)
		errx(1, "Xft font \"%s\" not found", opt_font);

	iconfont = XftFontOpenName(dpy, screen, opt_iconfont);
	if (!iconfont)
		errx(1, "icon Xft font \"%s\" not found", opt_iconfont);

	pixmap_gc = XCreateGC(dpy, root, 0, &gv);

	gv.function = GXinvert;
	gv.subwindow_mode = IncludeInferiors;
	invert_gc = XCreateGC(dpy, root,
	    GCFunction | GCSubwindowMode | GCLineWidth, &gv);

	scale_icon(close_xpm, hidpi_close_xpm, &close_pm, &close_pm_mask,
	    &close_pm_attrs);
	scale_icon(utility_close_xpm, hidpi_utility_close_xpm,
	    &utility_close_pm, &utility_close_pm_mask, &utility_close_pm_attrs);
	scale_icon(iconify_xpm, hidpi_iconify_xpm, &iconify_pm,
	    &iconify_pm_mask, &iconify_pm_attrs);
	scale_icon(zoom_xpm, hidpi_zoom_xpm, &zoom_pm, &zoom_pm_mask,
	    &zoom_pm_attrs);
	scale_icon(unzoom_xpm, hidpi_unzoom_xpm, &unzoom_pm, &unzoom_pm_mask,
	    &unzoom_pm_attrs);
	scale_icon(default_icon_xpm, hidpi_default_icon_xpm, &default_icon_pm,
	    &default_icon_pm_mask, &default_icon_pm_attrs);

	icon_size = ICON_SIZE_MULT * opt_scale;
	xis = XAllocIconSize();
	xis->min_width = icon_size;
	xis->min_height = icon_size;
	xis->max_width = icon_size;
	xis->max_height = icon_size;
	xis->width_inc = 1;
	xis->height_inc = 1;
	XSetIconSizes(dpy, root, xis, 1);
	XFree(xis);

	find_supported_atoms();

	if (opt_root_bg != NULL && strlen(opt_root_bg) &&
	    XAllocNamedColor(dpy, def_cmap, opt_root_bg, &root_bg, &exact)) {
		rootpx = XCreatePixmap(dpy, root, 1, 1,
		    DefaultDepth(dpy, screen));
		XSetForeground(dpy, pixmap_gc, root_bg.pixel);
		XFillRectangle(dpy, rootpx, pixmap_gc, 0, 0, 1, 1);
		XSetWindowBackgroundPixmap(dpy, root, rootpx);
		XClearWindow(dpy, root);
		set_atoms(root, xrootpmap_id, XA_PIXMAP, &rootpx, 1);
	} else if (opt_root_bg)
		warnx("invalid root_bgcolor value \"%s\"", opt_root_bg);

	set_atoms(root, net_num_desks, XA_CARDINAL, &ndesks, 1);
	get_atoms(root, net_cur_desk, XA_CARDINAL, 0, &cur_desk, 1, NULL);
	if (cur_desk >= ndesks) {
		cur_desk = ndesks - 1;
		set_atoms(root, net_cur_desk, XA_CARDINAL, &cur_desk, 1);
	}

	shape_support = XShapeQueryExtension(dpy, &shape_event, &shape_err);

	XQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);
	for (i = 0; i < nwins; i++) {
		ignore_xerrors++;
		XGetWindowAttributes(dpy, wins[i], &attr);
		ignore_xerrors--;
		if (!attr.override_redirect && attr.map_state == IsViewable) {
			c = new_client(wins[i]);
			c->placed = 1;
			map_client(c);
			map_if_desk(c);
		}
	}
	XFree(wins);

	/* become "the" window manager with SubstructureRedirectMask on root */
	sattr.event_mask = SubMask | ColormapChangeMask | ButtonMask;
	XChangeWindowAttributes(dpy, root, CWEventMask, &sattr);

	/* create a hidden window for _NET_SUPPORTING_WM_CHECK */
	supporting_wm_win = XCreateWindow(dpy, root, 0, 0, 1, 1,
	    0, DefaultDepth(dpy, screen), CopyFromParent,
	    DefaultVisual(dpy, screen), 0, NULL);
	set_string_atom(supporting_wm_win, net_wm_name,
	    (unsigned char *)"progman", 7);
	set_atoms(root, net_supporting_wm, XA_WINDOW, &supporting_wm_win, 1);
}

void
scale_icon(void *xpm, void *hidpi_xpm, Pixmap *pm, Pixmap *pm_mask,
    XpmAttributes *xpm_attrs)
{
	GC scale_gc, mask_scale_gc;
	Pixmap pm_scaled, pm_scaled_mask;
	int x, y, i, j;

	if (opt_scale == 2) {
		/* regular-sized icons are too big to 2x, so use hidpi ones */
		if (XpmCreatePixmapFromData(dpy, root, hidpi_xpm, pm, pm_mask,
		    xpm_attrs) != XpmSuccess)
			err(1, "XpmCreatePixmapFromData");

		return;
	}

	if (XpmCreatePixmapFromData(dpy, root, xpm, pm, pm_mask,
	    xpm_attrs) != XpmSuccess)
		err(1, "XpmCreatePixmapFromData");

	if (opt_scale == 1)
		return;

	scale_gc = XCreateGC(dpy, *pm, 0, 0);
	mask_scale_gc = XCreateGC(dpy, *pm_mask, 0, 0);

	pm_scaled = XCreatePixmap(dpy, *pm,
	    xpm_attrs->width * opt_scale, xpm_attrs->height * opt_scale,
	    DefaultDepth(dpy, screen));
	pm_scaled_mask = XCreatePixmap(dpy, *pm_mask,
	    xpm_attrs->width * opt_scale, xpm_attrs->height * opt_scale,
	    1);

	for (y = 0; y < xpm_attrs->height; y++) {
		for (x = 0; x < xpm_attrs->width; x++) {
			for (i = 0; i < opt_scale; i++) {
				for (j = 0; j < opt_scale; j++) {
					XCopyArea(dpy, *pm, pm_scaled, scale_gc,
					    x, y, 1, 1,
					    (x * opt_scale) + i,
					    (y * opt_scale) + j);
					XCopyArea(dpy, *pm_mask, pm_scaled_mask,
					    mask_scale_gc,
					    x, y, 1, 1,
					    (x * opt_scale) + i,
					    (y * opt_scale) + j);
				}
			}
		}
	}

	XFreeGC(dpy, scale_gc);
	XFreeGC(dpy, mask_scale_gc);

	xpm_attrs->width *= opt_scale;
	xpm_attrs->height *= opt_scale;

	XFreePixmap(dpy, *pm);
	XFreePixmap(dpy, *pm_mask);
	*pm = pm_scaled;
	*pm_mask = pm_scaled_mask;
}

void
sig_handler(int signum)
{
	pid_t pid;
	int status;

	switch (signum) {
	case SIGINT:
	case SIGTERM:
	case SIGHUP:
		quit();
		break;
	case SIGCHLD:
		while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0 ||
		    (pid < 0 && errno == EINTR))
			;
		break;
	}
}

void
quit(void)
{
	if (write(exitmsg[1], &exitmsg, 1))
		return;

	warn("failed to exit cleanly");
	exit(0);
}

int
handle_xerror(Display *dpy, XErrorEvent *e)
{
	char msg[255];

	if (e->error_code == BadAccess && e->resourceid == root)
		errx(1, "root window unavailable");

	if (!ignore_xerrors) {
		XGetErrorText(dpy, e->error_code, msg, sizeof(msg));
		warnx("X error (%#lx): %s", e->resourceid, msg);
		fflush(stdout);
		fflush(stderr);
	}

	return 0;
}

/*
 * We use XQueryTree here to preserve the window stacking order, since the
 * order in our linked list is different.
 */
void
cleanup(void)
{
	unsigned int nwins, i;
	XSetWindowAttributes sattr;
	Window qroot, qparent, *wins;
	client_t *c;

	XQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins);
	for (i = 0; i < nwins; i++) {
		c = find_client(wins[i], MATCH_FRAME);
		if (c)
			del_client(c, DEL_REMAP);
	}
	XFree(wins);

	XftFontClose(dpy, font);
	XftFontClose(dpy, iconfont);
	XFreeCursor(dpy, map_curs);
	XFreeCursor(dpy, move_curs);
	XFreeCursor(dpy, resize_n_curs);
	XFreeCursor(dpy, resize_s_curs);
	XFreeCursor(dpy, resize_e_curs);
	XFreeCursor(dpy, resize_w_curs);
	XFreeCursor(dpy, resize_nw_curs);
	XFreeCursor(dpy, resize_sw_curs);
	XFreeCursor(dpy, resize_ne_curs);
	XFreeCursor(dpy, resize_se_curs);
	XFreeGC(dpy, pixmap_gc);
	XFreeGC(dpy, invert_gc);
	XFreePixmap(dpy, close_pm);
	XFreePixmap(dpy, close_pm_mask);
	XFreePixmap(dpy, utility_close_pm);
	XFreePixmap(dpy, utility_close_pm_mask);
	XFreePixmap(dpy, iconify_pm);
	XFreePixmap(dpy, iconify_pm_mask);
	XFreePixmap(dpy, zoom_pm);
	XFreePixmap(dpy, zoom_pm_mask);
	XFreePixmap(dpy, unzoom_pm);
	XFreePixmap(dpy, unzoom_pm_mask);
	XFreePixmap(dpy, default_icon_pm);
	XFreePixmap(dpy, default_icon_pm_mask);

	XInstallColormap(dpy, DefaultColormap(dpy, screen));
	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);

	XDeleteProperty(dpy, root, net_supporting_wm);
	XDestroyWindow(dpy, supporting_wm_win);

	XDeleteProperty(dpy, root, net_supported);
	XDeleteProperty(dpy, root, net_client_list);

	launcher_programs_free();

	/* resign as "the" window manager */
	sattr.event_mask = 0;
	XChangeWindowAttributes(dpy, root, CWEventMask, &sattr);

	XCloseDisplay(dpy);
}


================================================
FILE: progman.h
================================================
/*
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef PROGMAN_H
#define PROGMAN_H

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xft/Xft.h>
#include <X11/xpm.h>
#include "atom.h"

/* Default options */

/* Title bars */
#define DEF_FG "white"
#define DEF_BG "#0000a8"
#define DEF_UNFOCUSED_FG "black"
#define DEF_UNFOCUSED_BG "white"
#define DEF_BUTTON_BG "#c0c7c8"
#define DEF_BEVEL_DARK "#87888f"
#define DEF_BEVEL_LIGHT "white"

/* Borders */
#define DEF_BORDER_FG "black"
#define DEF_BORDER_BG "#c0c7c8"

/* Launcher */
#define DEF_LAUNCHER_FG "black"
#define DEF_LAUNCHER_BG "#c0c7c8"

/* Default root color is unchanged */
#define DEF_ROOTBG NULL

#define DEF_FONT "Microsoft Sans Serif:bold:size=14"
#define DEF_ICONFONT "Microsoft Sans Serif:size=11"
#define DEF_BEVEL 2
#define DEF_PAD 6
#define DEF_BW 3
#define DEF_EDGE_RES 80
#define DEF_SCALE 2
#define ICON_SIZE_MULT 32

#define DEF_NDESKS 5

#define DOUBLE_CLICK_MSEC 250

#define BUF_SIZE 2048

/* End of options */

#define SubMask (SubstructureRedirectMask|SubstructureNotifyMask)
#define ButtonMask (ButtonPressMask|ButtonReleaseMask)
#define MouseMask (ButtonMask|PointerMotionMask)

#define GRAV(c) ((c->size.flags & PWinGravity) ? c->size.win_gravity : \
    NorthWestGravity)
#define CAN_PLACE_SELF(t) ((t) == net_wm_type_dock || \
    (t) == net_wm_type_menu || (t) == net_wm_type_splash || \
    (t) == net_wm_type_desk || (t) == net_wm_type_notif)
#define HAS_DECOR(t) (!CAN_PLACE_SELF(t))
#define DESK_ALL 0xFFFFFFFF
#define IS_ON_DESK(w, d) (w == d || w == DESK_ALL)
#define IS_ON_CUR_DESK(c) \
	(IS_ON_DESK((c)->desk, cur_desk) || (c)->state & STATE_ICONIFIED)
#define IS_RESIZE_WIN(c, w) (w == c->resize_nw || w == c->resize_w || \
	w == c->resize_sw || w == c->resize_s || w == c->resize_se || \
	w == c->resize_e || w == c->resize_ne || w == c->resize_n)

#ifdef DEBUG
#define SHOW_EV(name, memb) \
    case name: \
    	snprintf(ev_type, sizeof(ev_type), #name); \
	w = e.memb.window; \
	break;
#define SHOW(name) \
    case name: \
    	return #name;
#endif

typedef struct geom geom_t;
struct geom {
	long x;
	long y;
	long w;
	long h;
};

/* types of bindings */
enum {
	BINDING_TYPE_KEYBOARD,
	BINDING_TYPE_DESKTOP,
	BINDING_TYPE_DRAG,
};

/* keyboard and mouse bindings */
typedef struct action action_t;
struct action {
	int type;
	KeySym key;
	unsigned int mod;
	unsigned int button;
	int action;
	int iarg;
	char *sarg;
};
extern action_t *key_actions;
extern int nkey_actions;

/* types of actions in action_t.action */
enum {
	ACTION_INVALID = -2,
	ACTION_NONE = -1,
	ACTION_CYCLE = 1,
	ACTION_REVERSE_CYCLE,
	ACTION_DESK,
	ACTION_DESK_NEXT,
	ACTION_DESK_PREVIOUS,
	ACTION_CLOSE,
	ACTION_EXEC,
	ACTION_LAUNCHER,
	ACTION_RESTART,
	ACTION_QUIT,
	ACTION_DRAG,
};

/* client_t state */
enum {
	STATE_NORMAL = 0,
	STATE_ZOOMED = (1 << 1),
	STATE_SHADED = (1 << 2),
	STATE_FULLSCREEN = (1 << 3),
	STATE_ICONIFIED = (1 << 4),
	STATE_DOCK = (1 << 5),
	STATE_ABOVE = (1 << 6),
	STATE_BELOW = (1 << 7),
};

/* client_t frame_style */
enum {
	FRAME_NONE = 0,
	FRAME_BORDER = (1 << 1),
	FRAME_RESIZABLE = (1 << 2),
	FRAME_TITLEBAR = (1 << 3),
	FRAME_CLOSE = (1 << 4),
	FRAME_ICONIFY = (1 << 5),
	FRAME_ZOOM = (1 << 6),
	FRAME_ALL = FRAME_BORDER | FRAME_RESIZABLE | FRAME_TITLEBAR |
	    FRAME_CLOSE | FRAME_ICONIFY | FRAME_ZOOM,
};

typedef struct client client_t;
struct client {
	client_t *next;
	char *name;
	XftDraw *xftdraw;
	Window win, trans;
	geom_t geom, save;
	Window frame;
	geom_t frame_geom;
	unsigned int frame_style;
	Window close;
	geom_t close_geom;
	Bool close_pressed;
	Window titlebar;
	geom_t titlebar_geom;
	Window iconify;
	geom_t iconify_geom;
	Bool iconify_pressed;
	Window zoom;
	geom_t zoom_geom;
	Bool zoom_pressed;
	int border_width;
	Window resize_nw;
	geom_t resize_nw_geom;
	Window resize_n;
	geom_t resize_n_geom;
	Window resize_ne;
	geom_t resize_ne_geom;
	Window resize_e;
	geom_t resize_e_geom;
	Window resize_se;
	geom_t resize_se_geom;
	Window resize_s;
	geom_t resize_s_geom;
	Window resize_sw;
	geom_t resize_sw_geom;
	Window resize_w;
	geom_t resize_w_geom;
	Window icon;
	geom_t icon_geom;
	Window icon_label;
	geom_t icon_label_geom;
	Pixmap icon_pixmap;
	Pixmap icon_mask;
	int icon_managed;
	GC icon_gc;
	char *icon_name;
	XftDraw *icon_xftdraw;
	int icon_depth;
	XWMHints *wm_hints;
	XSizeHints size_hints;
	Colormap cmap;
	int ignore_unmap;
	unsigned long desk;
	Bool placed;
	Bool shaped;
	int state;
#define MAX_WIN_TYPE_ATOMS 5
	Atom win_type[MAX_WIN_TYPE_ATOMS];
	int old_bw;
};

typedef struct xft_line xft_line_t;
struct xft_line_t {
	char *str;
	unsigned int len;
	unsigned int xft_width;
};

typedef void sweep_func(client_t *, geom_t, int, int, int, int, strut_t *,
    void *);

enum {
	MATCH_WINDOW,
	MATCH_FRAME,
	MATCH_ANY,
};	/* find_client */
enum {
	DEL_WITHDRAW,
	DEL_REMAP,
};	/* del_client */

enum {
	ORDER_TOP,
	ORDER_ICONIFIED_TOP,
	ORDER_BOTTOM,
	ORDER_OUT,
	ORDER_INVERT,
};	/* adjust_client_order */

enum {
	FOCUS_NORMAL,
	FOCUS_FORCE,
};	/* focus_client */

/* progman.c */
extern char *orig_argv0;
extern Display *dpy;
extern Window root;
extern client_t *focused, *dragging;
extern int screen;
extern int ignore_xerrors;
extern unsigned long cur_desk;
extern unsigned long ndesks;
extern Bool shape_support;
extern int shape_event;
extern Window supporting_wm_win;
extern int icon_size;
extern XftFont *font;
extern XftFont *iconfont;
extern XftColor xft_fg;
extern XftColor xft_fg_unfocused;
extern XftColor xft_launcher;
extern XftColor xft_launcher_highlighted;
extern Colormap cmap;
extern XColor fg;
extern XColor bg;
extern XColor unfocused_fg;
extern XColor unfocused_bg;
extern XColor button_bg;
extern XColor bevel_dark;
extern XColor bevel_light;
extern XColor border_fg;
extern XColor border_bg;
extern XColor launcher_fg;
extern XColor launcher_bg;
extern GC pixmap_gc;
extern GC invert_gc;
extern Pixmap close_pm;
extern Pixmap close_pm_mask;
extern XpmAttributes close_pm_attrs;
extern Pixmap utility_close_pm;
extern Pixmap utility_close_pm_mask;
extern XpmAttributes utility_close_pm_attrs;
extern Pixmap iconify_pm;
extern Pixmap iconify_pm_mask;
extern XpmAttributes iconify_pm_attrs;
extern Pixmap zoom_pm;
extern Pixmap zoom_pm_mask;
extern XpmAttributes zoom_pm_attrs;
extern Pixmap unzoom_pm;
extern Pixmap unzoom_pm_mask;
extern XpmAttributes unzoom_pm_attrs;
extern Pixmap default_icon_pm;
extern Pixmap default_icon_pm_mask;
extern XpmAttributes default_icon_pm_attrs;
extern Cursor map_curs;
extern Cursor move_curs;
extern Cursor resize_n_curs;
extern Cursor resize_s_curs;
extern Cursor resize_e_curs;
extern Cursor resize_w_curs;
extern Cursor resize_nw_curs;
extern Cursor resize_sw_curs;
extern Cursor resize_ne_curs;
extern Cursor resize_se_curs;
extern char *opt_config_file;
extern char *opt_font;
extern char *opt_iconfont;
extern char *opt_fg;
extern char *opt_bg;
extern char *opt_unfocused_fg;
extern char *opt_unfocused_bg;
extern char *opt_button_bg;
extern char *opt_bevel_dark;
extern char *opt_bevel_light;
extern char *opt_border_fg;
extern char *opt_border_bg;
extern char *opt_root_bg;
extern int opt_bevel;
extern int opt_bw;
extern int opt_pad;
extern int opt_edge_resist;
extern int opt_scale;
extern int opt_drag_button;
extern int opt_drag_mod;
extern void sig_handler(int signum);
extern int exitmsg[2];

/* progman.c */
void cleanup(void);
void quit(void);

/* event.c */
extern void event_loop(void);
extern int handle_xerror(Display *, XErrorEvent *);
extern void handle_unmap_event(XUnmapEvent *);
#ifdef DEBUG
extern void show_event(XEvent);
#endif

/* client.c */
extern client_t *new_client(Window);
extern client_t *find_client(Window, int);
extern client_t *find_client_at_coords(Window, int, int);
extern client_t *top_client(void);
extern client_t *prev_focused(int);
extern void map_client(client_t *);
extern void update_size_hints(client_t *);
extern int has_win_type(client_t *, Atom);
extern void recalc_frame(client_t *);
extern int set_wm_state(client_t *, unsigned long);
extern void check_states(client_t *);
extern void parse_state_atom(client_t *, Atom);
extern void send_config(client_t *);
extern void redraw_frame(client_t *, Window);
extern void collect_struts(client_t *, strut_t *);
extern void get_client_icon(client_t *);
extern void redraw_icon(client_t *, Window);
extern void set_shape(client_t *);
extern void del_client(client_t *, int);

/* manage.c */
extern void user_action(client_t *, Window, int, int, int, int);
extern int pos_in_frame(client_t *, int, int);
extern Cursor cursor_for_resize_win(client_t *, Window);
extern void focus_client(client_t *, int);
extern void move_client(client_t *);
extern void resize_client(client_t *, Window);
extern void iconify_client(client_t *);
extern void uniconify_client(client_t *);
extern void place_icon(client_t *);
extern void shade_client(client_t *);
extern void unshade_client(client_t *);
extern void fullscreen_client(client_t *);
extern void unfullscreen_client(client_t *);
extern void zoom_client(client_t *);
extern void unzoom_client(client_t *);
extern void send_wm_delete(client_t *);
extern void goto_desk(int);
extern void map_if_desk(client_t *);
extern void sweep(client_t *, Cursor, sweep_func, void *, strut_t *);
extern void recalc_map(client_t *, geom_t, int, int, int, int, strut_t *,
    void *);
extern void recalc_move(client_t *, geom_t, int, int, int, int, strut_t *,
    void *);
extern void recalc_resize(client_t *, geom_t, int, int, int, int, strut_t *,
    void *);
extern void fix_size(client_t *);
extern void constrain_frame(client_t *);
extern char *state_name(client_t *);
extern void flush_expose_client(client_t *);
extern void flush_expose(Window);
extern int overlapping_geom(geom_t, geom_t);
extern void restack_clients(void);
extern void adjust_client_order(client_t *, int);
extern client_t *next_client_for_focus(client_t *);
#ifdef DEBUG
extern void dump_name(client_t *, const char *, const char *, const char *);
extern void dump_info(client_t *);
extern void dump_geom(client_t *, geom_t, const char *);
extern void dump_removal(client_t *, int);
extern void dump_clients(void);
extern const char *frame_name(client_t *, Window);
#endif

/* keyboard.c */
extern void bind_keys(void);
extern void handle_key_event(XKeyEvent *);

/* launcher.c */
extern Window launcher_win;
extern void launcher_setup(void);
extern void launcher_show(XButtonEvent *);
extern void launcher_programs_free(void);
extern client_t *cycle_head;

/* util.c */
extern void fork_exec(char *);
extern int get_pointer(int *, int *);
extern int send_xmessage(Window, Window, Atom, unsigned long, unsigned long);
extern action_t *bind_key(int, char *, char *);
extern void take_action(action_t *);
extern action_t *parse_action(char *, char *);

#endif	/* PROGMAN_H */


================================================
FILE: progman.ini
================================================
#
# This is the configuration file for progman, and is optional.  It should exist
# at ~/.config/progman/progman.ini
#
# Lines starting with '#' are ignored as comments.
#

[progman]
font = Microsoft Sans Serif:bold:size=14
iconfont = Microsoft Sans Serif:size=11

# Focused windows
fgcolor = white
bgcolor = #0000a8

# Unfocused windows
unfocused_fgcolor = black
unfocused_bgcolor = white

# Borders
border_fgcolor = black
border_bgcolor = #c0c7c8
border_width = 6
button_bgcolor = #c0c7c8
title_padding = 6

# For HiDPI displays, how many times to scale icons and buttons
scale = 2

# Launcher
launcher_fgcolor = black
launcher_bgcolor = #c0c7c8

# When not specified, the root color is not changed
#root_bgcolor = #c0c7c8

# Move windows by holding down this key and mouse button
drag_combo = Alt+Mouse1

# When moving windows, how hard to resist going off-screen
edgeresist = 80

# Custom key bindings can be specified as "Modifier+Key = action".
[keyboard]
Alt+Tab = cycle
Shift+Alt+Tab = reverse_cycle
Alt+F4 = close
Alt+1 = desk 0
Alt+2 = desk 1
Alt+3 = desk 2
Alt+4 = desk 3
Alt+5 = desk 4
Alt+6 = desk 5
Alt+7 = desk 6
Alt+8 = desk 7
Alt+9 = desk 8
Alt+0 = desk 9
Win+T = exec xterm

# Mouse clicks on desktop: right click reveals launcher, middle click launches
# terminal, wheel navigates desktops
[desktop]
Mouse2 = exec xterm
Mouse3 = launcher
Mouse4 = desk next
Mouse5 = desk previous

# When the launcher action is performed from a key binding or desktop click,
# this list will be shown; actions are the same as keyboard bindings
[launcher]
Xterm = exec xterm
Firefox = exec firefox
XCalc = exec xcalc
XEyes = exec xeyes
XClock = exec xclock
Lock = exec pkill -USR1 xidle
Restart = restart
Quit = quit


================================================
FILE: tests/Makefile
================================================
#
# Copyright 2020 joshua stein <jcs@jcs.org>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#

PKGLIBS=	x11 xft

CC?=		cc
CFLAGS+=	-O2 -Wall -Wunused \
		-Wunused -Wmissing-prototypes -Wstrict-prototypes \
		-Wpointer-sign \
		`pkg-config --cflags ${PKGLIBS}`
LDFLAGS+=	`pkg-config --libs ${PKGLIBS}`

BIN=		geometry \
		no-resize \
		win-type-utility

all: $(BIN)

atom.o: ../atom.c
harness.o: harness.c

$(BIN): atom.o harness.o $@.c
	$(CC) $(CFLAGS) -o $@ $@.c atom.o harness.o $(LDFLAGS)

clean:
	rm -f $(BIN) *.o

.PHONY: all install clean


================================================
FILE: tests/geometry.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "harness.h"

void
setup(int argc, char **argv)
{
	XSizeHints *hints;

	hints = XAllocSizeHints();
	if (!hints)
		err(1, "XAllocSizeHints");

	hints->flags = PPosition | PSize;
	hints->x = 10;
	hints->y = 10;
	hints->width = 200;
	hints->height = 200;

	XSetWMNormalHints(dpy, win, hints);
}

void
process_event(XEvent *ev)
{
}


================================================
FILE: tests/harness.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "harness.h"
#include <libgen.h>

Display *dpy;
Window win, root;
int screen;
int ignore_xerrors = 0;

int
main(int argc, char **argv)
{
	XEvent ev;
	XTextProperty name;
	char *title = basename(argv[0]);
	int screen;

	dpy = XOpenDisplay(NULL);
	if (!dpy)
		err(1, "can't open $DISPLAY");

	screen = DefaultScreen(dpy);
	root = RootWindow(dpy, screen);

	find_supported_atoms();

	win = XCreateWindow(dpy, root, 0, 0, 300, 200, 0,
	    DefaultDepth(dpy, screen), CopyFromParent,
	    DefaultVisual(dpy, screen), 0, NULL);
	if (!win)
		err(1, "XCreateWindow");

	if (!XStringListToTextProperty(&title, 1, &name))
		err(1, "!XStringListToTextProperty");
	XSetWMName(dpy, win, &name);

	XSetWindowBackground(dpy, win, WhitePixel(dpy, screen));
	XSelectInput(dpy, win, KeyPressMask);

	setup(argc, argv);

	XMapWindow(dpy, win);

	for (;;) {
		XNextEvent(dpy, &ev);

		switch (ev.type) {
		case KeyPress: {
			KeySym kc = XLookupKeysym(&ev.xkey, 0);
			if (kc == XK_Escape)
				exit(0);
			break;
		}
		}

		process_event(&ev);
	}

	return 0;
}


================================================
FILE: tests/harness.h
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "../atom.h"

extern Display *dpy;
extern Window win;
extern int screen;

extern void setup(int, char **);
extern void process_event(XEvent *ev);


================================================
FILE: tests/no-resize.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "harness.h"

void
setup(int argc, char **argv)
{
	XSizeHints *hints;

	hints = XAllocSizeHints();
	if (!hints)
		err(1, "XAllocSizeHints");

	hints->flags = PMinSize | PMaxSize;
	hints->min_width = 300;
	hints->min_height = 200;
	hints->max_width = 300;
	hints->max_height = 200;

	XSetWMNormalHints(dpy, win, hints);
}

void
process_event(XEvent *ev)
{
}


================================================
FILE: tests/win-type-utility.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "harness.h"

void
setup(int argc, char **argv)
{
	set_atoms(win, net_wm_state, XA_ATOM, &net_wm_state_above, 1);
	set_atoms(win, net_wm_wintype, XA_ATOM, &net_wm_type_utility, 1);
}

void
process_event(XEvent *ev)
{
}


================================================
FILE: themes/hotdogstand.ini
================================================
# Hotdog Stand
[progman]
fgcolor = white
bgcolor = black
unfocused_fgcolor = white
unfocused_bgcolor = red
border_bgcolor = red
border_fgcolor = black
root_bgcolor = yellow


================================================
FILE: util.c
================================================
/*
 * Copyright 2020 joshua stein <jcs@jcs.org>
 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <sys/types.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "progman.h"

void
fork_exec(char *cmd)
{
	pid_t pid = fork();

	switch (pid) {
	case 0:
		setsid();
		execl("/bin/sh", "sh", "-c", cmd, NULL);
		fprintf(stderr, "exec failed, cleaning up child\n");
		exit(1);
	case -1:
		fprintf(stderr, "can't fork\n");
	}
}

int
get_pointer(int *x, int *y)
{
	Window real_root, real_win;
	int wx, wy;
	unsigned int mask;

	XQueryPointer(dpy, root, &real_root, &real_win, x, y, &wx, &wy, &mask);
	return mask;
}

int
send_xmessage(Window t, Window w, Atom a, unsigned long x, unsigned long mask)
{
	XClientMessageEvent e;

	e.type = ClientMessage;
	e.window = w;
	e.message_type = a;
	e.format = 32;
	e.data.l[0] = x;
	e.data.l[1] = CurrentTime;

	return XSendEvent(dpy, t, False, mask, (XEvent *)&e);
}

action_t *
parse_action(char *prefix, char *action)
{
	char *taction = NULL, *targ = NULL;
	char *sep;
	char *sarg = NULL;
	action_t *out = NULL;
	int iaction = ACTION_NONE;
	int iarg = 0;

	taction = strdup(action);
	if ((sep = strchr(taction, ' '))) {
		*sep = '\0';
		targ = sep + 1;
	} else
		targ = NULL;

	if (strcmp(taction, "cycle") == 0)
		iaction = ACTION_CYCLE;
	else if (strcmp(taction, "reverse_cycle") == 0)
		iaction = ACTION_REVERSE_CYCLE;
	else if (strcmp(taction, "desk") == 0)
		iaction = ACTION_DESK;
	else if (strcmp(taction, "close") == 0)
		iaction = ACTION_CLOSE;
	else if (strcmp(taction, "exec") == 0)
		iaction = ACTION_EXEC;
	else if (strcmp(taction, "launcher") == 0)
		iaction = ACTION_LAUNCHER;
	else if (strcmp(taction, "restart") == 0)
		iaction = ACTION_RESTART;
	else if (strcmp(taction, "quit") == 0)
		iaction = ACTION_QUIT;
	else if (strcmp(taction, "drag") == 0)
		iaction = ACTION_DRAG;
	else if (taction[0] == '\n' || taction[0] == '\0')
		iaction = ACTION_NONE;
	else
		iaction = ACTION_INVALID;

	/* parse numeric or string args */
	switch (iaction) {
	case ACTION_DESK:
		if (targ == NULL) {
			warnx("%s: missing argument for \"%s\"",
			    prefix, taction);
			goto done;
		}

		if (strcmp(targ, "next") == 0)
			iaction = ACTION_DESK_NEXT;
		else if (strcmp(targ, "previous") == 0)
			iaction = ACTION_DESK_PREVIOUS;
		else {
			errno = 0;
			iarg = strtol(targ, NULL, 10);
			if (errno != 0) {
				warnx("%s: failed parsing numeric argument "
				    "\"%s\" for \"%s\"", prefix, targ, taction);
				goto done;
			}
		}
		break;
	case ACTION_EXEC:
		if (targ == NULL) {
			warnx("%s: missing string argument for \"%s\"", prefix,
			    taction);
			goto done;
		}
		sarg = strdup(targ);
		break;
	case ACTION_INVALID:
		warnx("%s: invalid action \"%s\"", prefix, taction);
		goto done;
	default:
		/* no args expected of other commands */
		if (targ != NULL) {
			warnx("%s: unexpected argument \"%s\" for \"%s\"",
			    prefix, taction, targ);
			goto done;
		}
	}

	out = malloc(sizeof(action_t));
	out->action = iaction;
	out->iarg = iarg;
	out->sarg = sarg;

done:
	if (taction)
		free(taction);

	return out;
}

void
take_action(action_t *action)
{
	client_t *p, *next;

	switch (action->action) {
	case ACTION_CYCLE:
	case ACTION_REVERSE_CYCLE:
		if (!cycle_head) {
			if (!focused)
				return;

			cycle_head = focused;
		}

		if ((next = next_client_for_focus(cycle_head)))
			focus_client(next, FOCUS_FORCE);
		else {
			/* probably at the end of the list, invert it */
			p = focused;
			adjust_client_order(NULL, ORDER_INVERT);

			if (p)
				/* p should now not be focused */
				redraw_frame(p, None);

			focus_client(cycle_head, FOCUS_FORCE);
		}
		break;
	case ACTION_DESK:
		goto_desk(action->iarg);
		break;
	case ACTION_DESK_NEXT:
		if (cur_desk < ndesks - 1)
			goto_desk(cur_desk + 1);
		break;
	case ACTION_DESK_PREVIOUS:
		if (cur_desk > 0)
			goto_desk(cur_desk - 1);
		break;
	case ACTION_CLOSE:
		if (focused)
			send_wm_delete(focused);
		break;
	case ACTION_EXEC:
		fork_exec(action->sarg);
		break;
	case ACTION_LAUNCHER:
		launcher_show(NULL);
		break;
	case ACTION_RESTART:
		cleanup();
		execlp(orig_argv0, orig_argv0, NULL);
		break;
	case ACTION_QUIT:
		quit();
		break;
	default:
		warnx("unhandled action %d\n", action->action);
	}
}
Download .txt
gitextract_0g4nf18d/

├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── atom.c
├── atom.h
├── client.c
├── events.c
├── icons/
│   ├── close.xpm
│   ├── default_icon.xpm
│   ├── hidpi-close.xpm
│   ├── hidpi-default_icon.xpm
│   ├── hidpi-iconify.xpm
│   ├── hidpi-unzoom.xpm
│   ├── hidpi-utility_close.xpm
│   ├── hidpi-zoom.xpm
│   ├── iconify.xpm
│   ├── unzoom.xpm
│   ├── utility_close.xpm
│   └── zoom.xpm
├── keyboard.c
├── launcher.c
├── manage.c
├── parser.c
├── parser.h
├── progman.c
├── progman.h
├── progman.ini
├── tests/
│   ├── Makefile
│   ├── geometry.c
│   ├── harness.c
│   ├── harness.h
│   ├── no-resize.c
│   └── win-type-utility.c
├── themes/
│   └── hotdogstand.ini
└── util.c
Download .txt
SYMBOL INDEX (128 symbols across 15 files)

FILE: atom.c
  function find_supported_atoms (line 74) | void
  function get_atoms (line 188) | unsigned long
  function set_atoms (line 222) | unsigned long
  function append_atoms (line 230) | unsigned long
  function remove_atom (line 238) | void
  function set_string_atom (line 381) | void
  function get_strut (line 396) | int
  function get_wm_state (line 433) | unsigned long

FILE: atom.h
  type strut (line 28) | struct strut {
  type strut_t (line 35) | typedef struct strut strut_t;

FILE: client.c
  function client_t (line 49) | client_t *
  function client_t (line 118) | client_t *
  function client_t (line 154) | client_t *
  function client_t (line 200) | client_t *
  function map_client (line 224) | void
  function update_size_hints (line 297) | void
  function init_geom (line 331) | static void
  function reparent (line 473) | static void
  function has_win_type (line 558) | int
  function recalc_frame (line 571) | void
  function set_wm_state (line 753) | int
  function check_states (line 759) | void
  function send_config (line 822) | void
  function redraw_frame (line 841) | void
  function bevel (line 1329) | static void
  function get_client_icon (line 1365) | void
  function redraw_icon (line 1507) | void
  function collect_struts (line 1603) | void
  function set_shape (line 1637) | void
  function del_client (line 1693) | void
  type xft_line_t (line 1777) | struct xft_line_t
  type xft_line_t (line 1783) | struct xft_line_t
  type xft_line_t (line 1793) | struct xft_line_t
  type xft_line_t (line 1847) | struct xft_line_t

FILE: events.c
  function event_loop (line 50) | void
  function handle_button_press (line 137) | static void
  function handle_button_release (line 190) | static void
  function handle_configure_request (line 221) | static void
  function handle_circulate_request (line 293) | static void
  function handle_map_request (line 326) | static void
  function handle_unmap_event (line 351) | void
  function handle_destroy_event (line 368) | static void
  function handle_client_message (line 377) | static void
  function handle_property_change (line 426) | static void
  function handle_enter_event (line 506) | static void
  function handle_cmap_change (line 520) | static void
  function handle_expose_event (line 536) | static void
  function handle_shape_change (line 545) | static void
  function show_event (line 555) | void

FILE: keyboard.c
  function action_t (line 34) | action_t *
  function handle_key_event (line 166) | void

FILE: launcher.c
  type program (line 27) | struct program {
  type program (line 35) | struct program
  function launcher_setup (line 42) | void
  function launcher_reload (line 85) | void
  function launcher_show (line 144) | void
  function launcher_programs_free (line 233) | void
  function launcher_redraw (line 255) | void

FILE: manage.c
  type timespec (line 41) | struct timespec
  function user_action (line 47) | void
  function Cursor (line 155) | Cursor
  function focus_client (line 179) | void
  function move_client (line 246) | void
  function resize_client (line 272) | void
  function maybe_toolbar_click (line 298) | void
  function monitor_toolbar_click (line 315) | void
  function iconify_client (line 351) | void
  function do_iconify (line 365) | void
  function uniconify_client (line 418) | void
  function place_icon (line 445) | void
  function shade_client (line 493) | void
  function unshade_client (line 504) | void
  function do_shade (line 515) | static void
  function fullscreen_client (line 537) | void
  function unfullscreen_client (line 564) | void
  function zoom_client (line 590) | void
  function unzoom_client (line 619) | void
  function send_wm_delete (line 639) | void
  function goto_desk (line 658) | void
  function map_if_desk (line 703) | void
  function sweep (line 713) | void
  function recalc_map (line 775) | void
  function recalc_move (line 791) | void
  function recalc_resize (line 875) | void
  function fix_size (line 938) | void
  function constrain_frame (line 976) | void
  function flush_expose_client (line 1028) | void
  function flush_expose (line 1062) | void
  function overlapping_geom (line 1070) | int
  function restack_clients (line 1080) | void
  function adjust_client_order (line 1147) | void
  function client_t (line 1256) | client_t *
  function SHOW (line 1345) | SHOW(UnmapGravity)
  function dump_info (line 1369) | void
  function dump_geom (line 1382) | void
  function dump_removal (line 1389) | void
  function dump_clients (line 1396) | void

FILE: parser.c
  function FILE (line 33) | FILE *
  function find_ini_section (line 60) | int
  function get_ini_kv (line 78) | int

FILE: progman.c
  function main (line 159) | int
  function read_config (line 213) | void
  function setup_display (line 302) | void
  function scale_icon (line 457) | void
  function sig_handler (line 521) | void
  function quit (line 541) | void
  function handle_xerror (line 551) | int
  function cleanup (line 573) | void

FILE: progman.h
  type geom_t (line 99) | typedef struct geom geom_t;
  type geom (line 100) | struct geom {
  type action_t (line 115) | typedef struct action action_t;
  type action (line 116) | struct action {
  type client_t (line 170) | typedef struct client client_t;
  type client (line 171) | struct client {
  type xft_line_t (line 232) | typedef struct xft_line xft_line_t;
  type xft_line_t (line 233) | struct xft_line_t {

FILE: tests/geometry.c
  function setup (line 24) | void
  function process_event (line 42) | void

FILE: tests/harness.c
  function main (line 30) | int

FILE: tests/no-resize.c
  function setup (line 24) | void
  function process_event (line 42) | void

FILE: tests/win-type-utility.c
  function setup (line 24) | void
  function process_event (line 31) | void

FILE: util.c
  function fork_exec (line 34) | void
  function get_pointer (line 50) | int
  function send_xmessage (line 61) | int
  function action_t (line 76) | action_t *
  function take_action (line 171) | void
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (213K chars).
[
  {
    "path": ".gitignore",
    "chars": 26,
    "preview": "progman\nprogman_ini.h\n*.o\n"
  },
  {
    "path": "LICENSE",
    "chars": 1116,
    "preview": "MIT License\n\nCopyright 2020 joshua stein <jcs@jcs.org>\nCopyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n\nPermi"
  },
  {
    "path": "Makefile",
    "chars": 2042,
    "preview": "#\n# Copyright 2020 joshua stein <jcs@jcs.org>\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n"
  },
  {
    "path": "README.md",
    "chars": 1444,
    "preview": "## progman\n\nprogman is a simple X11 window manager modeled after the Windows 3 era.\n\n![progman screenshot](https://jcs.o"
  },
  {
    "path": "atom.c",
    "chars": 13535,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Perm"
  },
  {
    "path": "atom.h",
    "chars": 3144,
    "preview": "/*\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Permission is hereby granted, free of charge, to "
  },
  {
    "path": "client.c",
    "chars": 53432,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Perm"
  },
  {
    "path": "events.c",
    "chars": 16307,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Perm"
  },
  {
    "path": "icons/close.xpm",
    "chars": 363,
    "preview": "/* XPM */\nstatic char * close_xpm[] = {\n\"14 14 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888F\",\n\"         "
  },
  {
    "path": "icons/default_icon.xpm",
    "chars": 1300,
    "preview": "/* XPM */\nstatic char * default_icon_xpm[] = {\n\"32 32 6 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #808080\",\n\"@\tc #C0C0C0\",\n\"#\t"
  },
  {
    "path": "icons/hidpi-close.xpm",
    "chars": 298,
    "preview": "/* XPM */\nstatic char * hidpi_close_xpm[] = {\n\"22 7 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888F\",\n\"...."
  },
  {
    "path": "icons/hidpi-default_icon.xpm",
    "chars": 4506,
    "preview": "/* XPM */\nstatic char * hidpi_default_icon_xpm[] = {\n\"64 64 6 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #00FFFF\",\n\"@\tc #808080"
  },
  {
    "path": "icons/hidpi-iconify.xpm",
    "chars": 241,
    "preview": "/* XPM */\nstatic char * hidpi_iconify_xpm[] = {\n\"13 9 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"             \",\n\"             \","
  },
  {
    "path": "icons/hidpi-unzoom.xpm",
    "chars": 394,
    "preview": "/* XPM */\nstatic char * hidpi_unzoom_xpm[] = {\n\"13 18 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"      .      \",\n\"     ...     \","
  },
  {
    "path": "icons/hidpi-utility_close.xpm",
    "chars": 377,
    "preview": "/* XPM */\nstatic char * hidpi_utility_close_xpm[] = {\n\"14 14 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888"
  },
  {
    "path": "icons/hidpi-zoom.xpm",
    "chars": 238,
    "preview": "/* XPM */\nstatic char * hidpi_zoom_xpm[] = {\n\"13 9 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"      .      \",\n\"     ...     \",\n\" "
  },
  {
    "path": "icons/iconify.xpm",
    "chars": 159,
    "preview": "/* XPM */\nstatic char * iconify_xpm[] = {\n\"9 6 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"         \",\n\".........\",\n\" ....... \",\n\""
  },
  {
    "path": "icons/unzoom.xpm",
    "chars": 237,
    "preview": "/* XPM */\nstatic char * unzoom_xpm[] = {\n\"9 12 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"    .    \",\n\"   ...   \",\n\"  .....  \",\n\""
  },
  {
    "path": "icons/utility_close.xpm",
    "chars": 194,
    "preview": "/* XPM */\nstatic char * utility_close_xpm[] = {\n\"7 7 4 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #FFFFFF\",\n\"@\tc #87888F\",\n\"   "
  },
  {
    "path": "icons/zoom.xpm",
    "chars": 156,
    "preview": "/* XPM */\nstatic char * zoom_xpm[] = {\n\"9 6 2 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"    .    \",\n\"   ...   \",\n\"  .....  \",\n\" .."
  },
  {
    "path": "keyboard.c",
    "chars": 6227,
    "preview": "/*\n * Copyright (c) 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "launcher.c",
    "chars": 6847,
    "preview": "/*\n * Copyright (c) 2021 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "manage.c",
    "chars": 31583,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Perm"
  },
  {
    "path": "parser.c",
    "chars": 3312,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "parser.h",
    "chars": 1310,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "progman.c",
    "chars": 17389,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Perm"
  },
  {
    "path": "progman.h",
    "chars": 11871,
    "preview": "/*\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Permission is hereby granted, free of charge, to "
  },
  {
    "path": "progman.ini",
    "chars": 1718,
    "preview": "#\n# This is the configuration file for progman, and is optional.  It should exist\n# at ~/.config/progman/progman.ini\n#\n#"
  },
  {
    "path": "tests/Makefile",
    "chars": 1536,
    "preview": "#\n# Copyright 2020 joshua stein <jcs@jcs.org>\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n"
  },
  {
    "path": "tests/geometry.c",
    "chars": 1440,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "tests/harness.c",
    "chars": 2153,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "tests/harness.h",
    "chars": 1380,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "tests/no-resize.c",
    "chars": 1469,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "tests/win-type-utility.c",
    "chars": 1331,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n *\n * Permission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "themes/hotdogstand.ini",
    "chars": 173,
    "preview": "# Hotdog Stand\n[progman]\nfgcolor = white\nbgcolor = black\nunfocused_fgcolor = white\nunfocused_bgcolor = red\nborder_bgcolo"
  },
  {
    "path": "util.c",
    "chars": 5417,
    "preview": "/*\n * Copyright 2020 joshua stein <jcs@jcs.org>\n * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.\n *\n * Perm"
  }
]

About this extraction

This page contains the full source code of the jcs/progman GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (190.1 KB), approximately 59.0k tokens, and a symbol index with 128 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!