<commit_before>/* $Id$
 * This was written by <NAME> in 2001.
 * This code is in the public domain and the author disclaims all
 * copyright privileges.
 */

/*
 * this is probably the most complex part of the window manager
 * because I'm unhappy with all the implementations of this that I've
 * seen.
 */

#include <stdio.h>
#include <stdlib.h>
#include <X11/keysym.h>

#include "move-resize.h"
#include "client.h"
#include "cursor.h"
#include "event.h"
#include "xwm.h"
#include "malloc.h"
#include "debug.h"

#ifndef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif /* MIN */

#ifndef MAX
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#endif /* MAX */

#ifndef ABS
#define ABS(x) ((x) < 0 ? (-(x)) : (x))
#endif /* ABS */

/*
 * These are the possible directions in which one can resize a window,
 * depending on which direction the mouse moves at first
 * 
 * UNKNOWN is obviously a marker to say the direction hasn't yet been
 * identified
 * 
 * We only constrain the window resizing to one dimension by using a
 * keypress; window movement is never constrained to one direction.
 */

typedef enum {
    NW = 0, NE, SE, SW, NORTH, SOUTH, EAST, WEST, UNKNOWN
} resize_direction_t;

typedef enum {
    FIRST, MIDDLE, LAST
} resize_ordinal_t;

int moving = 0;
int sizing = 0;

static int keycode_Escape = 0;
static int keycode_Return, keycode_Up, keycode_Down, keycode_Left;
static int keycode_Right, keycode_j, keycode_k, keycode_l, keycode_h;
static int keycode_w, keycode_a, keycode_s, keycode_d;
static int keycode_Control_L, keycode_Control_R, keycode_space;

/* optimization, see display_geometry() */
static char *titlebar_display = NULL;

static void compress_motion(XEvent *xevent);
static void process_resize(client_t *client, int new_x, int new_y,
                           resize_direction_t direction,
                           resize_direction_t old_direction,
                           int *old_x, int *old_y,
                           position_size *orig,
                           resize_ordinal_t ordinal);
static resize_direction_t get_direction(client_t *client, int x, int y);
static void display_geometry(char *s, client_t *client);
static void drafting_lines(client_t *client, resize_direction_t direction,
                           int x1, int y1, int x2, int y2);
static void draw_arrowhead(int x, int y, resize_direction_t direction);
static void xrefresh();
static void cycle_resize_direction_mouse(resize_direction_t *current,
                                         resize_direction_t *old);
static void cycle_resize_direction_keyboard(resize_direction_t *current);
static void set_keys();
static void move_constrain(client_t *client);
static int get_width_resize_inc(client_t *client);
static int get_height_resize_inc(client_t *client);
static void move_inform_client(client_t *client);
static Bool mouse_over_client(client_t *client);

void resize_maximize(XEvent *xevent, void *v)
{
    int h_inc, w_inc;
    client_t *client;

    client = client_find(xevent->xbutton.window);
    if (client == NULL) return;
    h_inc = get_height_resize_inc(client);
    w_inc = get_width_resize_inc(client);

    if (client->prev_width != -1 || client->prev_height != -1) {
        if (client->prev_height != -1) {
            client->height = client->prev_height;
            client->y = client->prev_y;
        }
        if (client->prev_width != -1) {
            client->width = client->prev_width;
            client->x = client->prev_x;
        }
        client->prev_x = client->prev_y = -1;
        client->prev_width = client->prev_height = -1;
    } else {
        client->prev_x = client->x;
        client->prev_y = client->y;
        client->prev_width = client->width;
        client->prev_height = client->height;
        client->width = (DisplayWidth(dpy, scr) / w_inc) * w_inc;
        client->height = (DisplayHeight(dpy, scr) / h_inc) * h_inc;
        client->y = client->x = 0;
    }
    XMoveResizeWindow(dpy, client->frame, client->x, client->y,
                      client->width, client->height);
    if (client->titlebar != None) {
        XResizeWindow(dpy, client->window, client->width,
                      client->height - TITLE_HEIGHT);
        XResizeWindow(dpy, client->titlebar, client->width, TITLE_HEIGHT);
    } else {
        XResizeWindow(dpy, client->window, client->width, client->height);
    }
}

/*
 * This takes over the event loop and grabs the pointer until the move
 * is complete; we keep most of the data 'static' as this may get
 * called recursively and all simultaneously-running invocations need
 * to keep some of the same state (the resize code is much worse).
 */
void move_client(XEvent *xevent, void *v)
{
    client_t *client = NULL;
    int x_start, y_start;       /* only used for mouse move */
    int orig_x, orig_y, delta;
    int have_mouse;
    unsigned int init_button;
    XEvent event1;
    enum { CONTINUE, DONE, RESET, RESIZE } action;

    if (moving || sizing) return;
    if (keycode_Escape == 0) set_keys();
    if (xevent->type == ButtonPress) {
        client = client_find(xevent->xbutton.window);
        x_start = xevent->xbutton.x_root;
        y_start = xevent->xbutton.y_root;
        init_button = xevent->xbutton.button;
        have_mouse = 1;
    } else if (xevent->type == KeyPress) {
        client = client_find(xevent->xkey.window);
        have_mouse = 0;
    } else {
        fprintf(stderr, "XWM: Error, move_client called incorrectly\n");
        return;
    }
    if (client == NULL) {
        fprintf(stderr, "XWM: Not moving a non-client\n");
        XUngrabPointer(dpy, CurrentTime);
        return;
    }
    
    moving = 1;
    orig_x = client->x;
    orig_y = client->y;
    
    /* I kind of like being able to move a window
     * without giving it focus, and this doesn't
     * cause any problems: */
    /* focus_set(client); */
    /* focus_ensure(); */

    /* here we grab the pointer for the client window; it also works
     * OK if we grab the root window or our frame, but it introduces a
     * problem:  the client receives some stray LeaveNotify, FocusOut,
     * EnterNotify and FocusIn events; in particular, xterm doesn't
     * know how to deal with them and it will never correctly deal
     * with a FocusOut event after the move. */
    XGrabPointer(dpy, client->window, True,
                 PointerMotionMask | ButtonReleaseMask | ButtonPressMask,
                 GrabModeAsync, GrabModeAsync, None,
                 cursor_moving, CurrentTime);
    XGrabKeyboard(dpy, root_window, True, GrabModeAsync, GrabModeAsync,
                  CurrentTime);
    display_geometry("Moving", client);

    action = CONTINUE;
    while (action == CONTINUE) {
        event_get(ConnectionNumber(dpy), &event1);
        xevent = &event1;
        
        switch (xevent->type) {
            case EnterNotify:
            case LeaveNotify:
                /* we have a separate event loop here because we want
                 * to gobble up all EnterNotify and LeaveNotify events
                 * while moving or resizing the window */
                break;
            case ButtonPress:
                /* if one clicks in window while moving with keyboard,
                 * behave as if moving with mouse */
                if (!have_mouse && mouse_over_client(client)) {
                    have_mouse = 1;
                    x_start = xevent->xbutton.x_root;
                    y_start = xevent->xbutton.y_root;
                    init_button = xevent->xbutton.button;
                }
                break;
                
            case MotionNotify:
                if (!have_mouse) break;
                compress_motion(xevent);
                if (client == NULL) {
                    fprintf(stderr,
                            "XWM: error, null client while moving\n");
                    action = DONE;
                    break;
                }
                /* just move the window */
                client->x += xevent->xbutton.x_root - x_start;
                client->y += xevent->xbutton.y_root - y_start;

                /* take out these two lines for a fun effect :) */
                x_start = xevent->xbutton.x_root;
                y_start = xevent->xbutton.y_root;
                XMoveWindow(dpy, client->frame, client->x, client->y);
                display_geometry("Moving", client);

                break;
                
            case KeyPress:
                if (xevent->xkey.keycode == keycode_Up ||
                    xevent->xkey.keycode == keycode_k ||
                    xevent->xkey.keycode == keycode_w) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta = -(client->y);
                    } else {
                        delta = -1;
                    }
                    client->y += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, 0, delta);
                        y_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Down ||
                           xevent->xkey.keycode == keycode_j ||
                           xevent->xkey.keycode == keycode_s) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta =
                          DisplayHeight(dpy, scr) - client->y - client->height;
                    } else {
                        delta = 1;
                    }
                    client->y += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, 0, delta);
                        y_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Left ||
                           xevent->xkey.keycode == keycode_h ||
                           xevent->xkey.keycode == keycode_a) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta = -(client->x);
                    } else {
                        delta = -1;
                    }
                    client->x += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, delta, 0);
                        x_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Right ||
                           xevent->xkey.keycode == keycode_l ||
                           xevent->xkey.keycode == keycode_d) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta =
                            DisplayWidth(dpy, scr) - client->x - client->width;
                    } else {
                        delta = 1;
                    }
                    client->x += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, delta, 0);
                        x_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Return) {
                    action = DONE;
                    break;
                } else if (xevent->xkey.keycode == keycode_Escape) {
                    client->x = orig_x;
                    client->y = orig_y;
                    action = RESET;
                    break;
                } else if (xevent->xkey.keycode == keycode_Control_L ||
                           xevent->xkey.keycode == keycode_Control_R) {
                    action = RESIZE;
                    break;
                }
                /* disallow other windowmanager commands while moving */
                break;

            case ButtonRelease:
                if (have_mouse && xevent->xbutton.button == init_button) {
                    if (client) {
                        client->x += xevent->xbutton.x_root - x_start;
                        client->y += xevent->xbutton.y_root - y_start;
                    }
                    action = DONE;
                    break;
                }
            
                break;
                
            default:
                debug(("\tStart recursive event processing\n"));
                event_dispatch(xevent);
                debug(("\tEnd recursive event processing\n"));
                break;
        }

    }
        
    if (client != NULL) {
        XMoveWindow(dpy, client->frame, client->x, client->y);
        if (client->name != NULL) Free(client->name);
        titlebar_display = NULL;
        client_set_name(client);
        client_paint_titlebar(client);
        /* must send a synthetic ConfigureNotify to the client
         * according to ICCCM 4.1.5 */
        if (action != RESET) {
            move_inform_client(client);
            client->prev_x = client->prev_y = -1;
            client->prev_width = client->prev_height = -1;
        }
    }

    XUngrabPointer(dpy, CurrentTime);
    XUngrabKeyboard(dpy, CurrentTime);
    moving = 0;
    if (action == RESIZE) {
        /* if we want to toggle from a move to a resize, we create
         * a fake XEvent, filling out the fields that the resize
         * code looks at.  The event isn't sent (no X requests are
         * made - it is just used to call the resize code. */
        if (have_mouse) {
            event1.type = ButtonPress;
            event1.xbutton.window = client->window;
            event1.xbutton.x_root = x_start;
            event1.xbutton.y_root = y_start;
            event1.xbutton.button = init_button;
        } else {
            event1.type = KeyPress;
            event1.xkey.window = client->window;
        }
        resize_client(&event1, v);
    }
}

static Bool mouse_over_client(client_t *client)
{
    Window junk1;
    int junk2, rootx, rooty;
    unsigned int junk3;
                    
    if (XQueryPointer(dpy, root_window, &junk1, &junk1,
                      &rootx, &rooty, &junk2, &junk2,
                      &junk3) == False)
        return False;
    if (rootx >= client->x &&
        rootx <= client->x + client->width &&
        rooty >= client->y &&
        rooty <= client->y + client->height) {

        return True;
    } else {
        return False;
    }
}

/* ICCCM 4.1.5 */
static void move_inform_client(client_t *client)
{
    XConfigureEvent event;
    
    event.type = ConfigureNotify;
    event.display = dpy;
    event.event = client->window;
    event.window = client->window;
    event.x = client->x;
    event.width = client->width;
    if (client->titlebar == None) {
        event.y = client->y;
        event.height = client->height;
    } else {
        event.y = client->y + TITLE_HEIGHT;
        event.height = client->height - TITLE_HEIGHT;
    }
    event.border_width = 0;
    event.above = None;
    event.override_redirect = False;
    XSendEvent(dpy, client->window, False,
               StructureNotifyMask, (XEvent *)&event);
    XFlush(dpy);
}

/* helper for move function, don't let user move window off-screen */
static void move_constrain(client_t *client)
{
    while (client->x + client->width < 0) client->x++;
    while (client->y + client->height < 0) client->y++;
    while (client->x > DisplayWidth(dpy, scr)) client->x--;
    while (client->y > DisplayHeight(dpy, scr)) client->y--;
    XMoveWindow(dpy, client->frame, client->x, client->y);
    display_geometry("Moving", client);
}

/*
 * WARNING:  this gets really, really ugly from here on
 */

void resize_client(XEvent *xevent, void *v)
{
    client_t *client = NULL;
    int x_start, y_start, have_mouse, delta;
    resize_direction_t resize_direction = UNKNOWN;
    resize_direction_t old_resize_direction = UNKNOWN;
    position_size orig;
    XEvent event1;
    unsigned int init_button;
    enum { CONTINUE, RESET, DONE, MOVE } action;

    if (moving || sizing) return;
    if (keycode_Escape == 0) set_keys();
    
    if (xevent->type == ButtonPress) {
        have_mouse = 1;
        client = client_find(xevent->xbutton.window);
        x_start = xevent->xbutton.x_root;
        y_start = xevent->xbutton.y_root;
        init_button = xevent->xbutton.button;
        resize_direction =
            get_direction(client, xevent->xbutton.x_root,
                          xevent->xbutton.y_root);
    } else if (xevent->type == KeyPress) {
        have_mouse = 0;
        client = client_find(xevent->xkey.window);
        if (client != NULL) {
            x_start = client->x + client->width;
            y_start = client->y + client->height;
        }
        resize_direction = SE;
    }
    if (client == NULL) {
        debug(("\tNot resizing a non-client\n"));
        XUngrabPointer(dpy, CurrentTime);
        return;
    }
    
    sizing = 1;
    orig.x = client->x;
    orig.y = client->y;
    orig.width = client->width;
    orig.height = client->height;
    if (have_mouse) {
        XGrabPointer(dpy, client->window, True,
                     PointerMotionMask | ButtonPressMask
                     | ButtonReleaseMask,
                     GrabModeAsync, GrabModeAsync, None,
                     cursor_direction_map[resize_direction],
                     CurrentTime);
    } else {
        /* different cursor */        
        XGrabPointer(dpy, client->window, True,
                     PointerMotionMask | ButtonPressMask
                     | ButtonReleaseMask,
                     GrabModeAsync, GrabModeAsync, None,
                     cursor_sizing, CurrentTime);
    }
    XGrabKeyboard(dpy, root_window, True,
                  GrabModeAsync, GrabModeAsync,
                  CurrentTime);
    /* we take over the titlebar display routines in process_resize() */
    if (client->titlebar != None) {
        free(client->name);
        client->name = strdup("");
        client_paint_titlebar(client);
    }
    /* just draws the initial drafting lines with FIRST argument */
    process_resize(client, x_start, y_start,
                   resize_direction, old_resize_direction,
                   &x_start, &y_start, &orig, FIRST);

    action = CONTINUE;
    while (action == CONTINUE) {
        event_get(ConnectionNumber(dpy), &event1);
        xevent = &event1;
        switch (xevent->type) {
            /* since we take over titlebar painting, also ignore Exposes */
            case EnterNotify:
            case LeaveNotify:
            case Expose:
                break;

            case ButtonPress:
                if (!have_mouse && mouse_over_client(client)) {
                    have_mouse = 1;
                    x_start = xevent->xbutton.x_root;
                    y_start = xevent->xbutton.y_root;
                    init_button = xevent->xbutton.button;
                    resize_direction =
                        get_direction(client, xevent->xbutton.x_root,
                                      xevent->xbutton.y_root);
                    XChangeActivePointerGrab(dpy,
                          PointerMotionMask
                          | ButtonPressMask | ButtonReleaseMask,
                          cursor_direction_map[resize_direction],
                          CurrentTime);
                    /* may have changed resize direction, redraw lines */
                    xrefresh();
                    process_resize(client, x_start, y_start, resize_direction,
                                   old_resize_direction, &x_start, &y_start,
                                   &orig, FIRST);
                }
                break;
                
            case KeyPress:
                if (xevent->xkey.keycode == keycode_Escape) {
                    action = RESET;
                    break;
                } else if (xevent->xkey.keycode == keycode_Return) {
                    if (have_mouse) {
                        /* cleans up drafting lines with LAST arg */
                        process_resize(client, xevent->xkey.x_root,
                                       xevent->xkey.y_root, resize_direction,
                                       old_resize_direction, &x_start,
                                       &y_start, &orig, LAST);
                    } else {
                        process_resize(client, x_start, y_start,
                                       resize_direction, old_resize_direction,
                                       &x_start, &y_start, &orig, LAST);
                    }
                    action = DONE;
                    break;
                } else if (xevent->xkey.keycode == keycode_space) {
                    if (have_mouse) {
                        cycle_resize_direction_mouse(&resize_direction,
                                                     &old_resize_direction);
                        XChangeActivePointerGrab(dpy,
                                    PointerMotionMask
                                    | ButtonPressMask | ButtonReleaseMask,
                                    cursor_direction_map[resize_direction],
                                    CurrentTime);
                    } else {
                        cycle_resize_direction_keyboard(&resize_direction);
                        switch (resize_direction) {
                            case NE:
                                x_start = client->x + client->width;
                                y_start = client->y;
                                break;
                            case NW:
                                x_start = client->x;
                                y_start = client->y;
                                break;
                            case SW:
                                x_start = client->x;
                                y_start = client->y + client->height;
                                break;
                            case SE:
                                x_start = client->x + client->width;
                                y_start = client->y + client->height;
                                break;
                            default: /* shuts up compiler warning */
                        }
                    }
                    xrefresh();
                    process_resize(client, x_start, y_start, resize_direction,
                                   old_resize_direction, &x_start, &y_start,
                                   &orig, FIRST);
                } else if (xevent->xkey.keycode == keycode_Up ||
                           xevent->xkey.keycode == keycode_k ||
                           xevent->xkey.keycode == keycode_w) {
                    if (resize_direction != EAST && resize_direction != WEST) {
                        delta = get_height_resize_inc(client);
                        /* FIXME:  these should resize to extreme */
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         0, -delta);
                        } else {
                            process_resize(client, x_start, y_start - delta,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Down ||
                           xevent->xkey.keycode == keycode_j ||
                           xevent->xkey.keycode == keycode_s) {
                    if (resize_direction != EAST && resize_direction != WEST) {
                        delta = get_height_resize_inc(client);
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         0, delta);
                        } else {
                            process_resize(client, x_start, y_start + delta,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Left ||
                           xevent->xkey.keycode == keycode_h ||
                           xevent->xkey.keycode == keycode_a) {
                    if (resize_direction != NORTH && resize_direction != SOUTH) {
                        delta = get_width_resize_inc(client);
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         -delta, 0);
                        } else {
                            process_resize(client, x_start - delta, y_start,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Right ||
                           xevent->xkey.keycode == keycode_l ||
                           xevent->xkey.keycode == keycode_d) {
                    if (resize_direction != NORTH && resize_direction != SOUTH) {
                        delta = get_width_resize_inc(client);
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         delta, 0);
                        } else {
                            process_resize(client, x_start + delta, y_start,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Control_L ||
                           xevent->xkey.keycode == keycode_Control_R) {
                    process_resize(client, x_start, y_start,
                                   resize_direction, old_resize_direction,
                                   &x_start, &y_start, &orig, LAST);
                    action = MOVE;
                    break;
                }
                /* disallow other window manager commands while resizing */
                break;
                
            case MotionNotify:
                if (!have_mouse) break;
                
                compress_motion(xevent);

                if (client == NULL) {
                    fprintf(stderr, "XWM: error, null client in resize\n");
                    action = RESET;
                    break;
                }
                process_resize(client, xevent->xbutton.x_root,
                               xevent->xbutton.y_root, resize_direction,
                               old_resize_direction, &x_start, &y_start,
                               &orig, MIDDLE);
                break;
            case ButtonRelease:
                if (have_mouse && xevent->xbutton.button == init_button) {
                    if (client == NULL) {
                        fprintf(stderr, "XWM: error, null client in resize\n");
                        action = RESET;
                        break;
                    }
                    process_resize(client, xevent->xmotion.x_root,
                                   xevent->xmotion.y_root, resize_direction,
                                   old_resize_direction, &x_start, &y_start,
                                   &orig, LAST);
                    action = DONE;
                    break;
                }
            
                break;
                
            default:
                debug(("\tStart recursive event processing\n"));
                event_dispatch(xevent);
                debug(("\tEnd recursive event processing\n"));
                break;
        }
    }
    
    if (action == RESET) {
        xrefresh();
        client->x = orig.x;
        client->y = orig.y;
        client->width = orig.width;
        client->height = orig.height;
    } else {
        client->prev_x = client->prev_y = -1;
        client->prev_width = client->prev_height = -1;
    }

    if (client != NULL) {
        XMoveResizeWindow(dpy, client->frame, client->x, client->y,
                          client->width, client->height);
        if (client->titlebar != None) {
            XResizeWindow(dpy, client->window, client->width,
                          client->height - TITLE_HEIGHT);
            XResizeWindow(dpy, client->titlebar, client->width,
                          TITLE_HEIGHT);
        } else {
            XResizeWindow(dpy, client->window, client->width, client->height);
        }
        if (client->name != NULL) Free(client->name);
        titlebar_display = NULL;
        client_set_name(client);
        client_paint_titlebar(client);
    }

    XUngrabPointer(dpy, CurrentTime);
    XUngrabKeyboard(dpy, CurrentTime);
    sizing = 0;
    if (action == MOVE) {
        if (have_mouse) {
            event1.type = ButtonPress;
            event1.xbutton.window = client->window;
            event1.xbutton.x_root = x_start;
            event1.xbutton.y_root = y_start;
            event1.xbutton.button = init_button;
        } else {
            event1.type = KeyPress;
            event1.xkey.window = client->window;
        }
        move_client(&event1, v);
    }
}

static void cycle_resize_direction_keyboard(resize_direction_t *d)
{
    switch (*d) {
        case SE:
            *d = NE;
            break;
        case NE:
            *d = NW;
            break;
        case NW:
            *d = SW;
            break;
        default:
            *d = SE;
    }
}

/*
 * SW -> SOUTH -> WEST -> SW -> ....
 * SE -> SOUTH -> EAST -> SW -> ....
 * NE -> NORTH -> EAST -> NE -> ....
 * NW -> NORTH -> WEST -> NW -> ....
 */

static void cycle_resize_direction_mouse(resize_direction_t *current,
                                         resize_direction_t *old)
{
    resize_direction_t tmp;
    
    switch (*current) {
        case SW:
            *old = SW;
            *current = SOUTH;
            break;
        case NW:
            *old = NW;
            *current = NORTH;
            break;
        case NE:
            *old = NE;
            *current = NORTH;
            break;
        case SE:
            *old = SE;
            *current = SOUTH;
            break;
        case SOUTH:
        case NORTH:
            tmp = *current;
            switch (*old) {
                case NW:
                case SW:
                    *current = WEST;
                    break;
                case NE:
                case SE:
                default:
                    *current = EAST;
            }
            *old = tmp;
            break;
        case EAST:
            if (*old == NORTH)
                *current = NE;
            else
                *current = SE;
            *old = EAST;
            break;
        case WEST:
            if (*old == NORTH)
                *current = NW;
            else
                *current = SW;
            *old = WEST;
            break;
        default:
    }
}

/*
 * works just like the program of the same name; we use this instead
 * of an XClearWindow() as we may have drawn on a number of windows
 * and this seems faster than walking the window tree and generating
 * expose events
 * 
 * FIXME:  test speed compared to XQueryTree()
 */

static void xrefresh()
{
    XSetWindowAttributes xswa;
    Window w;

    xswa.override_redirect = True;
    xswa.backing_store = NotUseful;
    xswa.save_under = False;
    w = XCreateWindow(dpy, root_window, 0, 0,
                      DisplayWidth(dpy, scr), DisplayHeight(dpy, scr),
                      0, DefaultDepth(dpy, scr), InputOutput,
                      DefaultVisual(dpy, scr),
                      CWOverrideRedirect | CWBackingStore | CWSaveUnder,
                      &xswa);<commit_msg>now uses global scr_width instead of DisplayWidth<commit_after>/* $Id$
 * This was written by <NAME> in 2001.
 * This code is in the public domain and the author disclaims all
 * copyright privileges.
 */

/*
 * this is probably the most complex part of the window manager
 * because I'm unhappy with all the implementations of this that I've
 * seen.
 */

#include <stdio.h>
#include <stdlib.h>
#include <X11/keysym.h>

#include "move-resize.h"
#include "client.h"
#include "cursor.h"
#include "event.h"
#include "xwm.h"
#include "malloc.h"
#include "debug.h"

#ifndef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif /* MIN */

#ifndef MAX
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#endif /* MAX */

#ifndef ABS
#define ABS(x) ((x) < 0 ? (-(x)) : (x))
#endif /* ABS */

/*
 * These are the possible directions in which one can resize a window,
 * depending on which direction the mouse moves at first
 * 
 * UNKNOWN is obviously a marker to say the direction hasn't yet been
 * identified
 * 
 * We only constrain the window resizing to one dimension by using a
 * keypress; window movement is never constrained to one direction.
 */

typedef enum {
    NW = 0, NE, SE, SW, NORTH, SOUTH, EAST, WEST, UNKNOWN
} resize_direction_t;

typedef enum {
    FIRST, MIDDLE, LAST
} resize_ordinal_t;

int moving = 0;
int sizing = 0;

static int keycode_Escape = 0;
static int keycode_Return, keycode_Up, keycode_Down, keycode_Left;
static int keycode_Right, keycode_j, keycode_k, keycode_l, keycode_h;
static int keycode_w, keycode_a, keycode_s, keycode_d;
static int keycode_Control_L, keycode_Control_R, keycode_space;

/* optimization, see display_geometry() */
static char *titlebar_display = NULL;

static void compress_motion(XEvent *xevent);
static void process_resize(client_t *client, int new_x, int new_y,
                           resize_direction_t direction,
                           resize_direction_t old_direction,
                           int *old_x, int *old_y,
                           position_size *orig,
                           resize_ordinal_t ordinal);
static resize_direction_t get_direction(client_t *client, int x, int y);
static void display_geometry(char *s, client_t *client);
static void drafting_lines(client_t *client, resize_direction_t direction,
                           int x1, int y1, int x2, int y2);
static void draw_arrowhead(int x, int y, resize_direction_t direction);
static void xrefresh();
static void cycle_resize_direction_mouse(resize_direction_t *current,
                                         resize_direction_t *old);
static void cycle_resize_direction_keyboard(resize_direction_t *current);
static void set_keys();
static void move_constrain(client_t *client);
static int get_width_resize_inc(client_t *client);
static int get_height_resize_inc(client_t *client);
static void move_inform_client(client_t *client);
static Bool mouse_over_client(client_t *client);

void resize_maximize(XEvent *xevent, void *v)
{
    int h_inc, w_inc;
    client_t *client;

    client = client_find(xevent->xbutton.window);
    if (client == NULL) return;
    h_inc = get_height_resize_inc(client);
    w_inc = get_width_resize_inc(client);

    if (client->prev_width != -1 || client->prev_height != -1) {
        if (client->prev_height != -1) {
            client->height = client->prev_height;
            client->y = client->prev_y;
        }
        if (client->prev_width != -1) {
            client->width = client->prev_width;
            client->x = client->prev_x;
        }
        client->prev_x = client->prev_y = -1;
        client->prev_width = client->prev_height = -1;
    } else {
        client->prev_x = client->x;
        client->prev_y = client->y;
        client->prev_width = client->width;
        client->prev_height = client->height;
        client->width = (scr_width / w_inc) * w_inc;
        client->height = (scr_height / h_inc) * h_inc;
        client->y = client->x = 0;
    }
    XMoveResizeWindow(dpy, client->frame, client->x, client->y,
                      client->width, client->height);
    if (client->titlebar != None) {
        XResizeWindow(dpy, client->window, client->width,
                      client->height - TITLE_HEIGHT);
        XResizeWindow(dpy, client->titlebar, client->width, TITLE_HEIGHT);
    } else {
        XResizeWindow(dpy, client->window, client->width, client->height);
    }
}

/*
 * This takes over the event loop and grabs the pointer until the move
 * is complete; we keep most of the data 'static' as this may get
 * called recursively and all simultaneously-running invocations need
 * to keep some of the same state (the resize code is much worse).
 */
void move_client(XEvent *xevent, void *v)
{
    client_t *client = NULL;
    int x_start, y_start;       /* only used for mouse move */
    int orig_x, orig_y, delta;
    int have_mouse;
    unsigned int init_button;
    XEvent event1;
    enum { CONTINUE, DONE, RESET, RESIZE } action;

    if (moving || sizing) return;
    if (keycode_Escape == 0) set_keys();
    if (xevent->type == ButtonPress) {
        client = client_find(xevent->xbutton.window);
        x_start = xevent->xbutton.x_root;
        y_start = xevent->xbutton.y_root;
        init_button = xevent->xbutton.button;
        have_mouse = 1;
    } else if (xevent->type == KeyPress) {
        client = client_find(xevent->xkey.window);
        have_mouse = 0;
    } else {
        fprintf(stderr, "XWM: Error, move_client called incorrectly\n");
        return;
    }
    if (client == NULL) {
        fprintf(stderr, "XWM: Not moving a non-client\n");
        XUngrabPointer(dpy, CurrentTime);
        return;
    }
    
    moving = 1;
    orig_x = client->x;
    orig_y = client->y;
    
    /* I kind of like being able to move a window
     * without giving it focus, and this doesn't
     * cause any problems: */
    /* focus_set(client); */
    /* focus_ensure(); */

    /* here we grab the pointer for the client window; it also works
     * OK if we grab the root window or our frame, but it introduces a
     * problem:  the client receives some stray LeaveNotify, FocusOut,
     * EnterNotify and FocusIn events; in particular, xterm doesn't
     * know how to deal with them and it will never correctly deal
     * with a FocusOut event after the move. */
    XGrabPointer(dpy, client->window, True,
                 PointerMotionMask | ButtonReleaseMask | ButtonPressMask,
                 GrabModeAsync, GrabModeAsync, None,
                 cursor_moving, CurrentTime);
    XGrabKeyboard(dpy, root_window, True, GrabModeAsync, GrabModeAsync,
                  CurrentTime);
    display_geometry("Moving", client);

    action = CONTINUE;
    while (action == CONTINUE) {
        event_get(ConnectionNumber(dpy), &event1);
        xevent = &event1;
        
        switch (xevent->type) {
            case EnterNotify:
            case LeaveNotify:
                /* we have a separate event loop here because we want
                 * to gobble up all EnterNotify and LeaveNotify events
                 * while moving or resizing the window */
                break;
            case ButtonPress:
                /* if one clicks in window while moving with keyboard,
                 * behave as if moving with mouse */
                if (!have_mouse && mouse_over_client(client)) {
                    have_mouse = 1;
                    x_start = xevent->xbutton.x_root;
                    y_start = xevent->xbutton.y_root;
                    init_button = xevent->xbutton.button;
                }
                break;
                
            case MotionNotify:
                if (!have_mouse) break;
                compress_motion(xevent);
                if (client == NULL) {
                    fprintf(stderr,
                            "XWM: error, null client while moving\n");
                    action = DONE;
                    break;
                }
                /* just move the window */
                client->x += xevent->xbutton.x_root - x_start;
                client->y += xevent->xbutton.y_root - y_start;

                /* take out these two lines for a fun effect :) */
                x_start = xevent->xbutton.x_root;
                y_start = xevent->xbutton.y_root;
                XMoveWindow(dpy, client->frame, client->x, client->y);
                display_geometry("Moving", client);

                break;
                
            case KeyPress:
                if (xevent->xkey.keycode == keycode_Up ||
                    xevent->xkey.keycode == keycode_k ||
                    xevent->xkey.keycode == keycode_w) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta = -(client->y);
                    } else {
                        delta = -1;
                    }
                    client->y += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, 0, delta);
                        y_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Down ||
                           xevent->xkey.keycode == keycode_j ||
                           xevent->xkey.keycode == keycode_s) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta =
                          scr_height - client->y - client->height;
                    } else {
                        delta = 1;
                    }
                    client->y += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, 0, delta);
                        y_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Left ||
                           xevent->xkey.keycode == keycode_h ||
                           xevent->xkey.keycode == keycode_a) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta = -(client->x);
                    } else {
                        delta = -1;
                    }
                    client->x += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, delta, 0);
                        x_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Right ||
                           xevent->xkey.keycode == keycode_l ||
                           xevent->xkey.keycode == keycode_d) {
                    if (xevent->xkey.state & ShiftMask) {
                        delta =
                            scr_width - client->x - client->width;
                    } else {
                        delta = 1;
                    }
                    client->x += delta;
                    move_constrain(client);
                    if (have_mouse) {
                        XWarpPointer(dpy, None, None, 0, 0, 0, 0, delta, 0);
                        x_start += delta;
                    }
                } else if (xevent->xkey.keycode == keycode_Return) {
                    action = DONE;
                    break;
                } else if (xevent->xkey.keycode == keycode_Escape) {
                    client->x = orig_x;
                    client->y = orig_y;
                    action = RESET;
                    break;
                } else if (xevent->xkey.keycode == keycode_Control_L ||
                           xevent->xkey.keycode == keycode_Control_R) {
                    action = RESIZE;
                    break;
                }
                /* disallow other windowmanager commands while moving */
                break;

            case ButtonRelease:
                if (have_mouse && xevent->xbutton.button == init_button) {
                    if (client) {
                        client->x += xevent->xbutton.x_root - x_start;
                        client->y += xevent->xbutton.y_root - y_start;
                    }
                    action = DONE;
                    break;
                }
            
                break;
                
            default:
                debug(("\tStart recursive event processing\n"));
                event_dispatch(xevent);
                debug(("\tEnd recursive event processing\n"));
                break;
        }

    }
        
    if (client != NULL) {
        XMoveWindow(dpy, client->frame, client->x, client->y);
        if (client->name != NULL) Free(client->name);
        titlebar_display = NULL;
        client_set_name(client);
        client_paint_titlebar(client);
        /* must send a synthetic ConfigureNotify to the client
         * according to ICCCM 4.1.5 */
        if (action != RESET) {
            move_inform_client(client);
            client->prev_x = client->prev_y = -1;
            client->prev_width = client->prev_height = -1;
        }
    }

    XUngrabPointer(dpy, CurrentTime);
    XUngrabKeyboard(dpy, CurrentTime);
    moving = 0;
    if (action == RESIZE) {
        /* if we want to toggle from a move to a resize, we create
         * a fake XEvent, filling out the fields that the resize
         * code looks at.  The event isn't sent (no X requests are
         * made - it is just used to call the resize code. */
        if (have_mouse) {
            event1.type = ButtonPress;
            event1.xbutton.window = client->window;
            event1.xbutton.x_root = x_start;
            event1.xbutton.y_root = y_start;
            event1.xbutton.button = init_button;
        } else {
            event1.type = KeyPress;
            event1.xkey.window = client->window;
        }
        resize_client(&event1, v);
    }
}

static Bool mouse_over_client(client_t *client)
{
    Window junk1;
    int junk2, rootx, rooty;
    unsigned int junk3;
                    
    if (XQueryPointer(dpy, root_window, &junk1, &junk1,
                      &rootx, &rooty, &junk2, &junk2,
                      &junk3) == False)
        return False;
    if (rootx >= client->x &&
        rootx <= client->x + client->width &&
        rooty >= client->y &&
        rooty <= client->y + client->height) {

        return True;
    } else {
        return False;
    }
}

/* ICCCM 4.1.5 */
static void move_inform_client(client_t *client)
{
    XConfigureEvent event;
    
    event.type = ConfigureNotify;
    event.display = dpy;
    event.event = client->window;
    event.window = client->window;
    event.x = client->x;
    event.width = client->width;
    if (client->titlebar == None) {
        event.y = client->y;
        event.height = client->height;
    } else {
        event.y = client->y + TITLE_HEIGHT;
        event.height = client->height - TITLE_HEIGHT;
    }
    event.border_width = 0;
    event.above = None;
    event.override_redirect = False;
    XSendEvent(dpy, client->window, False,
               StructureNotifyMask, (XEvent *)&event);
    XFlush(dpy);
}

/* helper for move function, don't let user move window off-screen */
static void move_constrain(client_t *client)
{
    while (client->x + client->width < 0) client->x++;
    while (client->y + client->height < 0) client->y++;
    while (client->x > scr_width) client->x--;
    while (client->y > scr_height) client->y--;
    XMoveWindow(dpy, client->frame, client->x, client->y);
    display_geometry("Moving", client);
}

/*
 * WARNING:  this gets really, really ugly from here on
 */

void resize_client(XEvent *xevent, void *v)
{
    client_t *client = NULL;
    int x_start, y_start, have_mouse, delta;
    resize_direction_t resize_direction = UNKNOWN;
    resize_direction_t old_resize_direction = UNKNOWN;
    position_size orig;
    XEvent event1;
    unsigned int init_button;
    enum { CONTINUE, RESET, DONE, MOVE } action;

    if (moving || sizing) return;
    if (keycode_Escape == 0) set_keys();
    
    if (xevent->type == ButtonPress) {
        have_mouse = 1;
        client = client_find(xevent->xbutton.window);
        x_start = xevent->xbutton.x_root;
        y_start = xevent->xbutton.y_root;
        init_button = xevent->xbutton.button;
        resize_direction =
            get_direction(client, xevent->xbutton.x_root,
                          xevent->xbutton.y_root);
    } else if (xevent->type == KeyPress) {
        have_mouse = 0;
        client = client_find(xevent->xkey.window);
        if (client != NULL) {
            x_start = client->x + client->width;
            y_start = client->y + client->height;
        }
        resize_direction = SE;
    }
    if (client == NULL) {
        debug(("\tNot resizing a non-client\n"));
        XUngrabPointer(dpy, CurrentTime);
        return;
    }
    
    sizing = 1;
    orig.x = client->x;
    orig.y = client->y;
    orig.width = client->width;
    orig.height = client->height;
    if (have_mouse) {
        XGrabPointer(dpy, client->window, True,
                     PointerMotionMask | ButtonPressMask
                     | ButtonReleaseMask,
                     GrabModeAsync, GrabModeAsync, None,
                     cursor_direction_map[resize_direction],
                     CurrentTime);
    } else {
        /* different cursor */        
        XGrabPointer(dpy, client->window, True,
                     PointerMotionMask | ButtonPressMask
                     | ButtonReleaseMask,
                     GrabModeAsync, GrabModeAsync, None,
                     cursor_sizing, CurrentTime);
    }
    XGrabKeyboard(dpy, root_window, True,
                  GrabModeAsync, GrabModeAsync,
                  CurrentTime);
    /* we take over the titlebar display routines in process_resize() */
    if (client->titlebar != None) {
        free(client->name);
        client->name = strdup("");
        client_paint_titlebar(client);
    }
    /* just draws the initial drafting lines with FIRST argument */
    process_resize(client, x_start, y_start,
                   resize_direction, old_resize_direction,
                   &x_start, &y_start, &orig, FIRST);

    action = CONTINUE;
    while (action == CONTINUE) {
        event_get(ConnectionNumber(dpy), &event1);
        xevent = &event1;
        switch (xevent->type) {
            /* since we take over titlebar painting, also ignore Exposes */
            case EnterNotify:
            case LeaveNotify:
            case Expose:
                break;

            case ButtonPress:
                if (!have_mouse && mouse_over_client(client)) {
                    have_mouse = 1;
                    x_start = xevent->xbutton.x_root;
                    y_start = xevent->xbutton.y_root;
                    init_button = xevent->xbutton.button;
                    resize_direction =
                        get_direction(client, xevent->xbutton.x_root,
                                      xevent->xbutton.y_root);
                    XChangeActivePointerGrab(dpy,
                          PointerMotionMask
                          | ButtonPressMask | ButtonReleaseMask,
                          cursor_direction_map[resize_direction],
                          CurrentTime);
                    /* may have changed resize direction, redraw lines */
                    xrefresh();
                    process_resize(client, x_start, y_start, resize_direction,
                                   old_resize_direction, &x_start, &y_start,
                                   &orig, FIRST);
                }
                break;
                
            case KeyPress:
                if (xevent->xkey.keycode == keycode_Escape) {
                    action = RESET;
                    break;
                } else if (xevent->xkey.keycode == keycode_Return) {
                    if (have_mouse) {
                        /* cleans up drafting lines with LAST arg */
                        process_resize(client, xevent->xkey.x_root,
                                       xevent->xkey.y_root, resize_direction,
                                       old_resize_direction, &x_start,
                                       &y_start, &orig, LAST);
                    } else {
                        process_resize(client, x_start, y_start,
                                       resize_direction, old_resize_direction,
                                       &x_start, &y_start, &orig, LAST);
                    }
                    action = DONE;
                    break;
                } else if (xevent->xkey.keycode == keycode_space) {
                    if (have_mouse) {
                        cycle_resize_direction_mouse(&resize_direction,
                                                     &old_resize_direction);
                        XChangeActivePointerGrab(dpy,
                                    PointerMotionMask
                                    | ButtonPressMask | ButtonReleaseMask,
                                    cursor_direction_map[resize_direction],
                                    CurrentTime);
                    } else {
                        cycle_resize_direction_keyboard(&resize_direction);
                        switch (resize_direction) {
                            case NE:
                                x_start = client->x + client->width;
                                y_start = client->y;
                                break;
                            case NW:
                                x_start = client->x;
                                y_start = client->y;
                                break;
                            case SW:
                                x_start = client->x;
                                y_start = client->y + client->height;
                                break;
                            case SE:
                                x_start = client->x + client->width;
                                y_start = client->y + client->height;
                                break;
                            default: /* shuts up compiler warning */
                        }
                    }
                    xrefresh();
                    process_resize(client, x_start, y_start, resize_direction,
                                   old_resize_direction, &x_start, &y_start,
                                   &orig, FIRST);
                } else if (xevent->xkey.keycode == keycode_Up ||
                           xevent->xkey.keycode == keycode_k ||
                           xevent->xkey.keycode == keycode_w) {
                    if (resize_direction != EAST && resize_direction != WEST) {
                        delta = get_height_resize_inc(client);
                        /* FIXME:  these should resize to extreme */
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         0, -delta);
                        } else {
                            process_resize(client, x_start, y_start - delta,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Down ||
                           xevent->xkey.keycode == keycode_j ||
                           xevent->xkey.keycode == keycode_s) {
                    if (resize_direction != EAST && resize_direction != WEST) {
                        delta = get_height_resize_inc(client);
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         0, delta);
                        } else {
                            process_resize(client, x_start, y_start + delta,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Left ||
                           xevent->xkey.keycode == keycode_h ||
                           xevent->xkey.keycode == keycode_a) {
                    if (resize_direction != NORTH && resize_direction != SOUTH) {
                        delta = get_width_resize_inc(client);
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         -delta, 0);
                        } else {
                            process_resize(client, x_start - delta, y_start,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Right ||
                           xevent->xkey.keycode == keycode_l ||
                           xevent->xkey.keycode == keycode_d) {
                    if (resize_direction != NORTH && resize_direction != SOUTH) {
                        delta = get_width_resize_inc(client);
                        if (xevent->xkey.state & ShiftMask) delta *= 10;
                        if (have_mouse) {
                            XWarpPointer(dpy, None, None, 0, 0, 0, 0,
                                         delta, 0);
                        } else {
                            process_resize(client, x_start + delta, y_start,
                                           resize_direction,
                                           old_resize_direction,
                                           &x_start, &y_start, &orig, MIDDLE);
                        }
                    }
                } else if (xevent->xkey.keycode == keycode_Control_L ||
                           xevent->xkey.keycode == keycode_Control_R) {
                    process_resize(client, x_start, y_start,
                                   resize_direction, old_resize_direction,
                                   &x_start, &y_start, &orig, LAST);
                    action = MOVE;
                    break;
                }
                /* disallow other window manager commands while resizing */
                break;
                
            case MotionNotify:
                if (!have_mouse) break;
                
                compress_motion(xevent);

                if (client == NULL) {
                    fprintf(stderr, "XWM: error, null client in resize\n");
                    action = RESET;
                    break;
                }
                process_resize(client, xevent->xbutton.x_root,
                               xevent->xbutton.y_root, resize_direction,
                               old_resize_direction, &x_start, &y_start,
                               &orig, MIDDLE);
                break;
            case ButtonRelease:
                if (have_mouse && xevent->xbutton.button == init_button) {
                    if (client == NULL) {
                        fprintf(stderr, "XWM: error, null client in resize\n");
                        action = RESET;
                        break;
                    }
                    process_resize(client, xevent->xmotion.x_root,
                                   xevent->xmotion.y_root, resize_direction,
                                   old_resize_direction, &x_start, &y_start,
                                   &orig, LAST);
                    action = DONE;
                    break;
                }
            
                break;
                
            default:
                debug(("\tStart recursive event processing\n"));
                event_dispatch(xevent);
                debug(("\tEnd recursive event processing\n"));
                break;
        }
    }
    
    if (action == RESET) {
        xrefresh();
        client->x = orig.x;
        client->y = orig.y;
        client->width = orig.width;
        client->height = orig.height;
    } else {
        client->prev_x = client->prev_y = -1;
        client->prev_width = client->prev_height = -1;
    }

    if (client != NULL) {
        XMoveResizeWindow(dpy, client->frame, client->x, client->y,
                          client->width, client->height);
        if (client->titlebar != None) {
            XResizeWindow(dpy, client->window, client->width,
                          client->height - TITLE_HEIGHT);
            XResizeWindow(dpy, client->titlebar, client->width,
                          TITLE_HEIGHT);
        } else {
            XResizeWindow(dpy, client->window, client->width, client->height);
        }
        if (client->name != NULL) Free(client->name);
        titlebar_display = NULL;
        client_set_name(client);
        client_paint_titlebar(client);
    }

    XUngrabPointer(dpy, CurrentTime);
    XUngrabKeyboard(dpy, CurrentTime);
    sizing = 0;
    if (action == MOVE) {
        if (have_mouse) {
            event1.type = ButtonPress;
            event1.xbutton.window = client->window;
            event1.xbutton.x_root = x_start;
            event1.xbutton.y_root = y_start;
            event1.xbutton.button = init_button;
        } else {
            event1.type = KeyPress;
            event1.xkey.window = client->window;
        }
        move_client(&event1, v);
    }
}

static void cycle_resize_direction_keyboard(resize_direction_t *d)
{
    switch (*d) {
        case SE:
            *d = NE;
            break;
        case NE:
            *d = NW;
            break;
        case NW:
            *d = SW;
            break;
        default:
            *d = SE;
    }
}

/*
 * SW -> SOUTH -> WEST -> SW -> ....
 * SE -> SOUTH -> EAST -> SW -> ....
 * NE -> NORTH -> EAST -> NE -> ....
 * NW -> NORTH -> WEST -> NW -> ....
 */

static void cycle_resize_direction_mouse(resize_direction_t *current,
                                         resize_direction_t *old)
{
    resize_direction_t tmp;
    
    switch (*current) {
        case SW:
            *old = SW;
            *current = SOUTH;
            break;
        case NW:
            *old = NW;
            *current = NORTH;
            break;
        case NE:
            *old = NE;
            *current = NORTH;
            break;
        case SE:
            *old = SE;
            *current = SOUTH;
            break;
        case SOUTH:
        case NORTH:
            tmp = *current;
            switch (*old) {
                case NW:
                case SW:
                    *current = WEST;
                    break;
                case NE:
                case SE:
                default:
                    *current = EAST;
            }
            *old = tmp;
            break;
        case EAST:
            if (*old == NORTH)
                *current = NE;
            else
                *current = SE;
            *old = EAST;
            break;
        case WEST:
            if (*old == NORTH)
                *current = NW;
            else
                *current = SW;
            *old = WEST;
            break;
        default:
    }
}

/*
 * works just like the program of the same name; we use this instead
 * of an XClearWindow() as we may have drawn on a number of windows
 * and this seems faster than walking the window tree and generating
 * expose events
 * 
 * FIXME:  test speed compared to XQueryTree()
 */

static void xrefresh()
{
    XSetWindowAttributes xswa;
    Window w;

    xswa.override_redirect = True;
    xswa.backing_store = NotUseful;
    xswa.save_under = False;
    w = XCreateWindow(dpy, root_window, 0, 0,
                      scr_width, scr_height,
                      0, DefaultDepth(dpy, scr), InputOutput,
                      DefaultVisual(dpy, scr),
                      CWOverrideRedirect | CWBackingStore | CWSaveUnder,
                      &xswa);
{'fraction_non_alphanumeric': 0.09638108166476414, 'fraction_numerical': 0.004072440070342146, 'mean_word_length': 1.2980963522280122, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 527, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 116, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4365276', 'n_tokens_mistral': 18068, 'n_tokens_neox': 17001, 'n_words': 4187}