/*-
 * 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 "wm.h"

/* list of screen structures, and count of screens */
int		screen_count	= 0;
screenlist_t	screen_list	= TAILQ_HEAD_INITIALIZER(screen_list);

/*
 * error handler to use while we're trying to init; if errors
 * happen here it's probably because someone else (another window
 * manager, presumably) selected for SubstructureRedirectMask
 * on the root window.
 */
static int init_handler(Display *d, XErrorEvent *e) {
	errx(1, "another window manager may be "
		"running; exiting now.");
}

/*
 * error handler used during normal window manager
 * operation.  it is expected that we get some BadWindows,
 * BadDrawables and such when windows exit; nonexpected
 * stuff is logged to stderr.
 */
static int error_handler(Display *d, XErrorEvent *event) {
	if (event->error_code != BadWindow 
			&& event->error_code != BadMatch
			&& event->request_code != X_GetGeometry 
			&& event->error_code != BadDrawable)
		trace("unexpected Xlib error; error code %d,"
			"request code %d, minor %d",
			event->error_code, event->request_code,
			event->minor_code);
	return 0;
}

/* this starts us up managing windows that already exist when we start */
static void manage_existing_windows(screen_t *screen) {
	XWindowAttributes attr;
	Window wroot, wparent, *children;
	int nchilds, i;
	client_t *client;

	XGrabServer(display);
	XQueryTree(display, screen->root, &wroot, &wparent, &children, &nchilds);
	for (i = 0; i < nchilds; i++) {
		XGetWindowAttributes(display, children[i], &attr);
		if (attr.override_redirect || attr.map_state != IsViewable)
			continue;
		client = client_add(screen, children[i], NULL, NULL);
		if (!client)
			continue;
		client->flags.unmap_from_reparent = 1;
	}
	XUngrabServer(display);
}

/* manage a screen */
static screen_t *screen_manage(int num) {
	XSetWindowAttributes wattr;
	XGCValues gcvalues;
	XColor clr;
	screen_t *screen;
	Window dumroot;
	int dumx, dumy, dumbord, dumdepth;
	int events;

	screen = calloc(1, sizeof(screen_t));
	screen->num = num;
	screen->root = RootWindow(display, screen->num);

	/* get root window dimensions */
	XGetGeometry(display, screen->root, &dumroot, &dumx, &dumy, &screen->width, &screen->height, 
		&dumbord, &dumdepth);

	/* we sync after this so we don't accidently change the root window cursor if a manager is running */
	events = SubstructureNotifyMask | SubstructureRedirectMask | ButtonPressMask | ButtonReleaseMask;
	XSelectInput(display, screen->root, events);
	XSync(display, 0);

	XDefineCursor(display, screen->root, cursor_default);
	XSaveContext(display, screen->root, root_context, (XPointer) screen);

	/* get a window to focus if none of the clients can get it */
	wattr.override_redirect = 1;
	screen->nofocus = XCreateWindow(display, screen->root, -10, -10, 4, 4, 0, 0,
		InputOnly, CopyFromParent, CWOverrideRedirect, &wattr);
	XMapWindow(display, screen->nofocus);

	/* get colors for the screen */
	if (!options.linefgclr || !XParseColor(display, DefaultColormap(display, screen->num), options.linefgclr, &clr)) {
		screen->linefg = (1 << DefaultDepth(display, screen->num)) - 1;
	} else {
		XAllocColor(display, DefaultColormap(display, screen->num), &clr);
		screen->linefg = clr.pixel;
	}
	if (!options.titleclr || !XParseColor(display, DefaultColormap(display, screen->num), options.titleclr, &clr))
		clr.pixel = BlackPixel(display, screen->num);
	else
		XAllocColor(display, DefaultColormap(display, screen->num), &clr);

	/* get graphics contexts */
	gcvalues.function = GXxor;
	gcvalues.foreground = screen->linefg;
	gcvalues.background = BlackPixel(display, screen->num);
	gcvalues.subwindow_mode = IncludeInferiors;
	gcvalues.line_width = options.linewidth;
	screen->xorgc = XCreateGC(display, screen->root, GCForeground | GCBackground 
		| GCSubwindowMode | GCFunction | GCLineWidth, &gcvalues);
	gcvalues.function = GXcopy;
	gcvalues.foreground = clr.pixel;
	gcvalues.background = WhitePixel(display, screen->num);
#ifdef I18N
	screen->titlegc = XCreateGC(display, screen->root, GCForeground | GCBackground
		| GCFunction, &gcvalues);
#else        
	gcvalues.font = titlefont->fid;
	screen->titlegc = XCreateGC(display, screen->root, GCForeground | GCBackground
		| GCFont | GCFunction, &gcvalues);
#endif
	return screen;
}

/* the main initialization routine */
void screen_init() {
	screen_t *screen;
	int dum, i;

	screen_count = ScreenCount(display);
	XSetErrorHandler(init_handler);

	/*
	 * get cursors, unique contexts and intern all
	 * the global atoms.
	 */
	cursor_default = XCreateFontCursor(display, XC_left_ptr);
	cursor_move = XCreateFontCursor(display, XC_fleur);
	cursor_place = XCreateFontCursor(display, XC_bottom_right_corner);
	client_context = XUniqueContext();
	root_context = XUniqueContext();
	decor_context = XUniqueContext();
	plugin_context = XUniqueContext();
	XInternAtoms(display, atom_names, NUM_ATOMS, 0, atoms);

	/* we need this before screen_manage calls */
	decor_init();

	/* manage all screens */
	for (i = 0; i < screen_count; i++) {
		screen = screen_manage(i);
		TAILQ_INSERT_HEAD(&screen_list, screen, s_list);
	}

	/*
	 * make sure no errors have happened yet, and
	 * switch to the real error handler used during
	 * normal program operation.
	 */
	XSync(display, 0);
	XSetErrorHandler(error_handler);
	XSync(display, 0);

	if (pixmap_getpixmaps() != 0)
		errx(1, "unable to get pixmaps");
	dgroup_learn_space();

	/*
	 * create desktops and grab keys for all
	 * the managed screens
	 */
	TAILQ_FOREACH_REVERSE(screen, &screen_list, screenlist, s_list) {
		TAILQ_INIT(&screen->s_desklist);
		if (desktop_create(screen, options.desktop_width, options.desktop_height,
				options.desktop_count) == -1)
			errx(1, "unable to add requested number of "
				"desktops to screen %d", screen->num);
		screen->desktop = TAILQ_FIRST(&screen->s_desklist);
		keys_grab(screen);
	}
	
	/* see if we have the shape extension */
	if (XShapeQueryExtension(display, &shape_base, &dum) == 0)
		errx(1, "shape extension not found; shape is "
			"required to run this windowmanager");
}

/* manage clients that already exist */
void screen_manage_existing() {
	screen_t *screen;

	TAILQ_FOREACH(screen, &screen_list, s_list)
		manage_existing_windows(screen);
}

/* shutdown routine */
void screen_shutdown() {
	screen_t *screen, *next;

	/* kill all screens */
	screen = TAILQ_FIRST(&screen_list);
	while (screen) {
		/* remove screen data */
		XDestroyWindow(display, screen->nofocus);
		XFreeGC(display, screen->xorgc);
		XFreeGC(display, screen->titlegc);

		/* get rid of all desktops */
		desktop_remove(screen);

		/* free the screen_t */
		next = TAILQ_NEXT(screen, s_list);
		free(screen);
		screen = next;
	}
	TAILQ_INIT(&screen_list);

	/*
	 * make sure input focus is on the root
	 * window before we leave.
	 */
	XSetInputFocus(display, PointerRoot, RevertToPointerRoot,
		CurrentTime);
	XSync(display, 0);
}
