/* wampager.c

Copyright © 2003 Andreas Larsson, David Reveman.

This file is part of Waimea.

Waimea 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, or (at your option) any later
version.

Waimea 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 Waimea; see the file COPYING. If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */

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

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xmd.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>
#include <unistd.h>

#include "background.xpm"
#include "foreground.xpm"
#include "grid.xpm"

#define WADOCK_WIDTH 64
#define WADOCK_HEIGHT 64
#define WADOCK_X_OFFSET 5
#define WADOCK_Y_OFFSET 5

Display *d;
Window win;
Window win2;
GC gc;
Visual *visual = NULL;
int depth = 0;
Window rootWin;
char *program_name;

Pixmap maskPixmap;
Pixmap backPixmap;
Pixmap frontPixmap;
Pixmap gridPixmap;
Pixmap pagerPixmap;

Atom net_desktop_viewport;
Atom net_desktop_geometry;
Atom net_current_desktop;

int snap_button = 1;
int exact_button = 3;

unsigned int nrOfDesktopsX = 0;
unsigned int nrOfDesktopsY = 0;
unsigned int screenSizeX;
unsigned int screenSizeY;
unsigned int virtualDesktopSizeX = 0;
unsigned int virtualDesktopSizeY = 0;
unsigned int desktopPosX = 0;
unsigned int desktopPosY = 0;

void usage(void) {
    printf("Usage: %s [-snap BUTTON] [-exact BUTTON]\n", program_name);
}

void createWindow(char *name, int width, int height) {
    XClassHint *classHints;
    XWMHints *wmHints;
    XTextProperty	pname;
    XGCValues		gcv;

    /* Create Windows */
    win = XCreateSimpleWindow(d, rootWin, 0, 0, width, height, 0, 0, 0);
    win2 = XCreateSimpleWindow(d, rootWin, 0, 0, 1, 1, 0, 0, 0);

    /* Set ClassHint */
    if (!(classHints = XAllocClassHint())){
        fprintf(stderr, "%s: can't allocate memory for class hints\n",
                program_name);
        exit(1);
    }
    classHints->res_class = "DockApp";
    classHints->res_name = name;
  
    XSetClassHint(d, win2, classHints);
    XFree(classHints);
    
    /* Set WMHints */
    if (!(wmHints = XAllocWMHints())){
        fprintf(stderr, "%s: can't allocate memory for wm hints\n",
                program_name);
        exit(1);
    }
    wmHints->flags = IconWindowHint|WindowGroupHint;
    wmHints->flags |= StateHint;
    wmHints->initial_state = WithdrawnState;
    wmHints->window_group = win2;
    wmHints->icon_window = win;  
    XSetWMHints(d, win2, wmHints);
    if (XStringListToTextProperty(&name, 1, &pname) == 0) {
        fprintf(stderr, "%s: can't allocate window name\n", program_name);
        exit(1);
    }
    XSetWMName(d, win, &pname);
    depth = DefaultDepth(d, DefaultScreen(d));
    visual = DefaultVisual(d, DefaultScreen(d));
    gc = DefaultGC(d, DefaultScreen(d));
    gcv.graphics_exposures = False;
    XChangeGC(d, gc, GCGraphicsExposures, &gcv);
  
    XFlush(d);
}

void getVirtualSize() {
    CARD32 *data;
    int real_format;
    Atom real_type;
    unsigned long items_read, items_left;
    int _d;
    unsigned int _ud;
    Window _wd;

    XGetGeometry(d, rootWin, &_wd, &_d, &_d, &screenSizeX, &screenSizeY, 
                 &_ud, &_ud);

    if (XGetWindowProperty(d, rootWin, net_desktop_geometry, 0L, 2L, False,
                           XA_CARDINAL, &real_type, &real_format, &items_read,
                           &items_left, (unsigned char **) &data) == Success &&
        items_read >= 2) {
        virtualDesktopSizeX = data[0];
        virtualDesktopSizeY = data[1];
        free(data);
    }
    nrOfDesktopsX = virtualDesktopSizeX/screenSizeX;
    nrOfDesktopsY = virtualDesktopSizeY/screenSizeY;
}


void pixmapFromData(char **data, Pixmap *pixmap, Pixmap *mask) {
    XpmAttributes	xpmAttr;
  
    xpmAttr.valuemask = XpmCloseness;
    xpmAttr.closeness = 40000;
    if (XpmCreatePixmapFromData(d, win, data, pixmap, mask, &xpmAttr) == -1) {
        fprintf(stderr, "%s: error reading pixmap\n", program_name);
        exit(-1);
    }
}

void setPixmap(Pixmap pixmap) {
    XSetWindowBackgroundPixmap(d, win, pixmap);
    XClearWindow(d, win);
    XFlush(d);
}

int setupPixmaps() {
    pixmapFromData(background_xpm, &backPixmap, &maskPixmap);
    pixmapFromData(foreground_xpm, &frontPixmap, None);
    pixmapFromData(grid_xpm, &gridPixmap, None);
    pagerPixmap = XCreatePixmap(d, win, WADOCK_WIDTH, WADOCK_HEIGHT, depth);
    return 1;
}

int updatePager() {
    int i;

    XCopyArea(d, backPixmap, pagerPixmap, gc, 0, 0,
              WADOCK_WIDTH, WADOCK_HEIGHT, 0, 0);
    
    if (virtualDesktopSizeX) {
        XCopyArea(d, frontPixmap, pagerPixmap, gc,
                  (1.0 * desktopPosX * (WADOCK_WIDTH - WADOCK_X_OFFSET * 2)) /
                  (screenSizeX * nrOfDesktopsX) + WADOCK_X_OFFSET,
                  
                  (1.0 * desktopPosY * (WADOCK_HEIGHT - WADOCK_Y_OFFSET * 2)) /
                  (screenSizeY * nrOfDesktopsY) + WADOCK_Y_OFFSET,
              
                  (1.0 * WADOCK_WIDTH - WADOCK_X_OFFSET * 2) /
                  nrOfDesktopsX + 1,
                  (1.0 * WADOCK_HEIGHT - WADOCK_Y_OFFSET * 2) /
                  nrOfDesktopsY + 1,
                  
                  (1.0 * desktopPosX * (WADOCK_WIDTH - WADOCK_X_OFFSET * 2)) /
                  (screenSizeX * nrOfDesktopsX) + WADOCK_X_OFFSET,
                  
                  (1.0 * desktopPosY * (WADOCK_HEIGHT - WADOCK_Y_OFFSET * 2)) /
                  (screenSizeY * nrOfDesktopsY) + WADOCK_Y_OFFSET);
    
        for (i = 1; i < nrOfDesktopsX; i++)
            XCopyArea(d, gridPixmap, pagerPixmap, gc,
                      (1.0 * WADOCK_WIDTH - WADOCK_X_OFFSET * 2) /
                      nrOfDesktopsX * i + WADOCK_X_OFFSET, 0, 1, WADOCK_HEIGHT,
                      (1.0 * WADOCK_WIDTH - WADOCK_X_OFFSET * 2) /
                      nrOfDesktopsX * i + WADOCK_X_OFFSET, 0);
    
        for (i = 1; i < nrOfDesktopsY; i++)
            XCopyArea(d, gridPixmap, pagerPixmap, gc, 0,
                      (1.0 * WADOCK_HEIGHT - WADOCK_Y_OFFSET * 2) /
                      nrOfDesktopsY * i + WADOCK_Y_OFFSET, WADOCK_WIDTH, 1, 0,
                      (1.0 * WADOCK_HEIGHT - WADOCK_Y_OFFSET * 2) /
                      nrOfDesktopsY * i + WADOCK_Y_OFFSET);
    }
    setPixmap(pagerPixmap);
    return 1;
}


void updateDesktopPos() {
    CARD32 *data;
    int real_format;
    Atom real_type;
    unsigned long items_read, items_left;
    int desktop = 0;

    if (XGetWindowProperty(d, rootWin, net_current_desktop, 0L, 1L, False,
                           XA_CARDINAL, &real_type, &real_format, &items_read,
                           &items_left, (unsigned char **) &data) == Success &&
        items_read) {
        desktop = data[0];
        XFree(data);
    }
  
    if (XGetWindowProperty(d, rootWin ,net_desktop_viewport, 0L, 8192L, False,
                           XA_CARDINAL, &real_type, &real_format, &items_read,
                           &items_left, (unsigned char **) &data) == Success &&
        items_read >= 2) {
        if (items_read >= (desktop * 2 + 2)) {
            desktopPosX = data[desktop * 2];
            desktopPosY = data[desktop * 2 + 1];
        }
        XFree(data);
    }
}

void setupAtoms(void) {
    net_desktop_viewport =
        XInternAtom(d, "_NET_DESKTOP_VIEWPORT", False);
    net_desktop_geometry =
        XInternAtom(d, "_NET_DESKTOP_GEOMETRY", False);
    net_current_desktop =
        XInternAtom(d, "_NET_CURRENT_DESKTOP", False);
}

void moveDesktopPos(int x, int y) {
    XEvent sevent;
  
    sevent.type = ClientMessage;
    sevent.xclient.message_type = net_desktop_viewport;
    sevent.xclient.display = d;
    sevent.xclient.window = rootWin;
    sevent.xclient.format = 32;
    sevent.xclient.data.l[0] = x;
    sevent.xclient.data.l[1] = y;
    XSendEvent(d, rootWin, False, PropertyChangeMask, &sevent);
}

void setDesktopExakt(int x, int y) {
    int newX, newY;
  
    if ((x > WADOCK_X_OFFSET) && (x < WADOCK_WIDTH-WADOCK_X_OFFSET) &&
        (y > WADOCK_Y_OFFSET) && (x < WADOCK_HEIGHT-WADOCK_Y_OFFSET)) {

        newX = ((1.0 * x + ((WADOCK_WIDTH - WADOCK_X_OFFSET) / nrOfDesktopsX)
                 - WADOCK_X_OFFSET) /
                ((WADOCK_WIDTH - WADOCK_X_OFFSET * 2) / nrOfDesktopsX) - 1) *
             screenSizeX - screenSizeX / 2;
    
        newY = ((1.0 * y + ((WADOCK_HEIGHT-WADOCK_Y_OFFSET) / nrOfDesktopsY)
                 - WADOCK_Y_OFFSET) /
                ((WADOCK_HEIGHT - WADOCK_Y_OFFSET * 2) / nrOfDesktopsY) - 1) *
            screenSizeY - screenSizeY / 2;

        if (newX < 0)
            newX = 0;
        if (newY < 0)
            newY = 0;
        if (newX > virtualDesktopSizeX - screenSizeX)
            newX = virtualDesktopSizeX - screenSizeX;
        if (newY > virtualDesktopSizeY - screenSizeY)
            newY = virtualDesktopSizeY - screenSizeY;
        
        moveDesktopPos(newX, newY);
    }
}

void setDesktop(int x, int y) {
    int newX, newY;
  
    if ((x > WADOCK_X_OFFSET) && (x < WADOCK_WIDTH-WADOCK_X_OFFSET) &&
        (y > WADOCK_Y_OFFSET) && (x < WADOCK_HEIGHT-WADOCK_Y_OFFSET)) {
    
        newX = (x + ((WADOCK_WIDTH - WADOCK_X_OFFSET) / nrOfDesktopsX) -
                WADOCK_X_OFFSET) / ((WADOCK_WIDTH - WADOCK_X_OFFSET * 2) /
                                    nrOfDesktopsX) - 1;
        
        newY = (y + ((WADOCK_HEIGHT - WADOCK_Y_OFFSET) / nrOfDesktopsY) -
                WADOCK_Y_OFFSET) / ((WADOCK_HEIGHT - WADOCK_Y_OFFSET * 2) /
                                    nrOfDesktopsY) - 1;
        
        newX *= screenSizeX;
        newY *= screenSizeY;
        
        moveDesktopPos(newX, newY);
    }
}

int main(int argc, char **argv) {
    XEvent event;
    int i, snap_drag = 0, exact_drag = 0;
  
    program_name = argv[0];
  
    for (i = 1; i < argc; i++) {
        if (! strcmp(argv[i], "--version")) {
            fprintf(stderr, "%s %s\n", PACKAGE, VERSION);
            exit(0);
        } else  if (! strcmp(argv[i], "--help")) {
            usage();
            exit(0);
        } else if (! strcmp(argv[i], "-exact")) {
            if (i + 1 < argc) exact_button = atoi(argv[i++ + 1]);
            else {
                fprintf(stderr, "%s: option '-exact' equires an argument\n",
                        program_name);
                usage();
                exit(1);
            }
        } else if (! strcmp(argv[i], "-snap")) {
            if (i + 1 < argc) snap_button = atoi(argv[i++ + 1]);
            else {
                fprintf(stderr, "%s: option '-snap' equires an argument\n",
                        program_name);
                usage();
                exit(1);
            }
        } else {
            fprintf(stderr, "%s: unrecognized option '%s'\n",
                    program_name, argv[i]);
            usage();
            exit(1);
        }
    }
  
    if (! (d = XOpenDisplay(NULL))) {
        fprintf(stderr, "%s: unable to open display\n", program_name);
    }
    rootWin = DefaultRootWindow(d);
    
    setupAtoms();
    createWindow(program_name, WADOCK_WIDTH, WADOCK_HEIGHT);
    setupPixmaps();
    XShapeCombineMask(d, win, ShapeBounding, 0, 0, maskPixmap, ShapeSet);
    
    getVirtualSize();
    
    updateDesktopPos();
    updatePager();
    
    XMapWindow(d, win2);
  
    XSelectInput(d, rootWin, PropertyChangeMask);
    XSelectInput(d, win, ButtonPressMask | ButtonReleaseMask |
                 PointerMotionMask);
  
    for (;;) {
        XNextEvent(d, &event);
        switch (event.type) {
            case PropertyNotify:
                if (event.xproperty.state == PropertyDelete) {
                    if (event.xproperty.atom == net_desktop_viewport)
                        virtualDesktopSizeX = 0;
                }
                else {
                    if (event.xproperty.atom == net_desktop_viewport) {
                        updateDesktopPos();
                        updatePager();
                    }
                    else if (event.xproperty.atom == net_desktop_geometry) {
                        getVirtualSize();
                        updateDesktopPos();
                        updatePager();
                    }
                }
                break;
            case Expose:
                break;
            case DestroyNotify:
                XCloseDisplay(d);
                exit(0);
                break;
            case ButtonPress:
                if ((event.xbutton.button == snap_button) &&
                    (virtualDesktopSizeX)) {
                    snap_drag = 1;
                    setDesktop(event.xbutton.x, event.xbutton.y);
                } else if ((event.xbutton.button == exact_button) &&
                           (virtualDesktopSizeX)) {
                    exact_drag = 1;
                    setDesktopExakt(event.xbutton.x, event.xbutton.y);
                }
                break;
            case ButtonRelease:
                if ((event.xbutton.button == snap_button) &&
                    (virtualDesktopSizeX)) {
                    snap_drag = 0;
                    setDesktop(event.xbutton.x, event.xbutton.y);
                }
                if ((event.xbutton.button == exact_button) &&
                    (virtualDesktopSizeX)) {
                    exact_drag = 0;
                }
                break;
            case MotionNotify:
                if ((snap_drag || exact_drag) && (virtualDesktopSizeX))
                    setDesktopExakt(event.xbutton.x, event.xbutton.y);
                break;
        }
    }
    XCloseDisplay(d);
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1