/*
 * Copyright Jon Dowland 2004
 * 
 * This program is distributed under the terms of the GNU General Public 
 * Licence (see COPYING)
 *
 * $Id: wm.c,v 1.49 2004/11/21 18:04:19 jon Exp $
 * vim: set ts=4 sts=4 expandtab sw=4 tw=78 autoindent:

    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 <X11/Xlib.h>
#include <stdlib.h> /* exit */
#include <stdio.h>  /* fprintf, stderr */
#include <unistd.h> /* sleep */
#include <errno.h>  /* errno */
#include <string.h> /* strerror */

#include "list.h"

#define PRINT(...) fprintf (stderr, __VA_ARGS__)

#define WM_BAR_HEIGHT      24
#define WM_BAR_WIDTH_RATIO 1
#define WM_BAR_PADDING     2 /* space between bar and container */
#define WM_BAR_FONT "*-helvetica-*-12-*"

#define WM_BAR_BACKGROUND       WhitePixel(display, DefaultScreen(display))
#define WM_CONTAINER_BORDER     BlackPixel(display, DefaultScreen(display))
#define WM_CONTAINER_BACKGROUND BlackPixel(display, DefaultScreen(display))

/* a window and its decorations */
struct window_group {
    struct list_head list;
    Window container;
    Window client;
    Window toolbar;
};

/* write given text into given window */
void write_text(Window win, char * text, Display * display, int screen) {
    int x,y;
    XFontStruct *font_info;
    GC gc;
    XGCValues values;
    
    gc = XCreateGC(display, win, 0, &values);

    XSync(display, False);
    font_info = XLoadQueryFont(display, WM_BAR_FONT);
    XSetFont(display, gc, font_info->fid);

    x = WM_BAR_HEIGHT/2 - ((font_info->ascent+font_info->descent)/2);
    y = -font_info->descent + WM_BAR_HEIGHT/2 + ((font_info->ascent+font_info->descent)/2);
    XDrawString(display, win, gc, x, y, text, strlen(text));
}

/*
 * create a new window group structure
 */
struct window_group * create_group() {
    struct window_group * w 
        = (struct window_group *) malloc(sizeof(struct window_group));
    if(NULL == w) {
            fprintf(stderr, "malloc error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
    }
    return w;
}

/*
 * create a new group, assign a window to it, create a toolbar
 */
struct window_group * create_assign_group(Display * display, Window client) {
    struct window_group * wg;
    Window d; /* d for dummy */
    char *window_name;
    unsigned int x,y,w,h,d2,d3;

    wg = create_group();
    XGetGeometry(display,client,&d,&x,&y,&w,&h,&d2,&d3);

    wg->client = client;
    wg->container = XCreateSimpleWindow(
        display, RootWindow(display, DefaultScreen(display)),
        x,
        y,
        w,h+WM_BAR_HEIGHT+WM_BAR_PADDING,WM_BAR_PADDING,WM_CONTAINER_BORDER,
        WM_CONTAINER_BACKGROUND
    );
    wg->toolbar = XCreateSimpleWindow(
        display, wg->container,
        0,0,
        w/WM_BAR_WIDTH_RATIO,WM_BAR_HEIGHT,0,BlackPixel(display,DefaultScreen(display)),
        WM_BAR_BACKGROUND
    );
    XSelectInput(display, wg->toolbar, ButtonPressMask|ButtonReleaseMask|Button1MotionMask|ExposureMask); 
    XReparentWindow(display, wg->client, wg->container, 0,WM_BAR_HEIGHT+WM_BAR_PADDING);
    XSelectInput(display, wg->container, SubstructureNotifyMask);
    XMapWindow(display, wg->container);
    XMapWindow(display, wg->toolbar);
    
    XFetchName(display, client, &window_name);
    if(!window_name) window_name = "untitled";
    write_text(wg->toolbar, window_name, display,DefaultScreen(display));
    
    return wg;
}

/*
 * search for a group in a list containing a window
 */
struct window_group * find_group(Window w, struct list_head * l) {
    struct list_head * c;
    list_for_each(c, l) {
        struct window_group * wg;
        wg = list_entry(c,struct window_group, list); 
        if(wg->client == w 
        || wg->toolbar == w
        || wg->container == w
        ) return wg;
    }
    return NULL;
}

/* capture existing windows */
void capture_existing_xwindows(Display * display, struct list_head * list) {
    Window dummy1, dummy2, *children;
    int childcount, i;
    int screen = DefaultScreen(display);

    XQueryTree(display, RootWindow(display, screen), &dummy1, &dummy2, &children, &childcount);
    for(i = 0; i < childcount; ++i) {
        struct window_group * wg;
        wg = create_assign_group(display, children[i]);
        list_add(&wg->list, list);
    }
    XFree(children);
}

/*
 * create a dummy window, for testing
 */
void create_dummy_window(Display * display, struct list_head * list) {
    struct window_group * win;
    Window client;

    client = XCreateSimpleWindow( 
        display, RootWindow(display, DefaultScreen(display)),
        DisplayWidth(display, DefaultScreen(display)) / 2,
        DisplayHeight(display, DefaultScreen(display)) / 2,
        320, 240, 0, BlackPixel(display, DefaultScreen(display)),
        WhitePixel(display, DefaultScreen(display))
    );
    win = create_assign_group(display, client);
    list_add(&win->list, list);

    XMapWindow(display, win->client);
}



int main() {
    Display * display;
    char * disp;
    int screen;
    char drag = 0;
    int clicked_x, clicked_y;
    struct list_head list;


    INIT_LIST_HEAD(&list);
    
    /* determine the desired display */
    disp = getenv("DISPLAY");
    if(!disp) disp = "localhost:0";
    
    /* attempt to open the desired display */
    display = XOpenDisplay(disp);
    if(!display) {
        fprintf(stderr, "Cannot open display %s\n", disp);
        exit(EXIT_FAILURE);
    }

    screen = DefaultScreen(display);

    capture_existing_xwindows(display, &list);
    
    XFlush(display);

    XSelectInput(display, RootWindow(display, screen), SubstructureNotifyMask); 

    while(1) {
        XEvent event;
        XNextEvent(display, &event);
        switch(event.type) {
            case ButtonPress:
                if(1 == event.xbutton.button) {
                    struct window_group * wg 
                        = find_group(event.xbutton.window, &list);
                    if(wg) {
                        XRaiseWindow(display, wg->container);
                        XSetInputFocus(display, wg->client, RevertToNone, CurrentTime);

                        if(wg->toolbar == event.xbutton.window) {
                            drag = 1;
                            clicked_x = event.xbutton.x;
                            clicked_y = event.xbutton.y;
                        }
                    }
                    
                } else if(2 == event.xbutton.button) {
                } else if(3 == event.xbutton.button) {

                    struct window_group * wg 
                        = find_group(event.xbutton.window, &list);
                    if(wg && wg->toolbar == event.xbutton.window) {
                        struct list_head * c;
                        Window d; /* d for dummy */
                        unsigned int x,y,w,h,d2,d3;

                        list_for_each(c, &list) {
                            wg = list_entry(c,struct window_group, list);
                            XGetGeometry(display,wg->container,&d,&x,&y,&w,&h,&d2,&d3);
                            XReparentWindow(display, wg->client, RootWindow(display, DefaultScreen(display)), x,y);
                        }
                        XCloseDisplay(display);
                        exit(EXIT_SUCCESS);
                    }

                }
                break;
            
            case ButtonRelease:
                drag = 0;
                break;
            case MotionNotify: 
                if(drag) {
                    struct window_group * wg 
                        = find_group(event.xbutton.window, &list);
                    if(wg) {
                        XMoveWindow(display, wg->container,
                            event.xmotion.x_root - clicked_x,
                            event.xmotion.y_root - clicked_y + WM_BAR_PADDING);
                    }
                }
                break;

            /*
             * decorate new windows with our toolbar
             */
            case CreateNotify: {
                struct window_group * wg;
                wg = find_group(event.xcreatewindow.window, &list);
                if(!wg) { /* NOT already grouped clients / toolbars */
                    wg = create_assign_group(display, event.xcreatewindow.window);
                    if(wg) list_add(&wg->list, &list);
                }
                break;
            } 

            /*
             * catch windows being deleted - remove our supporting structures
             */
            case DestroyNotify: {
                struct window_group * wg;
                wg = find_group(event.xdestroywindow.window, &list);
                if(wg && wg->client == event.xdestroywindow.window) {
                    XDestroyWindow(display, wg->container);
                    list_del(&wg->list);
                    free(wg);
                }
                break;
            }

            /*
             * when a toolbar is exposed, re-draw the text
             */
            case Expose: {
                struct window_group * group = find_group(event.xexpose.window, &list);
                if(group) if(event.xexpose.window == group->toolbar) {
                    char *window_name;
                    XFetchName(display,group->client, &window_name);
                    if(!window_name) window_name = "untitled";
                    write_text(group->toolbar, window_name, display,screen);
                }
                break;
            } /* end Expose */

        } /* end switch */
    } /* end while loop */
    
    return 0;
}

/*
 * $Log: wm.c,v $
 * Revision 1.49  2004/11/21 18:04:19  jon
 * we must listen to the container now for sub-structure events, since the root window will not report DestroyEvents for the clients (the client windows are no longer parented to it!)
 *
 * Revision 1.48  2004/11/21 17:59:47  jon
 * catch expose events and re-draw the titlebar if necessary
 *
 * Revision 1.47  2004/11/21 17:56:31  jon
 * get names of windows when creating decorations, apply to titlebar
 *
 * Revision 1.46  2004/11/20 21:51:42  jon
 * beginnings of writing text
 *
 * Revision 1.45  2004/11/20 16:14:12  jon
 * fixed a raise bug: we raise the container not the constituents
 *
 * Revision 1.44  2004/11/20 16:13:30  jon
 * the find_window function now considers the container, too
 * fixed bug: was looking for unmap events rather then destroy events
 *
 * Revision 1.43  2004/11/20 16:11:40  jon
 * parameterize the colours into #define's
 *
 * Revision 1.42  2004/11/20 16:10:58  jon
 * when reparenting the client window, make the container get the x/y coords
 * of the client, so that clients that are initially at the edge of a screen
 * are shunted inwards and the toolbar is visible.
 *
 * Revision 1.41  2004/11/20 16:09:57  jon
 * renamed list existing windows to capture... more accurate
 *
 * Revision 1.40  2004/11/20 16:07:22  jon
 * when exiting, reparent all client windows to the root window so they aren't killed by our exit
 * also, move the window's X/Y location (top-left corner) to that of it's former container
 *
 * Revision 1.39  2004/11/20 16:05:53  jon
 * added a container Window to our group structure
 * reparent the client and toolbars to belong to this container
 * apply move and destroy to the container (other windows cascade)
 * define WM_BAR_PADDING as the amount of space around the toolbar
 *
 * Revision 1.38  2004/11/20 16:04:14  jon
 * added some diagnostic output
 *
 * Revision 1.37  2004/11/20 16:03:40  jon
 * changed map/unmap event handlers into create/destroy handlers
 *
 * Revision 1.36  2004/11/20 16:01:13  jon
 * removed call to create_dummy_window: not needed
 * added event handlers to catch create/destroy events. The existing map/unmap
 * handlers would most likely be better employed on create/destroy events as
 * I can imagine map/unmap events happening and not signifying the end of
 * a window.
 *
 * Revision 1.35  2004/11/20 16:00:29  jon
 * changed the toolbar height (personal taste)
 *
 * Revision 1.34  2004/11/20 15:59:53  jon
 * added UnmapNotify handler
 * when a window is unmapped, it has it's structure freed, the list entry removed and the toolbar Window destroyed.
 *
 * Revision 1.33  2004/11/20 15:59:04  jon
 * give focus to a raised window
 *
 * Revision 1.32  2004/11/20 15:58:10  jon
 * added a MapNotify event handler
 * ensure that a mapped window is not already in our list
 *
 * Revision 1.31  2004/11/20 15:57:01  jon
 * experimenting with putting the RCS special 'Log' at the end of the file
 *
 */
