/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "plugutil.h"

/* per-screen structure */
static struct gnomescr {
	Window		wm_check; /* check window, also used as button proxy */
} *gnomescr = NULL;

/* win_state bitmask values */
#define WIN_STATE_STICKY		(1L << 0)
#define WIN_STATE_MINIMIZED		(1L << 1) /* ignored */
#define WIN_STATE_MAXIMIZED_VERT	(1L << 2) /* ignored */
#define WIN_STATE_MAXIMIZED_HORIZ	(1L << 3) /* ignored */
#define WIN_STATE_HIDDEN		(1L << 4) /* ignored */
#define WIN_STATE_SHADED		(1L << 5) /* ignored */
#define WIN_STATE_HID_WORKSPACE		(1L << 6) /* ignored */
#define WIN_STATE_HID_TRANSIENT		(1L << 7) /* ignored */
#define WIN_STATE_FIXED_POSITION	(1L << 8)
#define WIN_STATE_ARRANGE_IGNORE	(1L << 9) /* ignored */

/* win_hints bitmask values */
#define WIN_HINTS_SKIP_FOCUS		(1L << 0)
#define WIN_HINTS_SKIP_WINLIST		(1L << 1) /* ignored */
#define WIN_HINTS_SKIP_TASKBAR		(1L << 2) /* ignored */
#define WIN_HINTS_GROUP_TRANSIENT	(1L << 3) /* ignored */
#define WIN_HINTS_FOCUS_ON_CLICK	(1L << 4) /* ignored */
#define WIN_HINTS_DO_NOT_COVER		(1L << 5) /* ignored */

/* win_layer values */
#define WIN_LAYER_DESKTOP		0
#define WIN_LAYER_BELOW			2
#define WIN_LAYER_NORMAL		4
#define WIN_LAYER_ONTOP			6
#define WIN_LAYER_DOCK			8
#define WIN_LAYER_ABOVE_DOCK		10
#define WIN_LAYER_MENU			12

/* atoms */
static Atom	win_supporting_wm_check;
static Atom	win_state;
static Atom	win_hints;
static Atom	win_layer;

/* macros to get protocol atoms */
#define NUM_PROTOCOLS			6
#define WIN_CLIENT_LIST			win_protocols_list[0]
#define WIN_DESKTOP_BUTTON_PROXY	win_protocols_list[1]
#define WIN_WORKSPACE			win_protocols_list[2]
#define WIN_WORKSPACE_COUNT		win_protocols_list[3]
#define WIN_AREA_COUNT			win_protocols_list[4]
#define WIN_AREA			win_protocols_list[5]

/* _WIN_PROTOCOLS stuff */
static Atom win_protocols;
static Atom win_protocols_list[NUM_PROTOCOLS];
static char *win_protocols_names[NUM_PROTOCOLS] = {
	"_WIN_CLIENT_LIST",
	"_WIN_DESKTOP_BUTTON_PROXY",
	"_WIN_WORKSPACE",
	"_WIN_WORKSPACE_COUNT",
	"_WIN_AREA_COUNT",
	"_WIN_AREA"
};

/* set the list of clients for screen */
static int set_client_list(screen_t *screen) {
	Window *list, *tmp;
	client_t *client;
	int size, count;

	count = 0;
	size = 10;
	list = malloc(sizeof(Window) * size);
	if (!list)
		return -1;

	/* recreate the list of clients */
	LIST_FOREACH(client, &client_list, c_list)
		if (!client->flags.internal && client->screen == screen) {
			if (++count > size) {
				tmp = realloc(list, sizeof(Window) * (size + size));
				if (!tmp) {
					free(list);
					return -1;
				}
				list = tmp;
				size += size;
			}
			list[count - 1] = client->window;
		}

	/* put it on the root window */
	XChangeProperty(display, screen->root, WIN_CLIENT_LIST, XA_CARDINAL, 32,
		PropModeReplace, (u_char *) list, count);
	free(list);
	return 0;
}

/* handle hints for newly arriving clients */
static int init_hints(int pcall, client_t *client) {
	CARD32 *card;
	Atom type;
	u_long items, bytes;
	int fmt;

	/* _WIN_STATE hint */
	if (XGetWindowProperty(display, client->window, win_state, 0, 1, 0,
			XA_CARDINAL, &type, &fmt, &items, &bytes,
			(u_char **) &card) == Success && card) {
		if (*card & WIN_STATE_STICKY)
			client->flags.sticky = 1;
		if (*card & WIN_STATE_FIXED_POSITION) {
			client->flags.noresize = 1;
			client->flags.nomove = 1;
		}
		XFree(card);
	}

	/* _WIN_HINTS hint */
	if (XGetWindowProperty(display, client->window, win_hints, 0, 1, 0,
			XA_CARDINAL, &type, &fmt, &items, &bytes,
			(u_char **) &card) == Success && card) {
		if (*card & WIN_HINTS_SKIP_FOCUS)
			client->flags.nofocus = 1;
		XFree(card);
	}

	/* _WIN_LAYER hint */
	if (XGetWindowProperty(display, client->window, win_layer, 0, 1, 0,
			XA_CARDINAL, &type, &fmt, &items, &bytes,
			(u_char **) &card) == Success && card) {
		if (*card >= WIN_LAYER_DESKTOP && *card < WIN_LAYER_BELOW)
			client->stacklayer = STACKLAYER_BOTTOM;
		else if (*card < WIN_LAYER_NORMAL)
			client->stacklayer = STACKLAYER_BELOW;
		else if (*card < WIN_LAYER_ONTOP)
			client->stacklayer = STACKLAYER_NORMAL;
		else if (*card < WIN_LAYER_ABOVE_DOCK)
			client->stacklayer = STACKLAYER_ABOVE;
		else if (*card <= WIN_LAYER_MENU)
			client->stacklayer = STACKLAYER_TIPTOP;
		XFree(card);
	}

	return PLUGIN_OK;
}

/* when new windows arrive or cleave */
static int window_life(int pcall, client_t *client) {
	if (set_client_list(client->screen) == -1)
		return PLUGIN_UNLOAD;
	return PLUGIN_OK;
}

/* workspace changes */
static int workspace_change(int pcall, screen_t *screen, desktop_t *desktop) {
	CARD32 val[2];

	if (desktop == screen->desktop) {
		val[0] = (CARD32) screen->desktop->viewx;
		val[1] = (CARD32) screen->desktop->viewy;
		XChangeProperty(display, screen->root, WIN_AREA, XA_CARDINAL,
			32, PropModeReplace, (u_char *) val, 2);
	}

	return PLUGIN_OK;
}

/* when the user switches desktops */
static int desktop_change(int pcall, screen_t *screen, desktop_t *olddesk) {
	CARD32 val[2];

	/* set the workspace area properties */
	val[0] = (CARD32) screen->desktop->width;
	val[1] = (CARD32) screen->desktop->height;
	XChangeProperty(display, screen->root, WIN_AREA_COUNT, XA_CARDINAL,
		32, PropModeReplace, (u_char *) val, 2);

	/* now set the current desktop */
	val[0] = (CARD32) screen->desktop->num;
	XChangeProperty(display, screen->root, WIN_WORKSPACE, XA_CARDINAL,
		32, PropModeReplace, (u_char *) val, 1);

	/* desktop change also changes workspace */
	return workspace_change(pcall, screen, screen->desktop);
}

/* button press on the root window */
static int root_button(int pcall, screen_t *screen, XButtonEvent *e) {
	if (e->type == ButtonPress)
		XUngrabPointer(display, CurrentTime);
	XSendEvent(display, gnomescr[screen->num].wm_check, 0,
		SubstructureNotifyMask, (XEvent *) e);
	return PLUGIN_OK;
}

/* plugin death */
void shutdown() {
	screen_t *screen;

	/* kill our per-screen structure */
	if (!gnomescr)
		return;

	/* for each managed screen */
	TAILQ_FOREACH(screen, &screen_list, s_list) {
		XDeleteProperty(display, RootWindow(display, screen->num),
			win_supporting_wm_check);
		XDeleteProperty(display, RootWindow(display, screen->num),
			win_protocols);
		XDeleteProperty(display, RootWindow(display, screen->num),
			WIN_DESKTOP_BUTTON_PROXY);
		XDeleteProperty(display, RootWindow(display, screen->num),
			WIN_CLIENT_LIST);
		if (gnomescr[screen->num].wm_check)
			XDestroyWindow(display, gnomescr[screen->num].wm_check);
	}
	free(gnomescr);
}

/* alloc stuff; create the gnome window check window */
int start() {
	XSetWindowAttributes attr;
	screen_t *screen;
	CARD32 val[2];

	/* register callbacks */
	plugin_callback_add(plugin_this, PCALL_INIT_HINTS, init_hints);
	plugin_callback_add(plugin_this, PCALL_WINDOW_BIRTH, window_life);
	plugin_callback_add(plugin_this, PCALL_WINDOW_DEATH, window_life);
	plugin_callback_add(plugin_this, PCALL_WORKSPACE_CHANGE, workspace_change);
	plugin_callback_add(plugin_this, PCALL_DESKTOP_CHANGE, desktop_change);
	plugin_callback_add(plugin_this, PCALL_ROOT_BUTTON, root_button);

	/* get memory for the per-screen structure */
	gnomescr = calloc(screen_count, sizeof(struct gnomescr));
	if (!gnomescr)
		return PLUGIN_UNLOAD;

	/* get atoms */
	win_supporting_wm_check = XInternAtom(display, "_WIN_SUPPORTING_WM_CHECK", 0);
	win_state = XInternAtom(display, "_WIN_STATE", 0);
	win_hints = XInternAtom(display, "_WIN_HINTS", 0);
	win_layer = XInternAtom(display, "_WIN_LAYER", 0);

	/* get win protocols atoms */
	win_protocols = XInternAtom(display, "_WIN_PROTOCOLS", 0);
	XInternAtoms(display, win_protocols_names, NUM_PROTOCOLS,
		0, win_protocols_list);

	/* fill in the per-screen structure */
	attr.override_redirect = 1;
	TAILQ_FOREACH(screen, &screen_list, s_list) {
		/* create the gnome-compliance check window */
		gnomescr[screen->num].wm_check = XCreateWindow(display, screen->root,
			-30, -30, 2, 2, 0, CopyFromParent, CopyFromParent, CopyFromParent,
			CWOverrideRedirect, &attr);
		XChangeProperty(display, screen->root, win_supporting_wm_check, XA_CARDINAL,
			32, PropModeReplace, (u_char *) &gnomescr[screen->num].wm_check, 1);
		XChangeProperty(display, gnomescr[screen->num].wm_check,
			win_supporting_wm_check, XA_CARDINAL, 32, PropModeReplace,
			(u_char *) &gnomescr[screen->num].wm_check, 1);

		/* set the win_protocols list onto the root window */
		XChangeProperty(display, screen->root, win_protocols, XA_ATOM,
			32, PropModeReplace, (u_char *) win_protocols_list, NUM_PROTOCOLS);

		/* reuse the check window as the button proxy window */
		XChangeProperty(display, screen->root, WIN_DESKTOP_BUTTON_PROXY, XA_CARDINAL,
			32, PropModeReplace, (u_char *) &gnomescr[screen->num].wm_check, 1);
		XChangeProperty(display, gnomescr[screen->num].wm_check,
			WIN_DESKTOP_BUTTON_PROXY, XA_CARDINAL, 32, PropModeReplace,
			(u_char *) &gnomescr[screen->num].wm_check, 1);

		/* setup workspace count, current workspace */
		val[0] = (CARD32) screen->desktop_count;
		XChangeProperty(display, screen->root, WIN_WORKSPACE_COUNT, XA_CARDINAL, 32,
			PropModeReplace, (u_char *) val, 1);
		val[0] = (CARD32) screen->desktop->num;
		XChangeProperty(display, screen->root, WIN_WORKSPACE, XA_CARDINAL,
			32, PropModeReplace, (u_char *) val, 1);

		/* set the workspace area properties */
		val[0] = (CARD32) screen->desktop->width;
		val[1] = (CARD32) screen->desktop->height;
		XChangeProperty(display, screen->root, WIN_AREA_COUNT, XA_CARDINAL,
			32, PropModeReplace, (u_char *) val, 2);
		val[0] = (CARD32) screen->desktop->viewx;
		val[1] = (CARD32) screen->desktop->viewy;
		XChangeProperty(display, screen->root, WIN_AREA, XA_CARDINAL,
			32, PropModeReplace, (u_char *) val, 2);

		/* setup the CLIENT_LIST on the screen*/
		if (set_client_list(screen) == -1)
			return PLUGIN_UNLOAD;
	}

	return PLUGIN_OK;
}
