/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
 * See LICENSE file for license details.
 */
#define _X11_VISIBLE
#include <stuff/x.h>
#include <stuff/util.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/Xinerama.h>

#if RANDR_MAJOR < 1
#  error XRandR versions less than 1.0 are not supported
#endif

static void	randr_screenchange(XRRScreenChangeNotifyEvent*);
static bool	randr_event_p(XEvent *e);
static void	randr_init(void);
static void	render_init(void);
static void	xinerama_init(void);

typedef void		(*EvHandler)(XEvent*);
static EvHandler	randr_handlers[RRNumberEvents];

bool	have_RandR;
bool	have_render;
bool	have_xinerama;
int	randr_eventbase;
Visual*	render_visual;

static void
handle(XEvent *e, EvHandler h[], int base) {

	if(h[e->type-base])
		h[e->type-base](e);
}

void
xext_init(void) {
	randr_init();
	render_init();
	xinerama_init();
}

void
xext_event(XEvent *e) {

	if(randr_event_p(e))
		handle(e, randr_handlers, randr_eventbase);
}

static void
randr_init(void) {
	int errorbase, major, minor;

	have_RandR = XRRQueryExtension(display, &randr_eventbase, &errorbase);
	if(have_RandR)
		if(XRRQueryVersion(display, &major, &minor) && major < 1)
			have_RandR = false;
	if(have_RandR)
		XRRSelectInput(display, scr.root.xid, RRScreenChangeNotifyMask);
}

static bool
randr_event_p(XEvent *e) {
	return have_RandR
	    && (uint)e->type - randr_eventbase < RRNumberEvents;
}

static void
randr_screenchange(XRRScreenChangeNotifyEvent *ev) {

	XRRUpdateConfiguration((XEvent*)ev);
	if(ev->rotation*90 % 180)
		scr.rect = Rect(0, 0, ev->width, ev->height);
	else
		scr.rect = Rect(0, 0, ev->height, ev->width);
	init_screens();
}

static EvHandler randr_handlers[] = {
	[RRScreenChangeNotify] = (EvHandler)randr_screenchange,
};

/* Ripped most graciously from ecore_x. XRender documentation
 * is sparse.
 */
static void
render_init(void) {
	XVisualInfo *vip;
	XVisualInfo vi;
	int base, i, n;

	have_render = XRenderQueryExtension(display, &base, &base);
	if(!have_render)
		return;

	vi.class = TrueColor;
	vi.depth = 32;
	vi.screen = scr.screen;
	vip = XGetVisualInfo(display, VisualClassMask
				    | VisualDepthMask
				    | VisualScreenMask,
				    &vi, &n);
	for(i=0; i < n; i++)
		if(render_argb_p(vip[i].visual)) {
			render_visual = vip[i].visual;
			scr.visual32 = render_visual;
			break;
		}
	XFree(vip);
	if(render_visual)
		scr.colormap32 = XCreateColormap(display, scr.root.xid, render_visual, AllocNone);
}

bool
render_argb_p(Visual *v) {
	XRenderPictFormat *f;

	if(!have_render)
		return false;
	f = XRenderFindVisualFormat(display, v);
	return f
	    && f->type == PictTypeDirect
	    && f->direct.alphaMask;
}

static void
xinerama_init(void) {
	int base;

	have_xinerama = XineramaQueryExtension(display, &base, &base);
}

static bool
xinerama_active(void) {
	return have_xinerama && XineramaIsActive(display);
}

Rectangle*
xinerama_screens(int *np) {
	static Rectangle *rects;
	XineramaScreenInfo *res;
	int i, n;

	if(!xinerama_active()) {
		*np = 1;
		return &scr.rect;
	}

	free(rects);
	res = XineramaQueryScreens(display, &n);
	rects = emalloc(n * sizeof *rects);
	for(i=0; i < n; i++) {
		rects[i].min.x = res[i].x_org;
		rects[i].min.y = res[i].y_org;
		rects[i].max.x = res[i].x_org + res[i].width;
		rects[i].max.y = res[i].y_org + res[i].height;
	}
	XFree(res);

	*np = n;
	return rects;
}

