/*
 *  Window Maker window manager
 * 
 *  Copyright (c) 1997, 1998 Alfredo K. Kojima
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
 *  USA.
 */
#include "wconfig.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#ifdef SHAPE
#include <X11/extensions/shape.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "WindowMaker.h"
#include "wcore.h"
#include "framewin.h"
#include "window.h"
#include "properties.h"
#include "actions.h"
#include "icon.h"
#include "client.h"
#include "funcs.h"
#include "stacking.h"
#include "appicon.h"
#include "appmenu.h"
#ifdef KWM_HINTS
#include "kwm.h"
#endif

/****** Global Variables ******/

/* contexts */
extern XContext wWinContext;

extern Atom _XA_WM_STATE;
extern Atom _XA_WM_PROTOCOLS;
extern Atom _XA_WM_COLORMAP_WINDOWS;

extern Atom _XA_WINDOWMAKER_MENU;

extern Atom _XA_GNUSTEP_WM_ATTR;
extern Atom _XA_GNUSTEP_WM_RESIZEBAR;

#ifdef SHAPE
extern Bool wShapeSupported;
#endif


/*
 *--------------------------------------------------------------------
 * wClientRestore--
 * 	Reparent the window back to the root window.
 * 
 *--------------------------------------------------------------------
 */
void
wClientRestore(WWindow *wwin)
{
#if 0
    int gx, gy;

    wClientGetGravityOffsets(wwin, &gx, &gy);
    /* set the positio of the frame on screen */
    wwin->frame_x -= gx * FRAME_BORDER_WIDTH;
    wwin->frame_y -= gy * FRAME_BORDER_WIDTH;
    /* if gravity is to the south, account for the border sizes */
    if (gy > 0)
	wwin->frame_y += (wwin->frame->top_width + wwin->frame->bottom_width);
#endif
    XSetWindowBorderWidth(dpy, wwin->client_win, wwin->old_border_width);    
    XReparentWindow(dpy, wwin->client_win, wwin->screen_ptr->root_win,
		    wwin->frame_x, wwin->frame_y);
    
    /* don't let the window get iconified after restart */
    /*
    if (wwin->flags.shaded)
	wClientSetState(wwin, NormalState, None);
     */
}


/*
 *---------------------------------------------------------------------- 
 * wClientSetState--
 * 	Set the state of the client window to one of the window
 * states defined in ICCCM (Iconic, Withdrawn, Normal)
 * 
 * Side effects:
 * 	The WM_STATE property of the window is updated as well as the
 * WWindow.state variable.
 *---------------------------------------------------------------------- 
 */
void
wClientSetState(WWindow *wwin, int state, Window icon_win)
{
    CARD32 data[2];
    
    wwin->state = state;

    data[0] = (unsigned long) state;
    data[1] = (unsigned long) icon_win;

    XChangeProperty(dpy, wwin->client_win, _XA_WM_STATE, _XA_WM_STATE, 32, 
		    PropModeReplace, (unsigned char *) data, 2);
}


void
wClientGetGravityOffsets(WWindow *wwin, int *ofs_x, int *ofs_y)
{
    switch (wwin->normal_hints->win_gravity) {	
     case ForgetGravity:
     case CenterGravity:
     case StaticGravity:
	*ofs_x = 0;
	*ofs_y = 0;
	break;
     case NorthWestGravity:
	*ofs_x = -1;
	*ofs_y = -1;
	break;
     case NorthGravity:
	*ofs_x = 0;
	*ofs_y = -1;
	break;
     case NorthEastGravity:
	*ofs_x = 1;
	*ofs_y = -1;
	break;
     case WestGravity:
	*ofs_x = -1;
	*ofs_y = 0;
	break;
     case EastGravity:
	*ofs_x = 1;
	*ofs_y = 0;
	break;
     case SouthWestGravity:
	*ofs_x = -1;
	*ofs_y = 1;
	break;
     case SouthGravity:
	*ofs_x = 0;
	*ofs_y = 1;
	break;
     case SouthEastGravity:
	*ofs_x = 1;
	*ofs_y = 1;
	break;
    }
}



void
wClientConfigure(WWindow *wwin, XConfigureRequestEvent *xcre)
{
    XWindowChanges xwc;
    int nx, ny, nwidth, nheight;
    int ofs_x, ofs_y;

    if (wwin==NULL) {
	/* 
	 * configure a window that was not mapped by us 
	 */
	xwc.x = xcre->x;
	xwc.y = xcre->y;
	xwc.width = xcre->width;
	xwc.height = xcre->height;
	xwc.border_width = xcre->border_width;
	xwc.stack_mode = xcre->detail;
	xwc.sibling = xcre->above;
	XConfigureWindow(dpy, xcre->window, xcre->value_mask, &xwc);
	return;
    }
#ifdef SHAPE
    if (wShapeSupported) {
	int junk;
	unsigned int ujunk;
	int b_shaped;
	
	XShapeSelectInput(dpy, wwin->client_win, ShapeNotifyMask);
	XShapeQueryExtents(dpy, wwin->client_win, &b_shaped, &junk, &junk, 
			   &ujunk, &ujunk, &junk, &junk, &junk, &ujunk, 
			   &ujunk);
	wwin->flags.shaped = b_shaped;
    }
#endif
    if (xcre->value_mask & CWStackMode) {
	WObjDescriptor *desc;
	WWindow *sibling;

	if ((xcre->value_mask & CWSibling) &&
	    (XFindContext(dpy, xcre->above, wWinContext, 
			  (XPointer *)&desc) == XCSUCCESS)
	    && (desc->parent_type==WCLASS_WINDOW)) {
	    sibling=desc->parent;
	    xwc.sibling = sibling->frame->core->window;
	} else {
	    xwc.sibling = xcre->above;
	}
	xwc.stack_mode = xcre->detail;
	XConfigureWindow(dpy, wwin->frame->core->window,
			 xcre->value_mask & (CWSibling | CWStackMode), &xwc);
	/* fix stacking order */
	RemakeStackList(wwin->screen_ptr);
    }

    wClientGetGravityOffsets(wwin, &ofs_x, &ofs_y);
    
    if (xcre->value_mask & CWBorderWidth) {
	wwin->old_border_width = xcre->border_width;
    }

    if (!wwin->flags.shaded) {
        /* If the window is shaded, wrong height will be set for the window */
        if (xcre->value_mask & CWX) {
            nx = xcre->x;
	    if (!WFLAGP(wwin, no_border))
	      nx -= FRAME_BORDER_WIDTH;
	}
        else
            nx = wwin->frame_x;

        if (xcre->value_mask & CWY) {
            ny = xcre->y - ((ofs_y < 0) ? 0 : wwin->frame->top_width);
	    if (!WFLAGP(wwin, no_border))
	      ny -= FRAME_BORDER_WIDTH;
	}
        else
            ny = wwin->frame_y;

        if (xcre->value_mask & CWWidth)
            nwidth = xcre->width;
        else
            nwidth = wwin->frame->core->width;

        if (xcre->value_mask & CWHeight)
            nheight = xcre->height;
        else
            nheight = wwin->frame->core->height - wwin->frame->top_width - wwin->frame->bottom_width;

        wWindowConfigure(wwin, nx, ny, nwidth, nheight);
        wwin->old_geometry.x = nx;
        wwin->old_geometry.y = ny;
        wwin->old_geometry.width = nwidth;
        wwin->old_geometry.height = nheight;
    }
}


void
wClientSendProtocol(WWindow *wwin, Atom protocol, Time time)
{
    XEvent event;

    event.xclient.type = ClientMessage;
    event.xclient.message_type = _XA_WM_PROTOCOLS;
    event.xclient.format = 32;
    event.xclient.display = dpy;
    event.xclient.window = wwin->client_win;
    event.xclient.data.l[0] = protocol;
    event.xclient.data.l[1] = time;
    event.xclient.data.l[2] = 0;
    event.xclient.data.l[3] = 0;
    XSendEvent(dpy, wwin->client_win, False, NoEventMask, &event);
    XSync(dpy, False);
}



void
wClientKill(WWindow *wwin)
{
    XKillClient(dpy, wwin->client_win);

    XFlush(dpy);
}

/*
 *---------------------------------------------------------------------- 
 * wClientCheckProperty--
 * 	Handles PropertyNotify'es, verifying which property was
 * changed and updating internal state according to that, like redrawing
 * the icon title when it is changed.
 * 
 * Side effects:
 * 	Depends on the changed property.
 * 
 * TODO: _GNUSTEP_WM_ATTR
 *---------------------------------------------------------------------- 
 */
void
wClientCheckProperty(WWindow *wwin, XPropertyEvent *event) 
{
    XWindowAttributes attribs;
    XWMHints *new_hints;
    int i, g1, g2;
    char *tmp;
    
    switch (event->atom) {
     case XA_WM_NAME:
	/* window title was changed */
	wwin->flags.wm_name_changed = 1;
	if (wwin->frame) {
	    if (!wFetchName(dpy, wwin->client_win, &tmp)) {
		/* the hint was removed */
		tmp = wstrdup(DEF_WINDOW_TITLE);
#ifdef KWM_HINTS
		wKWMSendEventMessage(wwin, WKWMChangedClient);
#endif
	    }
            if (wFrameWindowChangeTitle(wwin->frame, tmp)) {
                /* only update the menu if the title has actually changed */
                UpdateSwitchMenu(wwin->screen_ptr, wwin, ACTION_CHANGE);
#ifdef KWM_HINTS
		wKWMSendEventMessage(wwin, WKWMChangedClient);
#endif
            }
	    if (tmp)
		free(tmp);
	}
	break;
	
     case XA_WM_ICON_NAME:
#ifdef KWM_HINTS
	wKWMSendEventMessage(wwin, WKWMChangedClient);
#endif
	if (!wwin->icon) 
	    break;
	else {
	    char *new_title;
	    
	    /* icon title was changed */
	    wGetIconName(dpy, wwin->client_win, &new_title);
	    wIconChangeTitle(wwin->icon, new_title);
	}
	break;
	
     case XA_WM_COMMAND:
	if (wwin->main_window!=None) {
	    WApplication *wapp = wApplicationOf(wwin->main_window);
	    char **argv;
	    int argc;

	    if (wapp && wapp->app_icon) {
		if (wapp->app_icon->command!=NULL)
		    free(wapp->app_icon->command);

		if (XGetCommand(dpy, wwin->main_window, &argv, &argc)) {
		    if (argc > 0 && argv != NULL)
			wapp->app_icon->command = FlattenStringList(argv,argc);
		    if (argv) {
			XFreeStringList(argv);
		    }
		}
	    }
	}
	break;
	
     case XA_WM_HINTS:
	/* WM_HINTS */

	new_hints = XGetWMHints(dpy, wwin->client_win);
	
	/* group leader update
	 * 
	 * This means that the window is setting the leader after
	 * it was mapped, changing leaders or removing the leader.
	 * 
	 * Valid state transitions are:
	 *
	 *          _1            __2
	 *         / \           /  \
	 *         v |           v  |
	 *         (GC)         (GC')
	 *        /  ^          /   ^
	 *       3|  |4        5|   |6
	 *        |  |          |   |
	 *        v  /          v   /
	 *        (G'C)        (G'C')
	 * 
	 * Where G is the window_group hint, C is CLIENT_LEADER property
	 * and ' indicates the hint is unset.
	 *
	 * 1,2 - change group leader to new value of window_group
	 * 3 - change leader to value of CLIENT_LEADER
	 * 4 - change leader to value of window_group
	 * 5 - destroy application
	 * 6 - create application
	 */
	if (new_hints && (new_hints->flags & WindowGroupHint)
	    && new_hints->window_group!=None) {
	    g2 = 1;
	} else {
	    g2 = 0;
	}
	if (wwin->wm_hints && (wwin->wm_hints->flags & WindowGroupHint)
	    && wwin->wm_hints->window_group!=None) {
	    g1 = 1;
	} else {
	    g1 = 0;
	}
	
	if (wwin->client_leader) {
	    if (g1 && g2 
		&& wwin->wm_hints->window_group!=new_hints->window_group) {
		i = 1;
	    } else if (g1 && !g2) {
		i = 3;
	    } else if (!g1 && g2) {
		i = 4;
	    } else {
		i = 0;
	    }
	} else {
	    if (g1 && g2 
		&& wwin->wm_hints->window_group!=new_hints->window_group) {
		i = 2;
	    } else if (g1 && !g2) {
		i = 5;
	    } else if (!g1 && g2) {
		i = 6;
	    } else {
		i = 0;
	    }
	}
	
	if (wwin->wm_hints)
	  XFree(wwin->wm_hints);

	wwin->wm_hints = new_hints;

	/* do action according to state transition */
	switch (i) {	    
	 /* 3 - change leader to value of CLIENT_LEADER */
	 case 3:
	    wApplicationDestroy(wApplicationOf(wwin->main_window));
	    wwin->main_window = wwin->client_leader;
	    wwin->group_id = None;
	    wApplicationCreate(wwin->screen_ptr, wwin->main_window);
	    break;

	 /* 1,2,4 - change leader to new value of window_group */
	 case 1:
	 case 2:
	 case 4:
	    wApplicationDestroy(wApplicationOf(wwin->main_window));
	    wwin->main_window = new_hints->window_group;
	    wwin->group_id = wwin->main_window;
	    wApplicationCreate(wwin->screen_ptr, wwin->main_window);
	    break;
	    
	 /* 5 - destroy application */
	 case 5:
	    wApplicationDestroy(wApplicationOf(wwin->main_window));
	    wwin->main_window = None;
	    wwin->group_id = None;
	    break;
	    
	 /* 6 - create application */
	 case 6:
	    wwin->main_window = new_hints->window_group;
	    wwin->group_id = wwin->main_window;
	    wApplicationCreate(wwin->screen_ptr, wwin->main_window);
	    break;
	}
#ifdef DEBUG
	if (i) {
	    printf("window leader update caused state transition %i\n",i);
	}
#endif

	if (wwin->wm_hints) {
	    /* update icon */
	    if ((wwin->wm_hints->flags & IconPixmapHint)
		|| (wwin->wm_hints->flags & IconWindowHint)) {
		WApplication *wapp;

		if (wwin->flags.miniaturized && wwin->icon) {
		    wIconUpdate(wwin->icon);
		}
		wapp = wApplicationOf(wwin->main_window);
		if (wapp && wapp->app_icon) {
		    wIconUpdate(wapp->app_icon->icon);
		}
#ifdef KWM_HINTS
		wKWMSendEventMessage(wwin, WKWMIconChange);
#endif
	    }

	    if (wwin->wm_hints->flags & UrgencyHint)
	      wwin->flags.urgent = 1;
	    else
	      wwin->flags.urgent = 0;
	} else {
	    wwin->group_id = None;
	}
	break;
	
     case XA_WM_NORMAL_HINTS:
	/* normal (geometry) hints */
	{
	    int foo;
	    unsigned bar;

	    XGetWindowAttributes(dpy, wwin->client_win, &attribs);
	    wClientGetNormalHints(wwin, &attribs, False, &foo, &foo,
				  &bar, &bar);
	    /* TODO: should we check for consistency of the current
	     * size against the new geometry hints? */
	}
	break;
	
     case XA_WM_TRANSIENT_FOR:
	{
	    Window new_owner;
	    WWindow *owner;
	    
	    if (!XGetTransientForHint(dpy, wwin->client_win, &new_owner)) {
		new_owner = None;
	    } else {
		if (new_owner==0 || new_owner == wwin->client_win) {
		    new_owner = wwin->screen_ptr->root_win;
		}
	    }
	    if (new_owner!=wwin->transient_for) {
		owner = wWindowFor(wwin->transient_for);
		if (owner) {
		    if (owner->flags.semi_focused) {
			owner->flags.semi_focused = 0;
			if ((owner->flags.mapped || owner->flags.shaded)
			    && owner->frame)
			  wFrameWindowPaint(owner->frame);
		    }
		}
		owner = wWindowFor(new_owner);
		if (owner) {
		    if (!owner->flags.semi_focused) {
			owner->flags.semi_focused = 1;
			if ((owner->flags.mapped || owner->flags.shaded)
			    && owner->frame)
			  wFrameWindowPaint(owner->frame);
		    }
		}
		wwin->transient_for = new_owner;
		if (new_owner==None) {
		    if (WFLAGP(wwin, no_miniaturizable)) {
			WSETUFLAG(wwin, no_miniaturizable, 0);
			WSETUFLAG(wwin, no_miniaturize_button, 0);
			if (wwin->frame)
			    wWindowConfigureBorders(wwin);
		    }
		} else if (!WFLAGP(wwin, no_miniaturizable)) {
		    WSETUFLAG(wwin, no_miniaturizable, 1);
		    WSETUFLAG(wwin, no_miniaturize_button, 1);
		    if (wwin->frame)
			wWindowConfigureBorders(wwin);
		}
	    }
	}
	break;
	
     default:
	if (event->atom==_XA_WM_PROTOCOLS) {

	    PropGetProtocols(wwin->client_win, &wwin->protocols);

	    WSETUFLAG(wwin, kill_close, !wwin->protocols.DELETE_WINDOW);

	    if (wwin->frame)
		wWindowUpdateButtonImages(wwin);

	} else if (event->atom==_XA_WM_COLORMAP_WINDOWS) {
	    
	    GetColormapWindows(wwin);
	    wColormapInstallForWindow(wwin->screen_ptr, wwin);

	} else if (event->atom==_XA_WINDOWMAKER_MENU) {
	    WApplication *wapp;

	    wapp = wApplicationOf(wwin->main_window);
	    if (wapp) {
		if (wapp->menu) {
		    /* update menu */
		    /* TODO: remake appmenu update */
		    wAppMenuDestroy(wapp->menu);
		}
		wapp->menu = wAppMenuGet(wwin->screen_ptr, wwin->main_window);
		/* make the appmenu be mapped */
		wSetFocusTo(wwin->screen_ptr, NULL);
		wSetFocusTo(wwin->screen_ptr, 
			    wwin->screen_ptr->focused_window);
	    }
	} else if (event->atom==_XA_GNUSTEP_WM_ATTR) {
	    GNUstepWMAttributes *attr;

	    PropGetGNUstepWMAttr(wwin->client_win, &attr);
	    
	    wWindowUpdateGNUstepAttr(wwin, attr);
	    
	    XFree(attr);
	} else {
#ifdef KWM_HINTS
	    Bool done;

	    done = wKWMCheckClientHintChange(wwin, event);
#endif /* KWM_HINTS */
	}
    }
}


/*
 *---------------------------------------------------------------------- 
 * wClientGetNormalHints--
 * 	Get size (normal) hints and a default geometry for the client 
 * window. The hints are also checked for inconsistency. If geometry is
 * True, the returned data will account for client specified initial
 * geometry.
 * 
 * Side effects:
 * 	normal_hints is filled with valid data.
 *---------------------------------------------------------------------- 
 */
void
wClientGetNormalHints(WWindow *wwin, XWindowAttributes *wattribs, Bool geometry,
		      int *x, int *y, unsigned *width, unsigned *height)
{
    int pre_icccm = 0;		       /* not used */

    /* find a position for the window */
    if (!wwin->normal_hints)
        wwin->normal_hints = XAllocSizeHints();

    if (!PropGetNormalHints(wwin->client_win, wwin->normal_hints, &pre_icccm)) {
	wwin->normal_hints->flags = 0;
    }
    *x = wattribs->x;
    *y = wattribs->y;
    
    *width = wattribs->width;
    *height = wattribs->height;

    if (!(wwin->normal_hints->flags & PWinGravity)) {
	wwin->normal_hints->win_gravity = NorthWestGravity;
    }
    if (!(wwin->normal_hints->flags & PMinSize)) {
	wwin->normal_hints->min_width = MIN_WINDOW_SIZE;
	wwin->normal_hints->min_height = MIN_WINDOW_SIZE;
    }
    if (!(wwin->normal_hints->flags & PBaseSize)) {
	wwin->normal_hints->base_width = 0;
	wwin->normal_hints->base_height = 0;
    }
    if (!(wwin->normal_hints->flags & PMaxSize)) {
	wwin->normal_hints->max_width = wwin->screen_ptr->scr_width*2;
	wwin->normal_hints->max_height = wwin->screen_ptr->scr_height*2;
    }

    /* some buggy apps set weird hints.. */
    if (wwin->normal_hints->min_width <= 0) 
      wwin->normal_hints->min_width = MIN_WINDOW_SIZE;
    
    if (wwin->normal_hints->min_height <= 0)
      wwin->normal_hints->min_height = MIN_WINDOW_SIZE;


    if (wwin->normal_hints->max_width < wwin->normal_hints->min_width)
	wwin->normal_hints->max_width = wwin->normal_hints->min_width;

    if (wwin->normal_hints->max_height < wwin->normal_hints->min_height)
	wwin->normal_hints->max_height = wwin->normal_hints->min_height;

    if (!(wwin->normal_hints->flags & PResizeInc)) {
	wwin->normal_hints->width_inc = 1;
	wwin->normal_hints->height_inc = 1;
    } else {	
	if (wwin->normal_hints->width_inc <= 0)
	  wwin->normal_hints->width_inc = 1;
	if (wwin->normal_hints->height_inc <= 0)
	  wwin->normal_hints->height_inc = 1;
    }
    
    if (wwin->normal_hints->flags & PAspect) {
	if (wwin->normal_hints->min_aspect.x < 1)
	    wwin->normal_hints->min_aspect.x = 1;
	if (wwin->normal_hints->min_aspect.y < 1)
	    wwin->normal_hints->min_aspect.y = 1;
	
	if (wwin->normal_hints->max_aspect.x < 1)
	    wwin->normal_hints->max_aspect.x = 1;
	if (wwin->normal_hints->max_aspect.y < 1)
	    wwin->normal_hints->max_aspect.y = 1;
    }
    
    if (wwin->normal_hints->min_height > wwin->normal_hints->max_height) {
	wwin->normal_hints->min_height = wwin->normal_hints->max_height;
    }
    if (wwin->normal_hints->min_width > wwin->normal_hints->max_width) {
	wwin->normal_hints->min_width = wwin->normal_hints->max_width;
    }

#ifdef IGNORE_PPOSITION
    wwin->normal_hints->flags &= ~PPosition;
#endif

    if (pre_icccm && !wwin->screen_ptr->flags.startup && geometry) {
	if (wwin->normal_hints->flags & (USPosition|PPosition)) {
	    *x = wwin->normal_hints->x;
	    *y = wwin->normal_hints->y;
	}
	if (wwin->normal_hints->flags & (USSize|PSize)) {
	    *width = wwin->normal_hints->width;
	    *height = wwin->normal_hints->height;
	}
    }
}


void 
GetColormapWindows(WWindow *wwin)
{
#ifndef NO_CRASHES
    if (wwin->cmap_windows) {
	XFree(wwin->cmap_windows);
    }
    
    wwin->cmap_windows = NULL;
    wwin->cmap_window_no = 0;

    if (!XGetWMColormapWindows(dpy, wwin->client_win, &(wwin->cmap_windows),
			      &(wwin->cmap_window_no))
	|| !wwin->cmap_windows) {
	wwin->cmap_window_no = 0;
	wwin->cmap_windows = NULL;
    }

#endif
}
