/* Clementine Window Manager
   Copyright 2002 Dave Berton <db@mosey.org>

   based on aewm Copyright 1998-2001 Decklin Foster <decklin@red-bean.com>

   This program is free software; see LICENSE for details. */

#include "windowsystem.h"
#include "painter.h"
#include "keys.h"
#include "look.h"

#include <X11/cursorfont.h>
#include <X11/Xmd.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>

#include <signal.h>
#include <sys/wait.h>

#include <stdio.h>

#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>

#define DEF_PAD  3

// ## db more : get rid of these
#define ChildMask (SubstructureRedirectMask|SubstructureNotifyMask)
#define ButtonMask (ButtonPressMask|ButtonReleaseMask)
#define MouseMask (ButtonMask|PointerMotionMask)

#include <iostream>
#include <sstream>

std::string convertToString(int x)
{
   std::ostringstream o;
   if (o << x)
     return o.str();
   // db more some sort of error handling goes here...
   return "string conversion error";
} 

int handle_xerror(Display *dpy, XErrorEvent *e)
{
//    if ( !windowManager ) {
	char msg[255];
	XGetErrorText(dpy, e->error_code, msg, sizeof msg);
	std::cerr<<"X error:" + std::string(msg) << std::endl;//e->resourceid
	//db more dump core
//	std::cout << ((std::string*)0)->size();
	exit(1);
//    }
	//   windowManager->handleXerror( e );
	// return 0;
}

void quit_nicely(void)
{
    printf("quit_nicely\n");
//    if ( !windowManager ) {
//	std::cerr << "internal error, exiting" << std::endl;
	exit(1);
//    }
//    windowManager->setQuitting( true ); // force graceful exit
//    windowManager->flush();
}

void sig_handler(int signal)
{
    switch (signal) {
    case SIGINT:
    case SIGTERM:
    case SIGHUP:
	quit_nicely(); break;
    case SIGCHLD:
	wait(NULL); break;
    }
}

WindowSystem::WindowSystem()
{
    struct sigaction act;
    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);

    opt_pad = DEF_PAD;
    quit = false;

    XSetWindowAttributes sattr;

    dpy = XOpenDisplay(NULL);

    if (!dpy) {
	err("can't open display '??' (is $DISPLAY set properly?)");
	    // ,getenv("DISPLAY"));
	exit(1);
    }

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

    wm_protos = XInternAtom(dpy, "WM_PROTOCOLS", False);
    wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    wm_state = XInternAtom(dpy, "WM_STATE", False);
    wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", False);

    move_curs = XCreateFontCursor(dpy, XC_fleur);
    resize_curs = XCreateFontCursor(dpy, XC_sizing);
    arrow_curs = XCreateFontCursor(dpy, XC_left_ptr);

    paint = new Painter( dpy, root, screen );
    keys = new Keys();

    sattr.cursor = arrow_curs;
    sattr.event_mask = ChildMask|ColormapChangeMask|KeyPressMask|ButtonMask;
    XChangeWindowAttributes(dpy, root, CWCursor|CWEventMask, &sattr);

    /* Window cycling */
    XGrabKey( dpy, XKeysymToKeycode(dpy, XK_Tab), Mod1Mask,
	      root, True, GrabModeAsync, GrabModeAsync);
    XGrabKey( dpy, XKeysymToKeycode(dpy, XK_Tab), ( Mod1Mask | ShiftMask ),
	      root, True, GrabModeAsync, GrabModeAsync);
    XGrabKey( dpy, XKeysymToKeycode(dpy, XK_Tab), ControlMask,
	      root, True, GrabModeAsync, GrabModeAsync);
    XGrabKey( dpy, XKeysymToKeycode(dpy, XK_Tab), ( ControlMask | ShiftMask ),
	      root, True, GrabModeAsync, GrabModeAsync);
}

WindowSystem::~WindowSystem()
{
    shutdown();
}

void WindowSystem::shutdown()
{
#ifdef DEBUG
    std::cout << "WindowSystem shutting down" << std::endl;
#endif

    delete paint;
    delete keys;
    XFreeCursor(dpy, move_curs);
    XFreeCursor(dpy, resize_curs);
    XFreeCursor(dpy, arrow_curs);
    

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

void WindowSystem::scanWindows()
{
    unsigned int nwins, i;
    Window dummyw1, dummyw2, *wins;
    XWindowAttributes attr;
    XQueryTree(dpy, root, &dummyw1, &dummyw2, &wins, &nwins);
    for (i = 0; i < nwins; i++) {
	XGetWindowAttributes(dpy, wins[i], &attr);
	if (!attr.override_redirect && attr.map_state == IsViewable) {
	    newWindow( wins[i], true, attr.x, attr.y, 
		       attr.width, attr.height, attr.colormap );
	}
    }
    XFree(wins);
}

std::string WindowSystem::windowName( Window w )
{
    char* n;
    if ( XFetchName(dpy, w, &n) ) {
	std::string name( n );
	XFree(n);
	return name;
    }
    return std::string();
}

   /* creates a window parented to the root window */
Window WindowSystem::createFrame( int x, int y, 
				  unsigned int width, unsigned int height )
{
    XSetWindowAttributes pattr;

    pattr.override_redirect = True;
    pattr.background_pixel = BlackPixel( dpy, screen );
    //   pattr.border_pixel = BlackPixel( dpy, screen );
    pattr.event_mask = ChildMask|ButtonPressMask|ButtonReleaseMask|
	ExposureMask|EnterWindowMask|KeyPressMask;
 
    return XCreateWindow(dpy, root,
			 x, y, width, height,
			 0, /* border width */ 
			 DefaultDepth(dpy, screen), 
			 CopyFromParent,
			 DefaultVisual(dpy, screen),
			 CWOverrideRedirect|CWBackPixel|
			 /*CWBorderPixel|*/CWEventMask, &pattr);
}

/* creates a window parented to the root window */
Window WindowSystem::createWindow( int x, int y, 
				  unsigned int width, unsigned int height )
{
    XSetWindowAttributes pattr;

    pattr.override_redirect = True;
    pattr.event_mask = ChildMask|ButtonPressMask|ButtonReleaseMask|
	ExposureMask|EnterWindowMask|KeyPressMask;
 
    Window w = XCreateWindow(dpy, root,
			     x, y, width, height,
			     0, /* border width */ 
			     DefaultDepth(dpy, screen), 
			     CopyFromParent,
			     DefaultVisual(dpy, screen),
			     CWOverrideRedirect|CWEventMask, &pattr);
    setBackPixmapParentRelative( w );
    return w;
}

void WindowSystem::setBackPixmapParentRelative( Window w )
{
//    XSetWindowAttributes sattr;
//    sattr.background_pixmap = ParentRelative;
//    XChangeWindowAttributes(dpy, w, CWBackPixmap, &sattr);
    XSetWindowBackgroundPixmap( dpy, w, None );
    XClearWindow( dpy, w );
}

Window WindowSystem::createMenu()
{
    XSetWindowAttributes pattr;

    pattr.override_redirect = True;
    //pattr.background_pixel = painter()->backgroundColor().pixel;
    pattr.border_pixel = BlackPixel( dpy, screen );
    pattr.event_mask = ChildMask|ButtonPressMask|ButtonMotionMask|
	ExposureMask|EnterWindowMask|PointerMotionMask;
 
    return XCreateWindow(dpy, root,
			 0, 0, 1, 1,
			 0, /* border width */ 
			 DefaultDepth(dpy, screen), 
			 CopyFromParent,
			 DefaultVisual(dpy, screen),
			 CWOverrideRedirect|/*CWBackPixel|*/
			 CWBorderPixel|CWEventMask, &pattr);
}


bool WindowSystem::grabPointer( Cursor c )
{
    return XGrabPointer(dpy, root, False, MouseMask,
			GrabModeAsync, GrabModeAsync, None, 
			c, CurrentTime) == GrabSuccess;
}

void WindowSystem::ungrabPointer() 
{ 
    XUngrabPointer(dpy, CurrentTime); 
}


void WindowSystem::waitForMouse( XEvent* ev )
{
    XMaskEvent(dpy, MouseMask, ev);
}


void WindowSystem::handleXerror( XErrorEvent *e)
{
    err("encountered X error, bailing");
    exit(1);
/*
    Client *c = findClient(e->resourceid, WINDOW);
    if (e->error_code == BadAccess && e->resourceid == root) {
	err("root window unavailible (maybe another wm is running?)");
	exit(1);
    } else {
	char msg[255];
	XGetErrorText(dpy, e->error_code, msg, sizeof msg);
	err("X error:" + std::string(msg));//e->resourceid
    }
    if (c) 
	remove(c, WITHDRAW);
*/
}

int WindowSystem::sendDeleteMessage( Window w)
{
    XEvent e;
    
    e.type = ClientMessage;
    e.xclient.window = w;
    e.xclient.message_type = wm_protos;
    e.xclient.format = 32;
    e.xclient.data.l[0] = wm_delete;
    e.xclient.data.l[1] = CurrentTime;
    
    return XSendEvent(dpy, w, False, NoEventMask, &e);
}

void WindowSystem::tryDelete( Window w )
{
    int i, n, found = 0;
    Atom *protocols;
    if ( getProtocols(w, &protocols, &n) ) {
	for (i=0; i<n; i++) if (protocols[i] == wm_delete) found++;
	xFree(protocols);
    }
    if (found)
	sendDeleteMessage(w);
    else 
	kill(w);
}


void WindowSystem::pointerPosition(int *x, int *y)
{
    Window mouse_root, mouse_win;
    int win_x, win_y;
    unsigned int mask;
    
    XQueryPointer(dpy, root, &mouse_root, &mouse_win,
		  x, y, &win_x, &win_y, &mask);
}

/* Attempt to follow the ICCCM by explicity specifying 32 bits for
 * this property. Does this goof up on 64 bit systems? */

void WindowSystem::setState(Window w, long state)
{
    CARD32 data[2];

    data[0] = state;
    data[1] = None;

    XChangeProperty(dpy, w, wm_state, wm_state,
		    32, PropModeReplace, (unsigned char *)data, 2);
}

void WindowSystem::iconize( Window w )
{
    setState( w, IconicState );
    XWindowAttributes attr;
    XGetWindowAttributes(dpy, w, &attr);
    if ( !attr.override_redirect )
	newIcon( w, (attr.map_state == IsViewable), 
		 attr.x, attr.y, attr.width, attr.height, attr.colormap);
}


/* If we can't find a WM_STATE we're going to have to assume
 * Withdrawn. This is not exactly optimal, since we can't really
 * distinguish between the case where no WM has run yet and when the
 * state was explicitly removed (Clients are allowed to either set the
 * atom to Withdrawn or just remove it... yuck.) */

long WindowSystem::getState(Window w)
{
    Atom real_type; int real_format;
    unsigned long items_read, bytes_left;
    long *data, state = WithdrawnState;

    if (XGetWindowProperty(dpy, w, wm_state, 0L, 2L, False,
			   wm_state, &real_type, &real_format, 
			   &items_read, &bytes_left,
			   (unsigned char **) &data) 
	== Success && items_read) {
	state = *data;
	XFree(data);
    }
    return state;
}


void WindowSystem::sendConfigureEvent( Window w, int x, int y,
				       unsigned int width, 
				       unsigned int height )
{
    XConfigureEvent ce;

    ce.type = ConfigureNotify;
    ce.event = w;
    ce.window = w;
    ce.x = x;
    ce.y = y;
    ce.width = width;
    ce.height = height;
    ce.border_width = 0;
    ce.above = None;
    ce.override_redirect = 0;

    XSendEvent(dpy, w, False, StructureNotifyMask, (XEvent *)&ce);
}

/* used below */
int ignore_xerror(Display */*dpy*/, XErrorEvent */*e*/)
{
    return 0;
}

void WindowSystem::ignoreErrors()
{
    XSetErrorHandler(ignore_xerror);
}

void WindowSystem::unignoreErrors()
{
    XSetErrorHandler(handle_xerror);
}

#ifdef DEBUG

/* Bleh, stupid macro names. I'm not feeling creative today. */

#define SHOW_EV(name, memb) \
case name: s = #name; w = e.memb.window; break;
#define SHOW(name) \
case name: return #name;

void WindowSystem::showEvent(XEvent e)
{
    char* s;
//    char* buf[20];
    Window w;
    // db more
//    Client *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(CreateNotify, xcreatewindow)
	    SHOW_EV(DestroyNotify, xdestroywindow)
	    SHOW_EV(EnterNotify, xcrossing)
	    SHOW_EV(Expose, xexpose)
	    SHOW_EV(KeyPress, xkey)
	    SHOW_EV(KeyRelease, xkey)
	    SHOW_EV(MapNotify, xmap)
	    SHOW_EV(MapRequest, xmaprequest)
	    SHOW_EV(MappingNotify, xmapping)
	    SHOW_EV(MotionNotify, xmotion)
	    SHOW_EV(PropertyNotify, xproperty)
	    SHOW_EV(ReparentNotify, xreparent)
	    SHOW_EV(ResizeRequest, xresizerequest)
	    SHOW_EV(UnmapNotify, xunmap)
	    default:
	s = "unknown event"; w = None;
	break;
    }

//    c = findClient(w, WINDOW);
//    snprintf(buf, sizeof buf, c ? c->name : "(none)");
//    err( convertToString(w) + std::string(":") + std::string( buf ) +
//	 std::string(":") + std::string( s ) );

    err( convertToString(w) + std::string(":") +
	 std::string(":") + std::string( s ) );
}
#endif

void WindowSystem::err( std::string e )
{
    std::cerr << e << std::endl;
}

void WindowSystem::eventLoop()
{
    scanWindows();

    XEvent ev;

    for (;;) {
	XNextEvent(dpy, &ev);
	if ( quit )
	    break;
#ifdef DEBUG
	showEvent(ev);
#endif
	switch (ev.type) {
	case ButtonPress:
	    handleButtonPress(&ev.xbutton); break;
	case ButtonRelease:
	    handleButtonRelease(&ev.xbutton); break;
	case ConfigureRequest:
	    handleConfigure(&ev.xconfigurerequest); break;
	case MapRequest:
	    handleMap(&ev.xmaprequest); break;
	case UnmapNotify:
	    handleUnmap(&ev.xunmap); break;
	case KeyPress:
	    handleKeyPress(&ev.xkey); break;
	case DestroyNotify:
	    handleDestroy(&ev.xdestroywindow); break;
	case ClientMessage:
	    handleClientMessage(&ev.xclient); break;
	case ColormapNotify:
	    handleColormapChange(&ev.xcolormap); break;
	case PropertyNotify:
	    handlePropertyChange(&ev.xproperty); break;
	case EnterNotify:
	    handleEnter(&ev.xcrossing); break;
	case Expose:
	    handleExpose(&ev.xexpose); break;
	case MotionNotify:
	    handleMotion(&ev.xmotion); break;
	}
    }
}

void WindowSystem::handleButtonPress(XButtonEvent *e)
{
    unsigned int button = 0;
    switch (e->button) {
    case Button1:
	button = 1;
	break;
    case Button2:
	button = 2;
	break;
    case Button3:
	button = 3;
	break;
    }
    if (e->window == root)
	takeRootButton( button, e->state & Mod1Mask, e->x, e->y );
    else
	takeButtonPress( e->window, button, 
			 e->state & Mod1Mask, e->x, e->y );
}

void WindowSystem::handleButtonRelease(XButtonEvent *e)
{
    unsigned int button = 0;
    switch (e->button) {
    case Button1:
	button = 1;
	break;
    case Button2:
	button = 2;
	break;
    case Button3:
	button = 3;
	break;
    }
    if (e->window != root)
	takeButtonRelease( e->window, button, 
			   e->state & Mod1Mask, e->x, e->y );
}

void WindowSystem::configure( Window w, int x, int y, 
			      int width, int height,
			      unsigned int valuemask,
			      Window sibling, int stack_mode)
{
    XWindowChanges wc;
    wc.x = x;
    wc.y = y;
    wc.width = width;
    wc.height = height;
    wc.border_width = 0;
    wc.sibling = sibling;
    wc.stack_mode = stack_mode;
    XConfigureWindow(dpy, w, valuemask, &wc);
}

void WindowSystem::handleConfigure(XConfigureRequestEvent *e)
{
    int x, y, width, height;
    x = y = width = height = 0;
    Window sibling;
    int stack_mode;

    if (e->value_mask & CWX) 
	x = e->x;
    if (e->value_mask & CWY) 
	y = e->y;
    if (e->value_mask & CWWidth) 
	width = e->width;
    if (e->value_mask & CWHeight) 
	height = e->height;
    sibling = e->above;
    stack_mode = e->detail;
    takeConfigureRequest( e->window, x, y, width, height,
			    e->value_mask,
			    sibling, stack_mode );
}

void WindowSystem::handleMap(XMapRequestEvent *e)
{
    XWindowAttributes attr;
    XGetWindowAttributes(dpy, e->window, &attr);
    //## db more fix this mess
    if (!attr.override_redirect && !managedWindow(e->window) ) {
	newWindow(e->window, (attr.map_state == IsViewable), 
		  attr.x, attr.y, attr.width, attr.height, attr.colormap);
    } else {
	takeMap( e->window );
    }
}

void WindowSystem::handleUnmap(XUnmapEvent *e)
{
    takeUnmap( e->window );
}


void WindowSystem::handleDestroy(XDestroyWindowEvent *e)
{
    takeDestroy( e->window );
}

void WindowSystem::handleClientMessage(XClientMessageEvent *e)
{
    if ( e->message_type == wm_change_state &&
	 e->format == 32 &&
	 e->data.l[0] == IconicState ) {
	iconize( e->window );
    }
}

void WindowSystem::handlePropertyChange(XPropertyEvent *e)
{
    switch (e->atom) {
    case XA_WM_NAME: {
	char* n;
	XFetchName(dpy, e->window, &n);
	std::string newName( n );
	XFree( n );
	takeNameChange( e->window, newName ); 
	break;
    }
    case XA_WM_NORMAL_HINTS:
	// db more
	err("XA_WM_NORMAL_HINTS not handled");
	//XGetWMNormalHints(dpy, c->window, c->size, &dummy);
    }
}

void WindowSystem::handleEnter(XCrossingEvent *e)
{
    takeFocus( e->window );
}

void WindowSystem::handleColormapChange(XColormapEvent *e)
{
    if ( e->c_new )
	takeNewColormap( e->window, e->colormap );
}

void WindowSystem::handleExpose(XExposeEvent *e)
{
    if (e->count == 0) 
	takeExpose( e->window );
}

void WindowSystem::handleMotion(XMotionEvent *e)
{
    takeMotion( e->window, e->x, e->y );
}

void WindowSystem::handleKeyPress(XKeyEvent *e)
{
    takeKeyPress( e->window, e->state, XKeycodeToKeysym( dpy, e->keycode, 0) );
}



syntax highlighted by Code2HTML, v. 0.9.1