shod

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit a0c6a2647a9a8551dde3868c32c0070d88b366c9
Author: phillbush <phillbush@cock.li>
Date:   Mon, 13 Sep 2021 07:56:05 -0300

initial commit

Diffstat:
AMakefile | 46++++++++++++++++++++++++++++++++++++++++++++++
AREADME | 21+++++++++++++++++++++
Ashod.c | 4055+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atheme.xpm | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 4297 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,46 @@ +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man +LOCALINC = /usr/local/include +LOCALLIB = /usr/local/lib +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# includes and libs +INCS = -I${LOCALINC} -I${X11INC} +LIBS = -L${LOCALLIB} -L${X11LIB} -lX11 -lXinerama -lXpm + +# flags +CFLAGS = -g -O0 -Wall -Wextra ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc + +# files +PROGS = shod +SRCS = ${PROGS}.c +OBJS = ${SRCS:.c=.o} + +all: shod + +shod: shod.o + ${CC} -o $@ shod.o ${LDFLAGS} + +shod.o: theme.xpm + +.c.o: + ${CC} ${CFLAGS} -c $< + +install: all + install -D -m 755 shod ${DESTDIR}${PREFIX}/bin/shod + #install -D -m 644 shod.1 ${DESTDIR}${MANPREFIX}/man1/shod.1 + +uninstall: + rm -f ${DESTDIR}/${PREFIX}/bin/shod + #rm -f ${DESTDIR}/${MANPREFIX}/man1/shod.1 + +clean: + -rm ${OBJS} ${PROGS} + +.PHONY: all install uninstall clean diff --git a/README b/README @@ -0,0 +1,21 @@ +┌──────────────┬──────────────┬─────────────────────────────┐ +├──────────────┴──────────────┼─────────────────────────────┤ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ ├─────────┬─────────┬─────────┤ +│ ├─────────┴─────────┴─────────┤ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +└─────────────────────────────┴─────────────────────────────┘ diff --git a/shod.c b/shod.c @@ -0,0 +1,4055 @@ +#include <err.h> +#include <limits.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xproto.h> +#include <X11/Xresource.h> +#include <X11/Xutil.h> +#include <X11/cursorfont.h> +#include <X11/xpm.h> +#include <X11/extensions/Xinerama.h> +#include "theme.xpm" + +#define WMNAME "shod2" +#define DIV 15 /* number to divide the screen into grids */ +#define FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso8859-1" +#define MODIFIER Mod1Mask +#define FOCUS_BUTTONS 1 +#define RAISE_BUTTONS 1 +#define NDESKTOPS 10 +#define NOTIFGRAVITY "NE" +#define NOTIFGAP 3 +#define IGNOREUNMAP 6 /* number of unmap notifies to ignore while scanning existing clients */ +#define NAMEMAXLEN 1024 /* maximum length of window's name */ + +/* title bar buttons */ +enum { + BUTTON_NONE, + BUTTON_LEFT, + BUTTON_RIGHT +}; + +/* state flag */ +enum { + REMOVE = 0, + ADD = 1, + TOGGLE = 2 +}; + +/* decoration style */ +enum { + FOCUSED, + UNFOCUSED, + URGENT, + STYLE_LAST +}; + +/* decoration state */ +enum { + /* the first decoration state is used both for focused tab and for unpressed borders */ + UNPRESSED = 0, + TAB_FOCUSED = 0, + + PRESSED = 1, + TAB_PRESSED = 1, + + /* the third decoration state is used for unfocused tab, transient borders, and merged borders */ + TAB_UNFOCUSED = 2, + DIALOG = 2, + NOTIFICATION = 2, + DIVISION = 2, + + DECOR_LAST = 3 +}; + +/* cursor types */ +enum { + CURSOR_NORMAL, + CURSOR_MOVE, + CURSOR_NW, + CURSOR_NE, + CURSOR_SW, + CURSOR_SE, + CURSOR_N, + CURSOR_S, + CURSOR_W, + CURSOR_E, + CURSOR_PIRATE, + CURSOR_LAST +}; + +/* window layers */ +enum { + LAYER_DESKTOP, + LAYER_BELOW, + LAYER_NORMAL, + LAYER_ABOVE, + LAYER_FULLSCREEN, + LAYER_LAST +}; + +/* notification spawning direction */ +enum { + DOWNWARDS, + UPWARDS +}; + +/* atoms */ +enum { + /* utf8 */ + UTF8_STRING, + + /* ICCCM atoms */ + WM_DELETE_WINDOW, + WM_WINDOW_ROLE, + WM_TAKE_FOCUS, + WM_PROTOCOLS, + WM_STATE, + + /* EWMH atoms */ + _NET_SUPPORTED, + _NET_CLIENT_LIST, + _NET_CLIENT_LIST_STACKING, + _NET_NUMBER_OF_DESKTOPS, + _NET_CURRENT_DESKTOP, + _NET_ACTIVE_WINDOW, + _NET_WM_DESKTOP, + _NET_SUPPORTING_WM_CHECK, + _NET_SHOWING_DESKTOP, + _NET_CLOSE_WINDOW, + _NET_MOVERESIZE_WINDOW, + _NET_WM_MOVERESIZE, + _NET_WM_NAME, + _NET_WM_WINDOW_TYPE, + _NET_WM_WINDOW_TYPE_DESKTOP, + _NET_WM_WINDOW_TYPE_MENU, + _NET_WM_WINDOW_TYPE_TOOLBAR, + _NET_WM_WINDOW_TYPE_DOCK, + _NET_WM_WINDOW_TYPE_DIALOG, + _NET_WM_WINDOW_TYPE_UTILITY, + _NET_WM_WINDOW_TYPE_SPLASH, + _NET_WM_WINDOW_TYPE_PROMPT, + _NET_WM_WINDOW_TYPE_NOTIFICATION, + _NET_WM_STATE, + _NET_WM_STATE_STICKY, + _NET_WM_STATE_MAXIMIZED_VERT, + _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_HIDDEN, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE_ABOVE, + _NET_WM_STATE_BELOW, + _NET_WM_STATE_FOCUSED, + _NET_WM_STATE_DEMANDS_ATTENTION, + + /* shod atoms */ + _SHOD_CONTAINER_FOCUS, + _SHOD_WINDOW_FOCUS, + _SHOD_RETILE, + _SHOD_CONTAINER_GEOMETRY, + _SHOD_CONTAINER_LIST, + _SHOD_CONTAINER, + _SHOD_ATTACH, + _SHOD_DETACH, + _SHOD_CURRENT_MONITOR, + _SHOD_MONITOR, + + ATOM_LAST +}; + +/* window eight sections (aka octants) */ +enum Octant { + C = 0, + N = (1 << 0), + S = (1 << 1), + W = (1 << 2), + E = (1 << 3), + NW = (1 << 0) | (1 << 2), + NE = (1 << 0) | (1 << 3), + SW = (1 << 1) | (1 << 2), + SE = (1 << 1) | (1 << 3), +}; + +/* struct returned by getwindow */ +struct Winres { + struct Notification *n; /* notification of window */ + struct Container *c; /* container of window */ + struct Row *row; /* row (with buttons) of window */ + struct Tab *t; /* tab of window */ + struct Dialog *d; /* dialog of window */ +}; + +/* dialog window structure */ +struct Dialog { + struct Dialog *prev, *next; /* pointer for list of dialogs */ + struct Tab *t; /* pointer to parent tab */ + Window frame; /* window to reparent the client window */ + Window win; /* actual client window */ + Pixmap pix; /* pixmap to draw the frame */ + int x, y, w, h; /* geometry of the dialog inside the tab frame */ + int maxw, maxh; /* maximum size of the dialog */ + int pw, ph; /* pixmap size */ + int ignoreunmap; /* number of unmapnotifys to ignore */ +}; + +/* tab structure */ +struct Tab { + struct Tab *prev, *next; /* pointer for list of tabs */ + struct Row *row; /* pointer to parent row */ + struct Dialog *ds; /* pointer to list of dialogs */ + Window title; /* title bar */ + Window win; /* actual client window */ + Window frame; /* window to reparent the client window */ + Pixmap pix; /* pixmap to draw the background of the frame */ + Pixmap pixtitle; /* pixmap to draw the background of the title window */ + char *name; /* client name */ + char *class; /* client class */ + int ignoreunmap; /* number of unmapnotifys to ignore */ + int isurgent; /* whether tab is urgent */ + int winw, winh; /* window geometry */ + int x, w; /* tab geometry */ + int ptw; /* pixmap width for the title window */ + int pw, ph; /* pixmap size of the frame */ +}; + +/* row of tiled container */ +struct Row { + struct Row *prev, *next; /* pointer for list of rows */ + struct Column *col; /* pointer to parent column */ + struct Tab *tabs; /* list of tabs */ + struct Tab *seltab; /* pointer to selected tab */ + Window bl; /* left button */ + Window br; /* right button */ + Pixmap pixbl; /* pixmap for left button */ + Pixmap pixbr; /* pixmap for right button */ + int ntabs; /* number of tabs */ + int y, h; /* row geometry */ +}; + +/* column of tiled container */ +struct Column { + struct Column *prev, *next; /* pointers for list of columns */ + struct Container *c; /* pointer to parent container */ + struct Row *rows; /* list of rows */ + struct Row *selrow; /* pointer to selected row */ + int nrows; /* number of rows */ + int x, w; /* column geometry */ +}; + +/* container structure */ +struct Container { + struct Container *prev, *next; /* pointers for list of containers */ + struct Container *fprev, *fnext; /* pointers for list of containers in decrescent focus order */ + struct Container *rprev, *rnext; /* pointers for list of containers from raised to lowered */ + struct Monitor *mon; /* monitor container is on */ + struct Desktop *desk; /* desktop container is on */ + struct Column *cols; /* list of columns in container */ + struct Column *selcol; /* pointer to selected container */ + Window curswin; /* dummy window used for change cursor while hovering borders */ + Window frame; /* window to reparent the contents of the container */ + Pixmap pix; /* pixmap to draw the frame */ + int ncols; /* number of columns */ + int ismaximized, isminimized, issticky; /* window states */ + int isfullscreen; /* whether container is fullscreen */ + int ishidden; /* whether container is hidden */ + int layer; /* stacking order */ + int x, y, w, h, b; /* current geometry */ + int pw, ph; /* pixmap width and height */ + int nx, ny, nw, nh; /* non-maximized geometry */ + int mx, my, mw, mh; /* maximized geometry */ +}; + +/* desktop of a monitor */ +struct Desktop { + struct Monitor *mon; /* monitor the desktop is in */ + int n; /* desktop number */ +}; + +/* data of a monitor */ +struct Monitor { + struct Monitor *prev, *next; /* pointers for list of monitors */ + struct Desktop *desks; /* array of desktops */ + struct Desktop *seldesk; /* pointer to focused desktop on that monitor */ + int mx, my, mw, mh; /* monitor size */ + int wx, wy, ww, wh; /* window area */ + int n; /* monitor number */ +}; + +/* notification structure */ +struct Notification { + struct Notification *prev, *next; /* pointers for list of notifications */ + Window frame; /* window to reparent the actual client window */ + Window win; /* actual client window */ + Pixmap pix; /* pixmap to draw the frame */ + int w, h; /* geometry of the entire thing (content + decoration) */ + int pw, ph; /* pixmap width and height */ +}; + +/* prompt structure, used only when calling promptisvalid() */ +struct Prompt { + Window win, frame; /* actual client window and its frame */ +}; + +/* decoration pixmaps and colors */ +struct Decor { + Pixmap bl; /* left button */ + Pixmap br; /* right button */ + Pixmap tl; /* title left end */ + Pixmap t; /* title middle */ + Pixmap tr; /* title right end */ + Pixmap nw; /* northwest corner */ + Pixmap nf; /* north first edge */ + Pixmap n; /* north border */ + Pixmap nl; /* north last edge */ + Pixmap ne; /* northeast corner */ + Pixmap wf; /* west first edge */ + Pixmap w; /* west border */ + Pixmap wl; /* west last edge */ + Pixmap ef; /* east first edge */ + Pixmap e; /* east border */ + Pixmap el; /* east last edge */ + Pixmap sw; /* southwest corner */ + Pixmap sf; /* south first edge */ + Pixmap s; /* south border */ + Pixmap sl; /* south last edge */ + Pixmap se; /* southeast corner */ + unsigned long fg; /* foreground color */ + unsigned long bg; /* background color */ +}; + +/* cursors, fonts, decorations, and decoration sizes */ +struct Visual { + struct Decor decor[STYLE_LAST][DECOR_LAST]; + Cursor cursors[CURSOR_LAST]; + XFontSet fontset; + int edge; /* size of the decoration edge */ + int corner; /* size of the decoration corner */ + int border; /* size of the decoration border */ + int center; /* size of the decoration center */ + int division; /* size of the decoration division */ + int button; /* size of the buttons (actually, it's equal to .tab) */ + int tab; /* height of the tab bar */ +}; + +/* window manager stuff */ +struct WM { + struct Monitor *monhead, *montail; /* list of monitors */ + struct Notification *nhead, *ntail; /* list of notifications */ + struct Container *c; /* list of containers */ + struct Container *focuslist; /* list of containers ordered by focus history */ + struct Container *fulllist; /* list of containers ordered from topmost to bottommost */ + struct Container *abovelist; /* list of containers ordered from topmost to bottommost */ + struct Container *centerlist; /* list of containers ordered from topmost to bottommost */ + struct Container *belowlist; /* list of containers ordered from topmost to bottommost */ + struct Container *focused; /* pointer to focused container */ + struct Container *prevfocused; /* pointer to previously focused container */ + struct Monitor *selmon; /* pointer to selected monitor */ + Window wmcheckwin; /* dummy window required by EWMH */ + Window focuswin; /* dummy window to get focus when no other window has it */ + Window layerwins[LAYER_LAST]; /* dummy windows used to set stacking order */ + int nclients; /* total number of client windows */ + int showingdesk; /* whether the desktop is being shown */ +}; + +/* configuration */ +struct Config { + unsigned int modifier; + unsigned int focusbuttons; + unsigned int raisebuttons; + int ndesktops; + int notifgap; + const char *theme_path; + const char *font; + const char *notifgravity; + + /* the following elements are computed from elements above */ + int gravity; + int direction; +}; + +/* global variables */ +static XSetWindowAttributes clientswa = { + .event_mask = EnterWindowMask | SubstructureNotifyMask | ExposureMask + | SubstructureRedirectMask | ButtonPressMask | FocusChangeMask + | PointerMotionMask +}; +static int (*xerrorxlib)(Display *, XErrorEvent *); +static Display *dpy; +static Window root; +static XrmDatabase xdb; +static GC gc; +static char *xrm; +static int depth; +static int screen, screenw, screenh; +static Atom atoms[ATOM_LAST]; +static struct Visual visual; +static struct WM wm; +static struct Config config; +volatile sig_atomic_t running = 1; + +/* get maximum */ +static int +max(int x, int y) +{ + return x > y ? x : y; +} + +/* get minimum */ +static int +min(int x, int y) +{ + return x < y ? x : y; +} + +/* call malloc checking for error */ +static void * +emalloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) + err(1, "malloc"); + return p; +} + +/* call strndup checking for error */ +static char * +estrndup(const char *s, size_t maxlen) +{ + char *p; + + if ((p = strndup(s, maxlen)) == NULL) + err(1, "strndup"); + return p; +} + +/* call calloc checking for error */ +static void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if ((p = calloc(nmemb, size)) == NULL) + err(1, "malloc"); + return p; +} + +/* error handler */ +static int +xerror(Display *dpy, XErrorEvent *e) +{ + /* stolen from berry, which stole from katriawm, which stole from dwm lol */ + + /* There's no way to check accesses to destroyed windows, thus those + * cases are ignored (especially on UnmapNotify's). Other types of + * errors call Xlibs default error handler, which may call exit. */ + if (e->error_code == BadWindow || + (e->request_code == X_SetInputFocus && e->error_code == BadMatch) || + (e->request_code == X_PolyText8 && e->error_code == BadDrawable) || + (e->request_code == X_PolyFillRectangle && e->error_code == BadDrawable) || + (e->request_code == X_PolySegment && e->error_code == BadDrawable) || + (e->request_code == X_ConfigureWindow && e->error_code == BadMatch) || + (e->request_code == X_GrabButton && e->error_code == BadAccess) || + (e->request_code == X_GrabKey && e->error_code == BadAccess) || + (e->request_code == X_CopyArea && e->error_code == BadDrawable) || + (e->request_code == 139 && e->error_code == BadDrawable) || + (e->request_code == 139 && e->error_code == 143)) + return 0; + + errx(1, "Fatal request. Request code=%d, error code=%d", e->request_code, e->error_code); + return xerrorxlib(dpy, e); +} + +/* create and copy pixmap */ +static Pixmap +copypixmap(Pixmap src, int sx, int sy, int w, int h) +{ + Pixmap pix; + + pix = XCreatePixmap(dpy, root, w, h, depth); + XCopyArea(dpy, src, pix, gc, sx, sy, w, h, 0, 0); + return pix; +} + +/* parse buttons string */ +static unsigned int +parsebuttons(const char *s) +{ + const char *origs; + unsigned int buttons; + + origs = s; + buttons = 0; + while (*s != '\0') { + if (*s < '1' || *s > '5') + errx(1, "improper buttons string %s", origs); + buttons |= 1 << (*s - '1'); + s++; + } + return buttons; +} + +/* parse modifier string */ +static unsigned int +parsemodifier(const char *s) +{ + if (strcasecmp(s, "Mod1") == 0) + return Mod1Mask; + else if (strcasecmp(s, "Mod2") == 0) + return Mod2Mask; + else if (strcasecmp(s, "Mod3") == 0) + return Mod3Mask; + else if (strcasecmp(s, "Mod4") == 0) + return Mod4Mask; + else if (strcasecmp(s, "Mod5") == 0) + return Mod5Mask; + else + errx(1, "improper modifier string %s", s); + return 0; +} + +/* stop running */ +static void +siginthandler(int signo) +{ + (void)signo; + running = 0; +} + +/* init configuration from X resources */ +static void +initconfig(void) +{ + XrmValue xval; + long n; + char *type; + + config.theme_path = NULL; + config.font = FONT; + config.notifgravity = NOTIFGRAVITY; + config.notifgap = NOTIFGAP; + config.ndesktops = NDESKTOPS; + config.modifier = MODIFIER; + config.focusbuttons = FOCUS_BUTTONS; + config.raisebuttons = RAISE_BUTTONS; + + if (xrm == NULL || xdb == NULL) + return; + + if (XrmGetResource(xdb, "shod.theme", "*", &type, &xval) == True) + config.theme_path = xval.addr; + + if (XrmGetResource(xdb, "shod.font", "*", &type, &xval) == True) + config.font = xval.addr; + + if (XrmGetResource(xdb, "shod.notification.gravity", "*", &type, &xval) == True) + config.notifgravity = xval.addr; + + if (XrmGetResource(xdb, "shod.notification.gap", "*", &type, &xval) == True) + if ((n = strtol(xval.addr, NULL, 10)) > 0) + config.notifgap = n; + + if (XrmGetResource(xdb, "shod.ndesktops", "*", &type, &xval) == True) + if ((n = strtol(xval.addr, NULL, 10)) > 0) + config.ndesktops = n; + + if (XrmGetResource(xdb, "shod.modifier", "*", &type, &xval) == True) + config.modifier = parsemodifier(xval.addr); + + if (XrmGetResource(xdb, "shod.focusButtons", "*", &type, &xval) == True) + config.focusbuttons = parsebuttons(xval.addr); + + if (XrmGetResource(xdb, "shod.raiseButtons", "*", &type, &xval) == True) + config.raisebuttons = parsebuttons(xval.addr); +} + +/* initialize signals */ +static void +initsignal(void) +{ + struct sigaction sa; + + /* remove zombies, we may inherit children when exec'ing shod in .xinitrc */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGCHLD, &sa, NULL) == -1) + err(1, "sigaction"); + + /* set running to 0 */ + sa.sa_handler = siginthandler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGINT, &sa, NULL) == -1) + err(1, "sigaction"); +} + +/* create dummy windows used for controlling focus and the layer of clients */ +static void +initdummywindows(void) +{ + XSetWindowAttributes swa; + int i; + + swa.do_not_propagate_mask = NoEventMask; + wm.wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + wm.focuswin = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWDontPropagate, &swa); + for (i = 0; i < LAYER_LAST; i++) { + wm.layerwins[i] = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + } +} + +/* initialize font set */ +static void +initfontset(void) +{ + char **dp, *ds; + int di; + + if ((visual.fontset = XCreateFontSet(dpy, config.font, &dp, &di, &ds)) == NULL) + errx(1, "XCreateFontSet: could not create fontset"); + XFreeStringList(dp); +} + +/* initialize cursors */ +static void +initcursors(void) +{ + visual.cursors[CURSOR_NORMAL] = XCreateFontCursor(dpy, XC_left_ptr); + visual.cursors[CURSOR_MOVE] = XCreateFontCursor(dpy, XC_fleur); + visual.cursors[CURSOR_NW] = XCreateFontCursor(dpy, XC_top_left_corner); + visual.cursors[CURSOR_NE] = XCreateFontCursor(dpy, XC_top_right_corner); + visual.cursors[CURSOR_SW] = XCreateFontCursor(dpy, XC_bottom_left_corner); + visual.cursors[CURSOR_SE] = XCreateFontCursor(dpy, XC_bottom_right_corner); + visual.cursors[CURSOR_N] = XCreateFontCursor(dpy, XC_top_side); + visual.cursors[CURSOR_S] = XCreateFontCursor(dpy, XC_bottom_side); + visual.cursors[CURSOR_W] = XCreateFontCursor(dpy, XC_left_side); + visual.cursors[CURSOR_E] = XCreateFontCursor(dpy, XC_right_side); + visual.cursors[CURSOR_PIRATE] = XCreateFontCursor(dpy, XC_pirate); +} + +/* initialize atom arrays */ +static void +initatoms(void) +{ + char *atomnames[ATOM_LAST] = { + [UTF8_STRING] = "UTF8_STRING", + [WM_DELETE_WINDOW] = "WM_DELETE_WINDOW", + [WM_WINDOW_ROLE] = "WM_WINDOW_ROLE", + [WM_TAKE_FOCUS] = "WM_TAKE_FOCUS", + [WM_PROTOCOLS] = "WM_PROTOCOLS", + [WM_STATE] = "WM_STATE", + [_NET_SUPPORTED] = "_NET_SUPPORTED", + [_NET_CLIENT_LIST] = "_NET_CLIENT_LIST", + [_NET_CLIENT_LIST_STACKING] = "_NET_CLIENT_LIST_STACKING", + [_NET_NUMBER_OF_DESKTOPS] = "_NET_NUMBER_OF_DESKTOPS", + [_NET_CURRENT_DESKTOP] = "_NET_CURRENT_DESKTOP", + [_NET_ACTIVE_WINDOW] = "_NET_ACTIVE_WINDOW", + [_NET_WM_DESKTOP] = "_NET_WM_DESKTOP", + [_NET_SUPPORTING_WM_CHECK] = "_NET_SUPPORTING_WM_CHECK", + [_NET_SHOWING_DESKTOP] = "_NET_SHOWING_DESKTOP", + [_NET_CLOSE_WINDOW] = "_NET_CLOSE_WINDOW", + [_NET_MOVERESIZE_WINDOW] = "_NET_MOVERESIZE_WINDOW", + [_NET_WM_MOVERESIZE] = "_NET_WM_MOVERESIZE", + [_NET_WM_NAME] = "_NET_WM_NAME", + [_NET_WM_WINDOW_TYPE] = "_NET_WM_WINDOW_TYPE", + [_NET_WM_WINDOW_TYPE_DESKTOP] = "_NET_WM_WINDOW_TYPE_DESKTOP", + [_NET_WM_WINDOW_TYPE_MENU] = "_NET_WM_WINDOW_TYPE_MENU", + [_NET_WM_WINDOW_TYPE_TOOLBAR] = "_NET_WM_WINDOW_TYPE_TOOLBAR", + [_NET_WM_WINDOW_TYPE_DOCK] = "_NET_WM_WINDOW_TYPE_DOCK", + [_NET_WM_WINDOW_TYPE_DIALOG] = "_NET_WM_WINDOW_TYPE_DIALOG", + [_NET_WM_WINDOW_TYPE_UTILITY] = "_NET_WM_WINDOW_TYPE_UTILITY", + [_NET_WM_WINDOW_TYPE_SPLASH] = "_NET_WM_WINDOW_TYPE_SPLASH", + [_NET_WM_WINDOW_TYPE_PROMPT] = "_NET_WM_WINDOW_TYPE_PROMPT", + [_NET_WM_WINDOW_TYPE_NOTIFICATION] = "_NET_WM_WINDOW_TYPE_NOTIFICATION", + [_NET_WM_STATE] = "_NET_WM_STATE", + [_NET_WM_STATE_STICKY] = "_NET_WM_STATE_STICKY", + [_NET_WM_STATE_MAXIMIZED_VERT] = "_NET_WM_STATE_MAXIMIZED_VERT", + [_NET_WM_STATE_MAXIMIZED_HORZ] = "_NET_WM_STATE_MAXIMIZED_HORZ", + [_NET_WM_STATE_HIDDEN] = "_NET_WM_STATE_HIDDEN", + [_NET_WM_STATE_FULLSCREEN] = "_NET_WM_STATE_FULLSCREEN", + [_NET_WM_STATE_ABOVE] = "_NET_WM_STATE_ABOVE", + [_NET_WM_STATE_BELOW] = "_NET_WM_STATE_BELOW", + [_NET_WM_STATE_FOCUSED] = "_NET_WM_STATE_FOCUSED", + [_NET_WM_STATE_DEMANDS_ATTENTION] = "_NET_WM_STATE_DEMANDS_ATTENTION", + [_SHOD_CONTAINER_FOCUS] = "_SHOD_CONTAINER_FOCUS", + [_SHOD_WINDOW_FOCUS] = "_SHOD_WINDOW_FOCUS", + [_SHOD_RETILE] = "_SHOD_RETILE", + [_SHOD_CONTAINER_GEOMETRY] = "_SHOD_CONTAINER_GEOMETRY", + [_SHOD_CONTAINER_LIST] = "_SHOD_CONTAINER_LIST", + [_SHOD_CONTAINER] = "_SHOD_CONTAINER", + [_SHOD_ATTACH] = "_SHOD_ATTACH", + [_SHOD_DETACH] = "_SHOD_DETACH", + [_SHOD_CURRENT_MONITOR] = "_SHOD_CURRENT_MONITOR", + [_SHOD_MONITOR] = "_SHOD_MONITOR", + }; + + XInternAtoms(dpy, atomnames, ATOM_LAST, False, atoms); +} + +/* initialize gravity and direction values for notifications */ +static void +initnotif(void) +{ + if (config.notifgravity == NULL || strcmp(config.notifgravity, "NE") == 0) { + config.gravity = NorthEastGravity; + config.direction = DOWNWARDS; + } else if (strcmp(config.notifgravity, "NW") == 0) { + config.gravity = NorthWestGravity; + config.direction = DOWNWARDS; + } else if (strcmp(config.notifgravity, "SW") == 0) { + config.gravity = SouthWestGravity; + config.direction = UPWARDS; + } else if (strcmp(config.notifgravity, "SE") == 0) { + config.gravity = SouthEastGravity; + config.direction = UPWARDS; + } else if (strcmp(config.notifgravity, "N") == 0) { + config.gravity = NorthGravity; + config.direction = DOWNWARDS; + } else if (strcmp(config.notifgravity, "W") == 0) { + config.gravity = WestGravity; + config.direction = DOWNWARDS; + } else if (strcmp(config.notifgravity, "C") == 0) { + config.gravity = CenterGravity; + config.direction = DOWNWARDS; + } else if (strcmp(config.notifgravity, "E") == 0) { + config.gravity = EastGravity; + config.direction = DOWNWARDS; + } else if (strcmp(config.notifgravity, "S") == 0) { + config.gravity = SouthGravity; + config.direction = UPWARDS; + } else { + errx(1, "unknown gravity %s", config.notifgravity); + } +} + +/* set up root window */ +static void +initroot(void) +{ + XSetWindowAttributes swa; + + /* Select SubstructureRedirect events on root window */ + swa.cursor = visual.cursors[CURSOR_NORMAL]; + swa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + | SubstructureRedirectMask + | SubstructureNotifyMask + | StructureNotifyMask + | ButtonPressMask; + XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &swa); + + /* Set focus to root window */ + XSetInputFocus(dpy, root, RevertToParent, CurrentTime); +} + +/* initialize decoration pixmap */ +static void +inittheme(void) +{ + XGCValues val; + XpmAttributes xa; + XImage *img; + Pixmap pix; + struct Decor *d; + unsigned int size; /* size of each square in the .xpm file */ + unsigned int x, y; + unsigned int i, j; + int status; + + memset(&xa, 0, sizeof xa); + if (config.theme_path) /* if the we have specified a file, read it instead */ + status = XpmReadFileToImage(dpy, config.theme_path, &img, NULL, &xa); + else /* else use the default theme */ + status = XpmCreateImageFromData(dpy, theme, &img, NULL, &xa); + if (status != XpmSuccess) + errx(1, "could not load theme"); + + /* create Pixmap from XImage */ + pix = XCreatePixmap(dpy, root, img->width, img->height, img->depth); + val.foreground = 1; + val.background = 0; + XChangeGC(dpy, gc, GCForeground | GCBackground, &val); + XPutImage(dpy, pix, gc, img, 0, 0, 0, 0, img->width, img->height); + + /* check whether the theme has the correct proportions and hotspots */ + size = 0; + if (xa.valuemask & (XpmSize | XpmHotspot) && + xa.width % 3 == 0 && xa.height % 3 == 0 && xa.height == xa.width && + (xa.width / 3) % 2 == 1 && (xa.height / 3) % 2 == 1 && + xa.x_hotspot < ((xa.width / 3) - 1) / 2) { + size = xa.width / 3; + visual.border = xa.x_hotspot; + visual.division = 2 * (visual.border / 2); + visual.button = visual.tab = xa.y_hotspot; + visual.corner = visual.border + visual.tab; + visual.edge = (size - 1) / 2 - visual.corner; + visual.center = size - visual.border * 2; + } + if (size == 0) { + XDestroyImage(img); + XFreePixmap(dpy, pix); + errx(1, "theme in wrong format"); + } + + /* destroy pixmap into decoration parts and copy them into the decor array */ + y = 0; + for (i = 0; i < STYLE_LAST; i++) { + x = 0; + for (j = 0; j < DECOR_LAST; j++) { + d = &visual.decor[i][j]; + d->bl = copypixmap(pix, x + visual.border, y + visual.border, visual.button, visual.button); + d->tl = copypixmap(pix, x + visual.border + visual.button, y + visual.border, visual.edge, visual.tab); + d->t = copypixmap(pix, x + visual.border + visual.button + visual.edge, y + visual.border, 1, visual.tab); + d->tr = copypixmap(pix, x + visual.border + visual.button + visual.edge + 1, y + visual.border, visual.edge, visual.tab); + d->br = copypixmap(pix, x + visual.border + visual.button + 2 * visual.edge + 1, y + visual.border, visual.button, visual.button); + d->nw = copypixmap(pix, x, y, visual.corner, visual.corner); + d->nf = copypixmap(pix, x + visual.corner, y, visual.edge, visual.border); + d->n = copypixmap(pix, x + visual.corner + visual.edge, y, 1, visual.border); + d->nl = copypixmap(pix, x + visual.corner + visual.edge + 1, y, visual.edge, visual.border); + d->ne = copypixmap(pix, x + size - visual.corner, y, visual.corner, visual.corner); + d->wf = copypixmap(pix, x, y + visual.corner, visual.border, visual.edge); + d->w = copypixmap(pix, x, y + visual.corner + visual.edge, visual.border, 1); + d->wl = copypixmap(pix, x, y + visual.corner + visual.edge + 1, visual.border, visual.edge); + d->ef = copypixmap(pix, x + size - visual.border, y + visual.corner, visual.border, visual.edge); + d->e = copypixmap(pix, x + size - visual.border, y + visual.corner + visual.edge, visual.border, 1); + d->el = copypixmap(pix, x + size - visual.border, y + visual.corner + visual.edge + 1, visual.border, visual.edge); + d->sw = copypixmap(pix, x, y + size - visual.corner, visual.corner, visual.corner); + d->sf = copypixmap(pix, x + visual.corner, y + size - visual.border, visual.edge, visual.border); + d->s = copypixmap(pix, x + visual.corner + visual.edge, y + size - visual.border, 1, visual.border); + d->sl = copypixmap(pix, x + visual.corner + visual.edge + 1, y + size - visual.border, visual.edge, visual.border); + d->se = copypixmap(pix, x + size - visual.corner, y + size - visual.corner, visual.corner, visual.corner); + d->fg = XGetPixel(img, x + size / 2, y + visual.corner + visual.edge); + d->bg = XGetPixel(img, x + size / 2, y + visual.border + visual.tab / 2); + x += size; + } + y += size; + } + + XDestroyImage(img); + XFreePixmap(dpy, pix); +} + +/* get pointer to client, tab or transient structure given a window */ +static struct Winres +getwin(Window win) +{ + struct Winres res; + struct Container *c; + struct Column *col; + struct Row *row; + struct Tab *t; + struct Dialog *d; + struct Notification *n; + + res.row = NULL; + res.n = NULL; + res.c = NULL; + res.t = NULL; + res.d = NULL; + for (n = wm.nhead; n != NULL; n = n->next) { + if (win == n->frame || win == n->win) { + res.n = n; + return res; + } + } + for (c = wm.c; c != NULL; c = c->next) { + if (win == c->frame || win == c->curswin) { + res.c = c; + return res; + } + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + if (win == row->bl || win == row->br) { + res.c = c; + res.row = row; + return res; + } + for (t = row->tabs; t != NULL; t = t->next) { + if (win == t->win || win == t->frame || win == t->title) { + res.c = c; + res.t = t; + return res; + } + for (d = t->ds; d != NULL; d = d->next) { + if (win == d->win || win == d->frame) { + res.c = c; + res.t = t; + res.d = d; + return res; + } + } + } + } + } + + } + return res; +} + +/* get monitor given coordinates */ +static struct Monitor * +getmon(int x, int y) +{ + struct Monitor *mon; + + for (mon = wm.monhead; mon; mon = mon->next) + if (x >= mon->mx && x <= mon->mx + mon->mw && + y >= mon->my && y <= mon->my + mon->mh) + return mon; + return NULL; +} + +/* get window name */ +char * +getwinname(Window win) +{ + XTextProperty tprop; + char **list = NULL; + char *name = NULL; + unsigned char *p = NULL; + unsigned long size, dl; + int di; + Atom da; + + if (XGetWindowProperty(dpy, win, atoms[_NET_WM_NAME], 0L, NAMEMAXLEN, False, atoms[UTF8_STRING], + &da, &di, &size, &dl, &p) == Success && p) { + name = estrndup((char *)p, NAMEMAXLEN); + XFree(p); + } else if (XGetWMName(dpy, win, &tprop) && + XmbTextPropertyToTextList(dpy, &tprop, &list, &di) == Success && + di > 0 && list && *list) { + name = estrndup(*list, NAMEMAXLEN); + XFreeStringList(list); + XFree(tprop.value); + } + return name; +} + +/* get atom property from window */ +static Atom +getatomprop(Window win, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + if (XGetWindowProperty(dpy, win, prop, 0L, sizeof atom, False, XA_ATOM, &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + XFree(p); + } + return atom; +} + +/* get window's WM_STATE property */ +static long +getstate(Window w) +{ + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom da; + int di; + + if (XGetWindowProperty(dpy, w, atoms[WM_STATE], 0L, 2L, False, atoms[WM_STATE], + &da, &di, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +/* get tab given window is a transient for */ +static struct Tab * +getdialogfor(Window win) +{ + struct Winres res; + Window tmpwin; + + if (XGetTransientForHint(dpy, win, &tmpwin)) { + res = getwin(tmpwin); + return res.t; + } + return NULL; +} + +/* get focused fullscreen window in given monitor and desktop */ +static struct Container * +getfullscreen(struct Monitor *mon, struct Desktop *desk) +{ + struct Container *c; + + for (c = wm.fulllist; c != NULL; c = c->rnext) + if (!c->isminimized && c->mon == mon && (c->issticky || c->desk == desk)) + return c; + return NULL; +} + +/* get next focused container after old on given monitor and desktop */ +static struct Container * +getnextfocused(struct Desktop *desk) +{ + struct Container *c; + + for (c = wm.focuslist; c != NULL; c = c->next) { + if (c->mon == desk->mon && (c->issticky || c->desk == desk)) { + break; + } + } + return c; +} + +/* get pointer position within a container */ +static enum Octant +getoctant(struct Container *c, Window win, int srcx, int srcy) +{ + Window dw; + int x, y; + + if (XTranslateCoordinates(dpy, win, c->frame, srcx, srcy, &x, &y, &dw) != True) + return 0; + if (c == NULL || c->isminimized) + return 0; + if ((y < c->b && x <= visual.corner) || (x < c->b && y <= visual.corner)) { + return NW; + } else if ((y < c->b && x >= c->w - visual.corner) || (x > c->w - c->b && y <= visual.corner)) { + return NE; + } else if ((y > c->h - c->b && x <= visual.corner) || (x < c->b && y >= c->h - visual.corner)) { + return SW; + } else if ((y > c->h - c->b && x >= c->w - visual.corner) || (x > c->w - c->b && y >= c->h - visual.corner)) { + return SE; + } else if (y < c->b) { + return N; + } else if (y >= c->h - c->b) { + return S; + } else if (x < c->b) { + return W; + } else if (x >= c->w - c->b) { + return E; + } else { + if (x >= c->w / 2 && y >= c->h / 2) { + return SE; + } + if (x >= c->w / 2 && y <= c->h / 2) { + return NE; + } + if (x <= c->w / 2 && y >= c->h / 2) { + return SW; + } + if (x <= c->w / 2 && y <= c->h / 2) { + return NW; + } + } + return 0; +} + +/* check whether window was placed by the user */ +static int +isuserplaced(Window win) +{ + XSizeHints size; + long dl; + + return (XGetWMNormalHints(dpy, win, &size, &dl) && (size.flags & USPosition)); +} + +/* set icccm wmstate */ +static void +icccmwmstate(Window win, int state) +{ + long data[2]; + + data[0] = state; + data[1] = None; + XChangeProperty(dpy, win, atoms[WM_STATE], atoms[WM_STATE], 32, PropModeReplace, (unsigned char *)&data, 2); +} + +/* delete window state property */ +static void +icccmdeletestate(Window win) +{ + XDeleteProperty(dpy, win, atoms[WM_STATE]); +} + +/* initialize ewmh hints */ +static void +ewmhinit(void) +{ + /* set window and property that indicates that the wm is ewmh compliant */ + XChangeProperty(dpy, wm.wmcheckwin, atoms[_NET_SUPPORTING_WM_CHECK], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wm.wmcheckwin, 1); + XChangeProperty(dpy, wm.wmcheckwin, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, PropModeReplace, (unsigned char *) WMNAME, sizeof(WMNAME)-1); + XChangeProperty(dpy, root, atoms[_NET_SUPPORTING_WM_CHECK], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wm.wmcheckwin, 1); + + /* set properties that the window manager supports */ + XChangeProperty(dpy, root, atoms[_NET_SUPPORTED], XA_ATOM, 32, PropModeReplace, (unsigned char *)atoms, ATOM_LAST); + XDeleteProperty(dpy, root, atoms[_NET_CLIENT_LIST]); + + /* set number of desktops */ + XChangeProperty(dpy, root, atoms[_NET_NUMBER_OF_DESKTOPS], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&config.ndesktops, 1); +} + +/* set current desktop hint */ +static void +ewmhsetcurrentdesktop(unsigned long n) +{ + XChangeProperty(dpy, root, atoms[_NET_CURRENT_DESKTOP], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&n, 1); +} + +/* set showing desktop hint */ +static void +ewmhsetshowingdesktop(int n) +{ + XChangeProperty(dpy, root, atoms[_NET_SHOWING_DESKTOP], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&n, 1); +} + +/* set list of clients hint */ +static void +ewmhsetclients(void) +{ + struct Container *c; + struct Column *col; + struct Row *row; + struct Tab *t; + struct Dialog *d; + Window *wins = NULL; + size_t i = 0; + + if (wm.nclients < 1) + return; + wins = ecalloc(wm.nclients, sizeof *wins); + for (c = wm.c; c != NULL; c = c->next) { + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + wins[i++] = t->win; + for (d = t->ds; d != NULL; d = d->next) { + wins[i++] = d->win; + } + } + } + } + } + XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST], XA_WINDOW, 32, PropModeReplace, (unsigned char *)wins, i); + free(wins); +} + +/* set stacking list of clients hint */ +static void +ewmhsetclientsstacking(void) +{ + struct Container *c; + struct Column *col; + struct Row *row; + struct Tab *t; + struct Dialog *d; + Window *wins = NULL; + size_t i = 0; + + if (wm.nclients < 1) + return; + wins = ecalloc(wm.nclients, sizeof *wins); + i = wm.nclients; + for (c = wm.fulllist; c != NULL; c = c->rnext) { + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + wins[--i] = t->win; + for (d = t->ds; d != NULL; d = d->next) { + wins[--i] = d->win; + } + } + } + } + } + for (c = wm.abovelist; c != NULL; c = c->rnext) { + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + wins[--i] = t->win; + for (d = t->ds; d != NULL; d = d->next) { + wins[--i] = d->win; + } + } + } + } + } + for (c = wm.centerlist; c != NULL; c = c->rnext) { + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + wins[--i] = t->win; + for (d = t->ds; d != NULL; d = d->next) { + wins[--i] = d->win; + } + } + } + } + } + for (c = wm.belowlist; c != NULL; c = c->rnext) { + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + wins[--i] = t->win; + for (d = t->ds; d != NULL; d = d->next) { + wins[--i] = d->win; + } + } + } + } + } + XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST], XA_WINDOW, 32, PropModeReplace, (unsigned char *)wins+i, wm.nclients-i); + free(wins); +} + +/* set active window hint */ +static void +ewmhsetactivewindow(Window w) +{ + XChangeProperty(dpy, root, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1); +} + +/* set desktop for a given window */ +static void +ewmhsetdesktop(Window win, long d) +{ + XChangeProperty(dpy, win, atoms[_NET_WM_DESKTOP], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&d, 1); +} + +/* set desktop for all windows in a container */ +static void +ewmhsetwmdesktop(struct Container *c) +{ + struct Column *col; + struct Row *row; + struct Tab *t; + struct Dialog *d; + long n; + + n = (c->issticky || c->isminimized) ? 0xFFFFFFFF : c->desk->n; + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + ewmhsetdesktop(t->win, n); + for (d = t->ds; d; d = d->next) { + ewmhsetdesktop(d->win, n); + } + } + } + } +} + +/* send a WM_DELETE message to client */ +static void +windowclose(Window win) +{ + XEvent ev; + + ev.type = ClientMessage; + ev.xclient.window = win; + ev.xclient.message_type = atoms[WM_PROTOCOLS]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = atoms[WM_DELETE_WINDOW]; + ev.xclient.data.l[1] = CurrentTime; + + /* + * communicate with the given Client, kindly telling it to + * close itself and terminate any associated processes using + * the WM_DELETE_WINDOW protocol + */ + XSendEvent(dpy, win, False, NoEventMask, &ev); +} + +/* notify window of configuration changing */ +static void +winnotify(Window win, int x, int y, int w, int h) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.x = x; + ce.y = y; + ce.width = w; + ce.height = h; + ce.border_width = 0; + ce.above = None; + ce.override_redirect = False; + ce.event = win; + ce.window = win; + XSendEvent(dpy, win, False, StructureNotifyMask, (XEvent *)&ce); +} + +/* check whether window is urgent */ +static int +winisurgent(Window win) +{ + XWMHints *wmh; + int ret; + + ret = 0; + if ((wmh = XGetWMHints(dpy, win)) != NULL) { + ret = wmh->flags & XUrgencyHint; + XFree(wmh); + } + return ret; +} + +/* check if container is visible */ +static int +containerisvisible(struct Container *c) +{ + if (c == NULL || c->isminimized) + return 0; + if (c->issticky || c->desk == c->desk->mon->seldesk) + return 1; + return 0; +} + +/* get tab decoration style */ +static int +tabgetstyle(struct Tab *t) +{ + if (t->isurgent) + return URGENT; + if (t->row->col->c == wm.focused) + return FOCUSED; + return UNFOCUSED; +} + +/* get decoration style (and state) of container */ +static int +containergetstyle(struct Container *c) +{ + struct Column *col; + struct Row *row; + struct Tab *t; + + if (c == wm.focused) + return FOCUSED; + for (col = c->cols; col != NULL; col = col->next) + for (row = col->rows; row != NULL; row = row->next) + for (t = row->tabs; t != NULL; t = t->next) + if (t->isurgent) + return URGENT; + return UNFOCUSED; +} + +/* calculate size of dialogs of a tab */ +static void +dialogcalcsize(struct Dialog *d) +{ + struct Tab *t; + + t = d->t; + d->w = max(1, min(d->maxw, t->winw - 2 * visual.border)); + d->h = max(1, min(d->maxh, t->winh - 2 * visual.border)); + d->x = t->winw / 2 - d->w / 2; + d->y = t->winh / 2 - d->h / 2; +} + +/* calculate position and width of tabs of a row */ +static void +rowcalctabs(struct Row *row) +{ + struct Dialog *d; + struct Tab *t; + int i, x; + + x = visual.button; + for (i = 0, t = row->tabs; t != NULL; t = t->next, i++) { + t->winw = row->col->w; + t->winh = row->h - visual.tab; + t->w = max(1, ((i + 1) * (t->winw - 2 * visual.button) / row->ntabs) - (i * (t->winw - 2 * visual.button) / row->ntabs)); + t->x = x; + x += t->w; + for (d = t->ds; d != NULL; d = d->next) { + dialogcalcsize(d); + } + } +} + +/* calculate position and height of rows of a column */ +static void +colcalcrows(struct Column *col, int recursive) +{ + struct Container *c; + struct Row *row; + int i, y, h, sumh; + + c = col->c; + + /* check if rows sum up the height of the container */ + sumh = 0; + for (row = col->rows; row != NULL; row = row->next) { + sumh += row->h; + } + sumh += (col->nrows - 1) * visual.division; + + h = col->c->h - 2 * c->b - (col->nrows - 1) * visual.division; + y = c->b; + for (i = 0, row = col->rows; row != NULL; row = row->next, i++) { + if (sumh != c->h) { + row->h = max(1, ((i + 1) * h / col->nrows) - (i * h / col->nrows)); + row->y = y; + y += row->h + visual.division; + } + if (recursive) { + rowcalctabs(row); + } + } +} + +/* calculate position and width of columns of a container */ +static void +containercalccols(struct Container *c, int recursive) +{ + struct Column *col; + int i, x, w, sumw; + + if (c->ismaximized) { + c->x = c->mx; + c->y = c->my; + c->w = c->mw; + c->h = c->mh; + } else { + c->x = c->nx; + c->y = c->ny; + c->w = c->nw; + c->h = c->nh; + } + + /* check if columns sum up the width of the container */ + sumw = 0; + for (col = c->cols; col != NULL; col = col->next) { + sumw += col->w; + } + sumw += (c->ncols - 1) * visual.division; + + w = c->w - 2 * c->b - (c->ncols - 1) * visual.division; + x = c->b; + for (i = 0, col = c->cols; col != NULL; col = col->next, i++) { + if (sumw != c->w) { + col->w = max(1, ((i + 1) * w / c->ncols) - (i * w / c->ncols)); + col->x = x; + x += col->w + visual.division; + } + if (recursive) { + colcalcrows(col, 1); + } + } +} + +/* find best position to place a container on screen */ +static void +containerplace(struct Container *c, struct Desktop *desk, int userplaced) +{ + struct Monitor *mon; + struct Container *tmp; + int grid[DIV][DIV] = {{0}, {0}}; + int lowest; + int i, j, k, w, h; + int ha, hb, wa, wb; + int ya, yb, xa, xb; + int subx, suby; /* position of the larger subregion */ + int subw, subh; /* larger subregion width and height */ + int origw, origh; + + if (desk == NULL || c == NULL || c->isminimized) + return; + + mon = desk->mon; + + /* if window is bigger than monitor, resize it while maintaining proportion */ + origw = c->nw + 2 * c->b; + origh = c->nh + 2 * c->b; + w = min(origw, mon->ww); + h = min(origh, mon->wh); + if (origw * h > origh * w) { + h = (origh * w) / origw; + w = (origw * h) / origh; + } else { + w = (origw * h) / origh; + h = (origh * w) / origw; + } + c->nw = max(visual.center + c->b * 2, w - (2 * c->b)); + c->nh = max(visual.center + c->b * 2, h - (2 * c->b)); + + /* if the user placed the window, we should not re-place it */ + if (userplaced) + return; + + /* increment cells of grid a window is in */ + for (tmp = wm.c; tmp; tmp = tmp->next) { + if (tmp != c && ((tmp->issticky && tmp->mon == mon) || tmp->desk == desk)) { + for (i = 0; i < DIV; i++) { + for (j = 0; j < DIV; j++) { + ha = mon->wy + (mon->wh * i)/DIV; + hb = mon->wy + (mon->wh * (i + 1))/DIV; + wa = mon->wx + (mon->ww * j)/DIV; + wb = mon->wx + (mon->ww * (j + 1))/DIV; + ya = tmp->ny; + yb = tmp->ny + tmp->nh + 2 * tmp->b; + xa = tmp->nx; + xb = tmp->nx + tmp->nw + 2 * tmp->b; + if (ya <= hb && ha <= yb && xa <= wb && wa <= xb) { + if (ya < ha && yb > hb) + grid[i][j]++; + if (xa < wa && xb > wb) + grid[i][j]++; + grid[i][j]++; + } + } + } + } + } + + /* find biggest region in grid with less windows in it */ + lowest = INT_MAX; + subx = suby = 0; + subw = subh = 0; + for (i = 0; i < DIV; i++) { + for (j = 0; j < DIV; j++) { + if (grid[i][j] > lowest) + continue; + else if (grid[i][j] < lowest) { + lowest = grid[i][j]; + subw = subh = 0; + } + for (w = 0; j+w < DIV && grid[i][j + w] == lowest; w++) + ; + for (h = 1; i+h < DIV && grid[i + h][j] == lowest; h++) { + for (k = 0; k < w && grid[i + h][j + k] == lowest; k++) + ; + if (k < w) + break; + } + if (k < w) + h--; + if (w * h > subw * subh) { + subw = w; + subh = h; + suby = i; + subx = j; + } + } + } + subx = subx * mon->ww / DIV; + suby = suby * mon->wh / DIV; + subw = subw * mon->ww / DIV; + subh = subh * mon->wh / DIV; + c->nx = min(mon->wx + mon->ww - c->nw - c->b, max(mon->wx + c->b, mon->wx + subx + subw / 2 - c->nw / 2)); + c->ny = min(mon->wy + mon->wh - c->nh - c->b, max(mon->wy + c->b, mon->wy + suby + subh / 2 - c->nh / 2)); + containercalccols(c, 1); +} + +/* decorate dialog window */ +static void +dialogdecorate(struct Dialog *d) +{ + XGCValues val; + struct Decor *decor; /* unpressed decoration */ + int fullw, fullh; /* size of dialog window + borders */ + int partw, parth; /* size of dialog window + borders - corners */ + + decor = &visual.decor[tabgetstyle(d->t)][DIALOG]; + fullw = d->w + 2 * visual.border; + fullh = d->h + 2 * visual.border; + partw = fullw - 2 * visual.corner; + parth = fullh - 2 * visual.corner; + + /* (re)create pixmap */ + if (d->pw != fullw || d->ph != fullh || d->pix == None) { + if (d->pix != None) + XFreePixmap(dpy, d->pix); + d->pix = XCreatePixmap(dpy, d->frame, fullw, fullh, depth); + } + d->pw = fullw; + d->ph = fullh; + + val.fill_style = FillTiled; + val.tile = decor->w; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, d->pix, gc, 0, visual.corner, visual.border, parth); + + val.tile = decor->e; + val.ts_x_origin = visual.border + d->w; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, d->pix, gc, visual.border + d->w, visual.corner, visual.border, parth); + + val.tile = decor->n; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, d->pix, gc, visual.corner, 0, partw, visual.border); + + val.tile = decor->s; + val.ts_x_origin = 0; + val.ts_y_origin = visual.border + d->h; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, d->pix, gc, visual.corner, visual.border + d->h, partw, visual.border); + + XCopyArea(dpy, decor->nw, d->pix, gc, 0, 0, visual.corner, visual.corner, 0, 0); + XCopyArea(dpy, decor->ne, d->pix, gc, 0, 0, visual.corner, visual.corner, fullw - visual.corner, 0); + XCopyArea(dpy, decor->sw, d->pix, gc, 0, 0, visual.corner, visual.corner, 0, fullh - visual.corner); + XCopyArea(dpy, decor->se, d->pix, gc, 0, 0, visual.corner, visual.corner, fullw - visual.corner, fullh - visual.corner); + + val.fill_style = FillSolid; + val.foreground = decor->bg; + XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); + XFillRectangle(dpy, d->pix, gc, visual.border, visual.border, d->w, d->h); + + XCopyArea(dpy, d->pix, d->frame, gc, 0, 0, fullw, fullh, 0, 0); +} + +/* decorate tab */ +static void +tabdecorate(struct Tab *t, int pressed) +{ + XGCValues val; + XRectangle box, dr; + struct Decor *decor; + size_t len; + int style; + int x, y; + + style = tabgetstyle(t); + if (t->row != NULL && t != t->row->seltab) + decor = &visual.decor[style][TAB_UNFOCUSED]; + else if (t->row != NULL && pressed) + decor = &visual.decor[style][TAB_PRESSED]; + else + decor = &visual.decor[style][TAB_FOCUSED]; + + /* (re)create pixmap */ + if (t->ptw != t->w || t->pixtitle == None) { + if (t->pixtitle != None) + XFreePixmap(dpy, t->pixtitle); + t->pixtitle = XCreatePixmap(dpy, t->title, t->w, visual.tab, depth); + } + t->ptw = t->w; + + if (t->pw != t->winw || t->ph != t->winh || t->pix == None) { + if (t->pix != None) + XFreePixmap(dpy, t->pix); + t->pix = XCreatePixmap(dpy, t->frame, t->winw, t->winh, depth); + } + t->pw = t->winw; + t->ph = t->winh; + + /* draw tab */ + val.tile = decor->t; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + val.fill_style = FillTiled; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin | GCFillStyle, &val); + XFillRectangle(dpy, t->pixtitle, gc, visual.edge, 0, t->w - visual.edge, visual.tab); + XCopyArea(dpy, decor->tl, t->pixtitle, gc, 0, 0, visual.edge, visual.tab, 0, 0); + XCopyArea(dpy, decor->tr, t->pixtitle, gc, 0, 0, visual.edge, visual.tab, t->w - visual.edge, 0); + + /* write tab title */ + if (t->name != NULL) { + len = strlen(t->name); + val.fill_style = FillSolid; + val.foreground = decor->fg; + XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); + XmbTextExtents(visual.fontset, t->name, len, &dr, &box); + x = (t->w - box.width) / 2 - box.x; + y = (visual.tab - box.height) / 2 - box.y; + XmbDrawString(dpy, t->pixtitle, visual.fontset, gc, x, y, t->name, len); + } + + /* draw frame background */ + if (!pressed) { + val.foreground = decor->bg; + val.fill_style = FillSolid; + XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); + XFillRectangle(dpy, t->pix, gc, 0, 0, t->winw, t->winh); + } + + XCopyArea(dpy, t->pixtitle, t->title, gc, 0, 0, t->w, visual.tab, 0, 0); + XCopyArea(dpy, t->pix, t->frame, gc, 0, 0, t->winw, t->winh, 0, 0); +} + +/* draw title bar buttons */ +static void +buttondecorate(struct Row *row, int button, int pressed) +{ + struct Decor *decor; /* decoration */ + int style; /* decoration style, used as index in the decor array */ + + style = containergetstyle(row->col->c); + decor = pressed ? &visual.decor[style][PRESSED] : &visual.decor[style][UNPRESSED]; + + if (button == BUTTON_LEFT) { + XCopyArea(dpy, decor->bl, row->pixbl, gc, 0, 0, visual.button, visual.button, 0, 0); + XCopyArea(dpy, row->pixbl, row->bl, gc, 0, 0, visual.button, visual.button, 0, 0); + } + + if (button == BUTTON_RIGHT) { + XCopyArea(dpy, decor->br, row->pixbr, gc, 0, 0, visual.button, visual.button, 0, 0); + XCopyArea(dpy, row->pixbr, row->br, gc, 0, 0, visual.button, visual.button, 0, 0); + } +} + +/* draw decoration on container frame */ +static void +containerdecorate(struct Container *c, int recursive, enum Octant o) +{ + struct Decor *decor; /* unpressed decoration */ + struct Decor *decorp; /* pressed decoration */ + struct Column *col; + struct Row *row; + struct Tab *t; + struct Dialog *d; + XGCValues val; + int style; /* decoration style, used as index in the decor array */ + int w, h; /* size of the edges */ + + if (c == NULL) + return; + style = containergetstyle(c); + decor = &visual.decor[style][UNPRESSED]; + decorp = &visual.decor[style][PRESSED]; + val.fill_style = FillTiled; + XChangeGC(dpy, gc, GCFillStyle, &val); + w = c->w - visual.corner * 2; + h = c->h - visual.corner * 2; + + /* (re)create pixmap */ + if (c->pw != c->w || c->ph != c->h || c->pix == None) { + if (c->pix != None) + XFreePixmap(dpy, c->pix); + c->pix = XCreatePixmap(dpy, c->frame, c->w, c->h, depth); + } + c->pw = c->w; + c->ph = c->h; + + if (c->b > 0) { + + /* draw borders */ + if (w > 0 && (o == 0 || o == N)) { + val.tile = (o == N) ? decorp->n : decor->n; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, c->pix, gc, visual.corner, 0, w, c->b); + XCopyArea(dpy, (o == N) ? decorp->nf : + decor->nf, c->pix, gc, 0, 0, visual.edge, visual.border, + visual.corner, 0); + XCopyArea(dpy, (o == N) ? decorp->nl : + decor->nl, c->pix, gc, 0, 0, visual.edge, visual.border, + visual.corner + w - visual.edge, 0); + } + + if (w > 0 && (o == 0 || o == S)) { + val.tile = (o == S) ? decorp->s : decor->s; + val.ts_x_origin = 0; + val.ts_y_origin = c->h - c->b; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin , &val); + XFillRectangle(dpy, c->pix, gc, visual.corner, c->h - c->b, w, c->b); + XCopyArea(dpy, (o == S) ? decorp->sf : + decor->sf, c->pix, gc, 0, 0, visual.edge, visual.border, + visual.corner, c->h - visual.border); + XCopyArea(dpy, (o == S) ? decorp->sl : + decor->sl, c->pix, gc, 0, 0, visual.edge, visual.border, + visual.corner + w - visual.edge, c->h - visual.border); + } + + if (h > 0 && (o == 0 || o == W)) { + val.tile = (o == W) ? decorp->w : decor->w; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin , &val); + XFillRectangle(dpy, c->pix, gc, 0, visual.corner, c->b, h); + XCopyArea(dpy, (o == W) ? decorp->wf : + decor->wf, c->pix, gc, 0, 0, visual.border, visual.edge, 0, + visual.corner); + XCopyArea(dpy, (o == W) ? decorp->wl : + decor->wl, c->pix, gc, 0, 0, visual.border, visual.edge, 0, + visual.corner + h - visual.edge); + } + + if (h > 0 && (o == 0 || o == E)) { + val.tile = (o == E) ? decorp->e : decor->e; + val.ts_x_origin = c->w - c->b; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin , &val); + XFillRectangle(dpy, c->pix, gc, c->w - c->b, visual.corner, c->b, h); + XCopyArea(dpy, (o == E) ? decorp->ef : + decor->ef, c->pix, gc, 0, 0, visual.border, visual.edge, + c->w - visual.border, visual.corner); + XCopyArea(dpy, (o == E) ? decorp->el : + decor->el, c->pix, gc, 0, 0, visual.border, visual.edge, + c->w - visual.border, visual.corner + h - visual.edge); + } + + if (o == 0 || o == NW) { + XCopyArea(dpy, (o == NW) ? decorp->nw : + decor->nw, c->pix, gc, 0, 0, visual.corner, visual.corner, 0, 0); + } + + if (o == 0 || o == NE) { + XCopyArea(dpy, (o == NE) ? decorp->ne : + decor->ne, c->pix, gc, 0, 0, visual.corner, visual.corner, + c->w - visual.corner, 0); + } + + if (o == 0 || o == SW) { + XCopyArea(dpy, (o == SW) ? decorp->sw : + decor->sw, c->pix, gc, 0, 0, visual.corner, visual.corner, + 0, c->h - visual.corner); + } + + if (o == 0 || o == SE) { + XCopyArea(dpy, (o == SE) ? decorp->se : + decor->se, c->pix, gc, 0, 0, visual.corner, visual.corner, + c->w - visual.corner, c->h - visual.corner); + } + } + + /* draw background */ + if (o == 0) { + val.foreground = decor->bg; + val.fill_style = FillSolid; + XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); + XFillRectangle(dpy, c->pix, gc, c->b, c->b, c->w - 2 * c->b, c->h - 2 * c->b); + } + + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + if (o == 0) { + buttondecorate(row, BUTTON_LEFT, 0); + buttondecorate(row, BUTTON_RIGHT, 0); + } + if (recursive) { + for (t = row->tabs; t != NULL; t = t->next) { + tabdecorate(t, 0); + for (d = t->ds; d != NULL; d = d->next) { + dialogdecorate(d); + } + } + } + } + } + + XCopyArea(dpy, c->pix, c->frame, gc, 0, 0, c->w, c->h, 0, 0); +} + +/* remove container from the focus list */ +static void +containerdelfocus(struct Container *c) +{ + if (c->fnext) { + c->fnext->fprev = c->fprev; + } + if (c->fprev) { + c->fprev->fnext = c->fnext; + } else if (wm.focuslist == c) { + wm.focuslist = c->fnext; + } +} + +/* put container on beginning of focus list */ +static void +containeraddfocus(struct Container *c) +{ + if (c == NULL || c->isminimized) + return; + containerdelfocus(c); + c->fnext = wm.focuslist; + c->fprev = NULL; + if (wm.focuslist) + wm.focuslist->fprev = c; + wm.focuslist = c; +} + +/* hide container */ +static void +containerhide(struct Container *c, int hide) +{ + struct Column *col; + struct Row *row; + struct Tab *t; + struct Dialog *d; + + if (c == NULL) + return; + c->ishidden = hide; + if (hide) + XUnmapWindow(dpy, c->frame); + else + XMapWindow(dpy, c->frame); + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + icccmwmstate(t->win, (hide ? IconicState : NormalState)); + for (d = t->ds; d != NULL; d = d->next) { + icccmwmstate(d->win, (hide ? IconicState : NormalState)); + } + } + } + } +} + +/* commit dialog size and position */ +static void +dialogmoveresize(struct Dialog *d) +{ + int dx, dy, dw, dh; + + dx = d->x - visual.border; + dy = d->y - visual.border; + dw = d->w + 2 * visual.border; + dh = d->h + 2 * visual.border; + XMoveResizeWindow(dpy, d->frame, dx, dy, dw, dh); + XMoveResizeWindow(dpy, d->win, visual.border, visual.border, d->w, d->h); + if (d->pw != dw || d->ph != dh) { + dialogdecorate(d); + } +} + +/* configure transient window */ +static void +dialogconfigure(struct Dialog *d, unsigned int valuemask, XWindowChanges *wc) +{ + if (d == NULL) + return; + if (valuemask & CWWidth) + d->maxw = wc->width; + if (valuemask & CWHeight) + d->maxh = wc->height; + dialogmoveresize(d); +} + +/* commit tab size and position */ +static void +tabmoveresize(struct Tab *t) +{ + struct Dialog *d; + int x, y, w, h; + + x = t->row->col->x; + y = t->row->y; + w = t->row->col->w; + h = t->row->h; + XMoveResizeWindow(dpy, t->frame, x, y + visual.tab, t->winw, t->winh); + for (d = t->ds; d != NULL; d = d->next) { + dialogmoveresize(d); + } + XResizeWindow(dpy, t->win, t->winw, t->winh); + XMoveResizeWindow(dpy, t->title, x + t->x, y, t->w, visual.tab); + if (t->ptw != t->w) { + tabdecorate(t, 0); + } +} + +/* commit container size and position */ +static void +containermoveresize(struct Container *c) +{ + struct Column *col; + struct Row *row; + struct Tab *t; + + if (c == NULL) + return; + XMoveResizeWindow(dpy, c->frame, c->x, c->y, c->w, c->h); + XMoveResizeWindow(dpy, c->curswin, 0, 0, c->w, c->h); + for (col = c->cols; col != NULL; col = col->next) { + for (row = col->rows; row != NULL; row = row->next) { + for (t = row->tabs; t != NULL; t = t->next) { + tabmoveresize(t); + } + } + } + if (c->pw != c->w || c->ph != c->h) { + containerdecorate(c, 0, 0); + } +} + +/* configure container size and position */ +static void +containerconfigure(struct Container *c, unsigned int valuemask, XWindowChanges *wc) +{ + if (c == NULL || c->isminimized || c->isfullscreen || c->ismaximized) + return; + if (valuemask & CWX) + c->nx = wc->x; + if (valuemask & CWY) + c->ny = wc->y; + if (valuemask & CWWidth) + c->nw = wc->width; + if (valuemask & CWHeight) + c->nh = wc->height; + containercalccols(c, 1); + containermoveresize(c); +} + +/* remove container from the raise list */ +static void +containerdelraise(struct Container *c) +{ + if (c->rnext) { + c->rnext->rprev = c->rprev; + } + if (c->rprev) { + c->rprev->rnext = c->rnext; + } else if (wm.fulllist == c) { + wm.fulllist = c->rnext; + } else if (wm.abovelist == c) { + wm.abovelist = c->rnext; + } else if (wm.centerlist == c) { + wm.centerlist = c->rnext; + } else if (wm.belowlist == c) { + wm.belowlist = c->rnext; + } +} + +/* put container on beginning of a raise list */ +static void +containeraddraise(struct Container *c) +{ + struct Container **list; + + containerdelraise(c); + if (c->isfullscreen) + list = &wm.fulllist; + else if (c->layer > 0) + list = &wm.abovelist; + else if (c->layer < 0) + list = &wm.belowlist; + else + list = &wm.centerlist; + c->rnext = *list; + c->rprev = NULL; + if (*list != NULL) + (*list)->rprev = c; + *list = c; +} + +/* raise container */ +static void +containerraise(struct Container *c) +{ + Window wins[2]; + + if (c == NULL || c->isminimized) + return; + containeraddraise(c); + wins[1] = c->frame; + if (c->isfullscreen) + wins[0] = wm.layerwins[LAYER_FULLSCREEN]; + else if (c->layer > 0) + wins[0] = wm.layerwins[LAYER_ABOVE]; + else if (c->layer < 0) + wins[0] = wm.layerwins[LAYER_BELOW]; + else + wins[0] = wm.layerwins[LAYER_NORMAL]; + XRestackWindows(dpy, wins, sizeof(wins)); + ewmhsetclientsstacking(); +} + +/* send container to desktop, raise it and optionally place it */ +static void +containersendtodesk(struct Container *c, struct Desktop *desk, int place, int userplaced) +{ + int visible; + + if (c == NULL || desk == NULL || c->isminimized) + return; + c->desk = desk; + c->mon = desk->mon; + if (place) { + containerplace(c, c->desk, userplaced); + } + visible = containerisvisible(c); + containerhide(c, !visible); + containerraise(c); + ewmhsetwmdesktop(c); +} + +/* minimize container; optionally focus another container */ +static void +containerminimize(struct Container *c, int minimize, int focus) +{ + void tabfocus(struct Tab *, int); + struct Container *tofocus; + + if (minimize != REMOVE && !c->isminimized) { + c->isminimized = 1; + containerhide(c, 1); + containerdelfocus(c); + if (focus) { + if ((tofocus = getnextfocused(c->desk)) != NULL) { + tabfocus(tofocus->selcol->selrow->seltab, 0); + } else { + tabfocus(NULL, 0); + } + } + } else if (minimize != ADD && c->isminimized) { + c->isminimized = 0; + containersendtodesk(c, wm.selmon->seldesk, 1, 0); + containermoveresize(c); + /* no need to call clienthide(c, 0) here for containersendtodesk already calls it */ + } +} + +/* create new container */ +static struct Container * +containernew(int x, int y, int w, int h) +{ + struct Container *c; + + c = emalloc(sizeof *c); + c->prev = c->next = NULL; + c->fprev = c->fnext = NULL; + c->rprev = c->rnext = NULL; + c->mon = NULL; + c->cols = NULL; + c->selcol = NULL; + c->ncols = 0; + c->isfullscreen = 0; + c->ismaximized = 0; + c->isminimized = 0; + c->issticky = 0; + c->ishidden = 0; + c->layer = 0; + c->desk = 0; + c->pw = c->ph = 0; + c->mx = c->my = c->mw = c->mh = 0; + c->x = c->nx = x - visual.border; + c->y = c->ny = y - visual.border; + c->w = c->nw = w + 2 * visual.border; + c->h = c->nh = h + 2 * visual.border + visual.tab; + c->b = visual.border; + c->pix = None; + c->frame = XCreateWindow(dpy, root, c->x, c->y, c->w, c->h, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &clientswa); + c->curswin = XCreateWindow(dpy, c->frame, 0, 0, c->w, c->h, 0, + CopyFromParent, InputOnly, CopyFromParent, + 0, NULL); + XMapWindow(dpy, c->curswin); + if (wm.c) + wm.c->prev = c; + c->next = wm.c; + wm.c = c; + return c; +} + +/* move container x pixels to the right and y pixels down */ +static void +containerincrmove(struct Container *c, int x, int y) +{ + struct Monitor *monto; + + if (c == NULL || c->isminimized || c->ismaximized || c->isfullscreen) + return; + c->nx += x; + c->ny += y; + c->x = c->nx; + c->y = c->ny; + containermoveresize(c); + if (!c->issticky) { + monto = getmon(c->nx + c->nw / 2, c->ny + c->nh / 2); + if (monto != NULL && monto != c->mon) { + containersendtodesk(c, monto->seldesk, 0, 0); + } + } +} + +/* delete dialog */ +static void +dialogdel(struct Dialog *d) +{ + if (d->next) + d->next->prev = d->prev; + if (d->prev) + d->prev->next = d->next; + else + d->t->ds = d->next; + if (d->pix != None) + XFreePixmap(dpy, d->pix); + icccmdeletestate(d->win); + XReparentWindow(dpy, d->win, root, 0, 0); + XDestroyWindow(dpy, d->frame); + free(d); +} + +/* detach tab from row */ +static void +tabdetach(struct Tab *t, int x, int y) +{ + if (t->row->seltab == t) + t->row->seltab = (t->prev != NULL) ? t->prev : t->next; + t->row->ntabs--; + t->ignoreunmap = IGNOREUNMAP; + XReparentWindow(dpy, t->title, root, x, y); + if (t->next) + t->next->prev = t->prev; + if (t->prev) + t->prev->next = t->next; + else + t->row->tabs = t->next; + t->next = NULL; + t->prev = NULL; + rowcalctabs(t->row); +} + +/* delete tab */ +static void +tabdel(struct Tab *t) +{ + while (t->ds) + dialogdel(t->ds); + tabdetach(t, 0, 0); + if (t->pixtitle != None) + XFreePixmap(dpy, t->pixtitle); + if (t->pix != None) + XFreePixmap(dpy, t->pix); + icccmdeletestate(t->win); + XReparentWindow(dpy, t->win, root, 0, 0); + XDestroyWindow(dpy, t->title); + XDestroyWindow(dpy, t->frame); + free(t->name); + free(t->class); + free(t); +} + +/* detach row from column */ +static void +rowdetach(struct Row *row) +{ + if (row->col->selrow == row) + row->col->selrow = (row->prev != NULL) ? row->prev : row->next; + row->col->nrows--; + if (row->next) + row->next->prev = row->prev; + if (row->prev) + row->prev->next = row->next; + else + row->col->rows = row->next; + row->next = NULL; + row->prev = NULL; + colcalcrows(row->col, 0); +} + +/* delete row */ +static void +rowdel(struct Row *row) +{ + while (row->tabs) + tabdel(row->tabs); + rowdetach(row); + XDestroyWindow(dpy, row->bl); + XDestroyWindow(dpy, row->br); + XFreePixmap(dpy, row->pixbl); + XFreePixmap(dpy, row->pixbr); + free(row); +} + +/* detach column from container */ +static void +coldetach(struct Column *col) +{ + if (col->c->selcol == col) + col->c->selcol = (col->prev != NULL) ? col->prev : col->next; + col->c->ncols--; + if (col->next) + col->next->prev = col->prev; + if (col->prev) + col->prev->next = col->next; + else + col->c->cols = col->next; + col->next = NULL; + col->prev = NULL; + containercalccols(col->c, 0); +} + +/* delete column */ +static void +coldel(struct Column *col) +{ + while (col->rows) + rowdel(col->rows); + coldetach(col); + free(col); +} + +/* delete container */ +static void +containerdel(struct Container *c) +{ + containerdelfocus(c); + containerdelraise(c); + if (wm.focused == c) + wm.focused = NULL; + if (c->next) + c->next->prev = c->prev; + if (c->prev) + c->prev->next = c->next; + else + wm.c = c->next; + while (c->cols) + coldel(c->cols); + if (c->pix != None) + XFreePixmap(dpy, c->pix); + XDestroyWindow(dpy, c->frame); + XDestroyWindow(dpy, c->curswin); + free(c); +} + +/* add column to container */ +static void +containeraddcol(struct Container *c, struct Column *col, int pos) +{ + struct Container *oldc; + struct Column *tmp, *prev; + int i; + + oldc = col->c; + col->c = c; + c->selcol = col; + c->ncols++; + if (pos == 0 || c->cols == NULL) { + col->prev = NULL; + col->next = c->cols; + if (c->cols != NULL) + c->cols->prev = col; + c->cols = col; + } else { + for (i = 0, prev = tmp = c->cols; tmp != NULL && (pos < 0 || i < pos); tmp = tmp->next, i++) + prev = tmp; + if (prev->next != NULL) + prev->next->prev = col; + col->next = prev->next; + col->prev = prev; + prev->next = col; + } + containercalccols(c, 0); + if (oldc != NULL && oldc->ncols == 0) { + containerdel(oldc); + } +} + +/* create new column */ +static struct Column * +colnew(void) +{ + struct Column *col; + + col = emalloc(sizeof(*col)); + col->prev = col->next = NULL; + col->c = NULL; + col->rows = NULL; + col->selrow = NULL; + col->nrows = 0; + col->x = 0; + col->w = 0; + return col; +} + +/* add row to column */ +static void +coladdrow(struct Column *col, struct Row *row, int pos) +{ + struct Container *c; + struct Column *oldcol; + struct Row *tmp, *prev; + int i; + + c = col->c; + oldcol = row->col; + row->col = col; + col->selrow = row; + col->nrows++; + if (pos == 0 || col->rows == NULL) { + row->prev = NULL; + row->next = col->rows; + if (col->rows != NULL) + col->rows->prev = row; + col->rows = row; + } else { + for (i = 0, prev = tmp = col->rows; tmp && (pos < 0 || i < pos); tmp = tmp->next, i++) + prev = tmp; + if (prev->next) + prev->next->prev = row; + row->next = prev->next; + row->prev = prev; + prev->next = row; + } + colcalcrows(col, 0); /* set row->y, row->h, etc */ + XReparentWindow(dpy, row->bl, c->frame, col->x, row->y); + XMapWindow(dpy, row->bl); + XReparentWindow(dpy, row->br, c->frame, col->x + col->w - visual.button, row->y); + XMapWindow(dpy, row->br); + if (oldcol != NULL && oldcol->nrows == 0) { + coldel(oldcol); + } +} + +/* create new row */ +static struct Row * +rownew(void) +{ + struct Row *row; + + row = emalloc(sizeof(*row)); + row->prev = row->next = NULL; + row->col = NULL; + row->tabs = NULL; + row->seltab = NULL; + row->ntabs = 0; + row->y = 0; + row->h = 0; + row->bl = XCreateWindow(dpy, root, 0, 0, visual.button, visual.button, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &clientswa); + row->pixbl = XCreatePixmap(dpy, row->bl, visual.button, visual.button, depth); + row->br = XCreateWindow(dpy, root, 0, 0, visual.button, visual.button, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &clientswa); + row->pixbr = XCreatePixmap(dpy, row->bl, visual.button, visual.button, depth); + XDefineCursor(dpy, row->br, visual.cursors[CURSOR_PIRATE]); + return row; +} + +/* add tab to row */ +static void +rowaddtab(struct Row *row, struct Tab *t, int pos) +{ + struct Container *c; + struct Column *col; + struct Row *oldrow; + struct Tab *tmp, *prev; + int i; + + c = row->col->c; + col = row->col; + oldrow = t->row; + t->row = row; + row->seltab = t; + row->ntabs++; + if (pos == 0 || row->tabs == NULL) { + t->prev = NULL; + t->next = row->tabs; + if (row->tabs != NULL) + row->tabs->prev = t; + row->tabs = t; + } else { + for (i = 0, prev = tmp = row->tabs; tmp && (pos < 0 || i < pos); tmp = tmp->next, i++) + prev = tmp; + if (prev->next) + prev->next->prev = t; + t->next = prev->next; + t->prev = prev; + prev->next = t; + } + rowcalctabs(row); /* set t->x, t->w, etc */ + if (t->title == None) { + t->title = XCreateWindow(dpy, c->frame, col->x + t->x, row->y, t->w, visual.tab, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &clientswa); + XMapWindow(dpy, t->title); + } else { + XReparentWindow(dpy, t->title, c->frame, col->x + t->x, row->y); + } + XReparentWindow(dpy, t->frame, c->frame, col->x, row->y + visual.tab); + XMapWindow(dpy, t->frame); + if (oldrow != NULL) { /* deal with the row this tab came from */ + if (oldrow->ntabs == 0) { + rowdel(oldrow); + } else { + rowcalctabs(oldrow); + } + } +} + +/* check if desktop is visible */ +static int +deskisvisible(struct Desktop *desk) +{ + return desk->mon->seldesk == desk; +} + +/* change desktop */ +static void +deskfocus(struct Desktop *desk) +{ + void tabfocus(struct Tab *t, int gotodesk); + struct Container *c; + int cursorx, cursory; + Window da, db; /* dummy variables */ + int dx, dy; /* dummy variables */ + unsigned int du; /* dummy variable */ + + if (desk == NULL || desk == wm.selmon->seldesk) + return; + if (!deskisvisible(desk)) { + /* unhide cointainers of new current desktop + * hide containers of previous current desktop */ + for (c = wm.c; c != NULL; c = c->next) { + if (c->desk == desk) { + containermoveresize(c); + containerhide(c, 0); + } else if (c->desk == desk->mon->seldesk) { + containerhide(c, 1); + } + } + } + + /* if changing focus to a new monitor and the cursor isn't there, warp it */ + XQueryPointer(dpy, root, &da, &db, &cursorx, &cursory, &dx, &dy, &du); + if (desk->mon != wm.selmon && desk->mon != getmon(cursorx, cursory)) { + XWarpPointer(dpy, None, root, 0, 0, 0, 0, desk->mon->mx + desk->mon->mw / 2, + desk->mon->my + desk->mon->mh / 2); + } + + /* update current desktop */ + wm.selmon = desk->mon; + wm.selmon->seldesk = desk; + if (wm.showingdesk) { + wm.showingdesk = 0; + ewmhsetshowingdesktop(1); + } + ewmhsetcurrentdesktop(desk->n); + + /* focus client on the new current desktop */ + c = getnextfocused(desk); + if (c != NULL) { + tabfocus(c->selcol->selrow->seltab, 0); + } else { + tabfocus(NULL, 0); + } +} + +/* create new tab */ +static struct Tab * +tabnew(Window win, int ignoreunmap) +{ + struct Tab *t; + + t = emalloc(sizeof(*t)); + t->prev = t->next = NULL; + t->row = NULL; + t->ds = NULL; + t->name = NULL; + t->class = NULL; + t->ignoreunmap = ignoreunmap; + t->isurgent = 0; + t->winw = t->winh = 0; + t->x = t->w = 0; + t->pw = 0; + t->pix = None; + t->pixtitle = None; + t->title = None; + t->win = win; + t->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &clientswa); + XReparentWindow(dpy, t->win, t->frame, 0, 0); + XMapWindow(dpy, t->win); + icccmwmstate(win, NormalState); + return t; +} + +/* clear window urgency */ +static void +tabclearurgency(struct Tab *t) +{ + XWMHints wmh = {0}; + + XSetWMHints(dpy, t->win, &wmh); + t->isurgent = 0; +} + +/* update tab urgency */ +static void +tabupdateurgency(struct Tab *t, int isurgent) +{ + int prev; + + prev = t->isurgent; + t->isurgent = isurgent; + if (t->isurgent && t->row->col->c == wm.focused && t == t->row->seltab) { + tabclearurgency(t); + } + if (prev != t->isurgent) { + tabdecorate(t, 0); + } +} + +/* focus tab */ +void +tabfocus(struct Tab *t, int gotodesk) +{ + struct Container *c; + + wm.prevfocused = wm.focused; + if (t == NULL) { + wm.focused = NULL; + if (wm.prevfocused) + containerdecorate(wm.prevfocused, 1, 0); + XSetInputFocus(dpy, wm.focuswin, RevertToParent, CurrentTime); + ewmhsetactivewindow(None); + } else { + c = t->row->col->c; + if (wm.focused && wm.focused->selcol->selrow->seltab == t) + return; /* tab is already focused */ + if (!c->isfullscreen && getfullscreen(c->mon, c->desk) != NULL) + return; /* we should not focus a client below a fullscreen client */ + wm.focused = c; + t->row->seltab = t; + t->row->col->selrow = t->row; + t->row->col->c->selcol = t->row->col; + XRaiseWindow(dpy, t->frame); + if (t->ds) { + XRaiseWindow(dpy, t->ds->frame); + XSetInputFocus(dpy, t->ds->win, RevertToParent, CurrentTime); + ewmhsetactivewindow(t->ds->win); + } else { + XSetInputFocus(dpy, t->win, RevertToParent, CurrentTime); + ewmhsetactivewindow(t->win); + } + if (t->isurgent) + tabclearurgency(t); + if (wm.prevfocused) + containerdecorate(wm.prevfocused, 1, 0); + containeraddfocus(c); + containerdecorate(c, 1, 0); + containerminimize(c, 0, 0); + containerraise(c); + if (gotodesk) { + deskfocus(c->issticky ? c->mon->seldesk : c->desk); + } + } +} + +/* update tab title */ +static void +tabupdatetitle(struct Tab *t) +{ + free(t->name); + t->name = getwinname(t->win); +} + +/* update tab class */ +static void +tabupdateclass(struct Tab *t) +{ + XClassHint chint; + + if (XGetClassHint(dpy, t->win, &chint)) { + free(t->class); + t->class = (chint.res_class != NULL && chint.res_class[0] != '\0') + ? estrndup(chint.res_class, NAMEMAXLEN) + : NULL; + XFree(chint.res_class); + XFree(chint.res_name); + } +} + +/* add dialog window into tab */ +static void +tabadddialog(struct Tab *t, struct Dialog *d) +{ + struct Container *c; + + c = t->row->col->c; + d->t = t; + XReparentWindow(dpy, d->frame, t->frame, 0, 0); + if (t->ds) + t->ds->prev = d; + d->next = t->ds; + t->ds = d; + icccmwmstate(d->win, NormalState); + dialogcalcsize(d); + dialogmoveresize(d); + XMapRaised(dpy, d->frame); +} + +/* create new dialog */ +static struct Dialog * +dialognew(Window win, int maxw, int maxh, int ignoreunmap) +{ + struct Dialog *d; + + d = emalloc(sizeof(*d)); + d->prev = d->next = NULL; + d->t = NULL; + d->x = d->y = d->w = d->h = 0; + d->pix = None; + d->pw = d->ph = 0; + d->maxw = maxw; + d->maxh = maxh; + d->ignoreunmap = ignoreunmap; + d->win = win; + d->frame = XCreateWindow(dpy, root, 0, 0, maxw, maxh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &clientswa); + XReparentWindow(dpy, d->win, d->frame, 0, 0); + XMapWindow(dpy, d->win); + return d; +} + +/* check if monitor geometry is unique */ +static int +monisuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} + +/* add monitor */ +static void +monnew(XineramaScreenInfo *info) +{ + struct Monitor *mon; + int i; + + mon = emalloc(sizeof *mon); + mon->prev = NULL; + mon->next = NULL; + mon->mx = mon->wx = info->x_org; + mon->my = mon->wy = info->y_org; + mon->mw = mon->ww = info->width; + mon->mh = mon->wh = info->height; + mon->desks = ecalloc(config.ndesktops, sizeof(*mon->desks)); + for (i = 0; i < config.ndesktops; i++) { + mon->desks[i].mon = mon; + mon->desks[i].n = i; + } + mon->seldesk = &mon->desks[0]; + if (wm.montail != NULL) { + wm.montail->next = mon; + mon->prev = wm.montail; + } else { + wm.monhead = mon; + } + wm.montail = mon; +} + +/* delete monitor and set monitor of clients on it to NULL */ +static void +mondel(struct Monitor *mon) +{ + struct Container *c; + + if (mon->next) + mon->next->prev = mon->prev; + else + wm.montail = mon->prev; + if (mon->prev) + mon->prev->next = mon->next; + else + wm.monhead = mon->next; + for (c = wm.c; c; c = c->next) + if (c->mon == mon) + c->mon = NULL; + free(mon->desks); + free(mon); +} + +/* update the list of monitors */ +static void +monupdate(void) +{ + XineramaScreenInfo *info = NULL; + XineramaScreenInfo *unique = NULL; + struct Monitor *mon; + struct Monitor *tmp; + struct Container *c, *focus; + int delselmon = 0; + int del, add; + int i, j, n; + int moncount; + + info = XineramaQueryScreens(dpy, &n); + unique = ecalloc(n, sizeof *unique); + + /* only consider unique geometries as separate screens */ + for (i = 0, j = 0; i < n; i++) + if (monisuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof *unique); + XFree(info); + moncount = j; + + /* look for monitors that do not exist anymore and delete them */ + mon = wm.monhead; + while (mon) { + del = 1; + for (i = 0; i < moncount; i++) { + if (unique[i].x_org == mon->mx && unique[i].y_org == mon->my && + unique[i].width == mon->mw && unique[i].height == mon->mh) { + del = 0; + break; + } + } + tmp = mon; + mon = mon->next; + if (del) { + if (tmp == wm.selmon) + delselmon = 1; + mondel(tmp); + } + } + + /* look for new monitors and add them */ + for (i = 0; i < moncount; i++) { + add = 1; + for (mon = wm.monhead; mon; mon = mon->next) { + if (unique[i].x_org == mon->mx && unique[i].y_org == mon->my && + unique[i].width == mon->mw && unique[i].height == mon->mh) { + add = 0; + break; + } + } + if (add) { + monnew(&unique[i]); + } + } + if (delselmon) + wm.selmon = wm.monhead; + + /* update monitor number */ + for (i = 0, mon = wm.monhead; mon; mon = mon->next, i++) + mon->n = i; + + /* send containers which do not belong to a window to selected desktop */ + focus = NULL; + for (c = wm.c; c; c = c->next) { + if (!c->isminimized && c->mon == NULL) { + focus = c; + containersendtodesk(c, wm.selmon->seldesk, 1, 0); + containermoveresize(c); + } + } + if (focus != NULL) /* if a contained changed desktop, focus it */ + tabfocus(focus->selcol->selrow->seltab, 1); + + free(unique); +} + +/* select window input events, grab mouse button presses, and clear its border */ +static void +preparewin(Window win) +{ + XSelectInput(dpy, win, EnterWindowMask | StructureNotifyMask + | PropertyChangeMask | FocusChangeMask); + XGrabButton(dpy, AnyButton, AnyModifier, win, False, ButtonPressMask, + GrabModeSync, GrabModeSync, None, None); + XSetWindowBorderWidth(dpy, win, 0); +} + +/* check if event is related to the prompt or its frame */ +static Bool +promptvalidevent(Display *dpy, XEvent *ev, XPointer arg) +{ + struct Prompt *prompt; + + (void)dpy; + prompt = (struct Prompt *)arg; + switch(ev->type) { + case DestroyNotify: + if (ev->xdestroywindow.window == prompt->win) + return True; + break; + case UnmapNotify: + if (ev->xunmap.window == prompt->win) + return True; + break; + case ConfigureRequest: + if (ev->xconfigurerequest.window == prompt->win) + return True; + break; + case Expose: + case ButtonPress: + return True; + } + return False; +} + +/* calculate position and size of prompt window and the size of its frame */ +static void +promptcalcgeom(int *x, int *y, int *w, int *h, int *fw, int *fh) +{ + *w = min(*w, wm.selmon->ww - visual.border * 2); + *h = min(*h, wm.selmon->wh - visual.border); + *x = wm.selmon->wx + (wm.selmon->ww - *w) / 2 - visual.border; + *y = 0; + *fw = *w + visual.border * 2; + *fh = *h + visual.border; +} + +/* decorate prompt frame */ +static void +promptdecorate(Window frame, int w, int h) +{ + XGCValues val; + + val.fill_style = FillSolid; + val.foreground = visual.decor[FOCUSED][2].bg; + XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); + XFillRectangle(dpy, frame, gc, visual.border, visual.border, w, h); + + val.fill_style = FillTiled; + val.tile = visual.decor[FOCUSED][2].w; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, frame, gc, 0, 0, visual.border, h + visual.border); + + val.fill_style = FillTiled; + val.tile = visual.decor[FOCUSED][2].e; + val.ts_x_origin = visual.border + w; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, frame, gc, visual.border + w, 0, visual.border, h + visual.border); + + val.fill_style = FillTiled; + val.tile = visual.decor[FOCUSED][2].s; + val.ts_x_origin = 0; + val.ts_y_origin = visual.border + h; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, frame, gc, visual.border, h, w + 2 * visual.border, visual.border); + + XCopyArea(dpy, visual.decor[FOCUSED][2].sw, frame, gc, 0, 0, visual.corner, visual.corner, 0, h + visual.border - visual.corner); + XCopyArea(dpy, visual.decor[FOCUSED][2].se, frame, gc, 0, 0, visual.corner, visual.corner, w + 2 * visual.border - visual.corner, h + visual.border - visual.corner); +} + +/* create notification window */ +static void +notifnew(Window win, int w, int h) +{ + struct Notification *n; + + n = emalloc(sizeof(*n)); + n->w = w + 2 * visual.border; + n->h = h + 2 * visual.border; + n->pw = n->ph = 0; + n->prev = wm.ntail; + n->next = NULL; + if (wm.ntail != NULL) + wm.ntail->next = n; + else + wm.nhead = n; + wm.ntail = n; + n->pix = None; + n->win = win; + n->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, + &(XSetWindowAttributes){.event_mask = SubstructureNotifyMask | SubstructureRedirectMask}); + XReparentWindow(dpy, n->win, n->frame, 0, 0); + XMapWindow(dpy, n->win); +} + +/* decorate notification */ +static void +notifdecorate(struct Notification *n, int style) +{ + XGCValues val; + int w, h; + + if (n->pw != n->w || n->ph != n->h || n->pix == None) { + if (n->pix != None) + XFreePixmap(dpy, n->pix); + n->pix = XCreatePixmap(dpy, n->frame, n->w, n->h, depth); + } + n->pw = n->w; + n->ph = n->h; + + w = n->w - 2 * visual.border; + h = n->h - 2 * visual.border; + + val.fill_style = FillTiled; + val.tile = visual.decor[style][NOTIFICATION].w; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, n->pix, gc, 0, visual.border, visual.border, h); + + val.tile = visual.decor[style][NOTIFICATION].e; + val.ts_x_origin = visual.border + w; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, n->pix, gc, visual.border + w, visual.border, visual.border, h); + + val.tile = visual.decor[style][NOTIFICATION].n; + val.ts_x_origin = 0; + val.ts_y_origin = 0; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, n->pix, gc, visual.border, 0, w, visual.border); + + val.tile = visual.decor[style][NOTIFICATION].s; + val.ts_x_origin = 0; + val.ts_y_origin = visual.border + h; + XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); + XFillRectangle(dpy, n->pix, gc, visual.border, visual.border + h, w, visual.border); + + XCopyArea(dpy, visual.decor[style][NOTIFICATION].nw, n->pix, gc, 0, 0, visual.corner, visual.corner, 0, 0); + XCopyArea(dpy, visual.decor[style][NOTIFICATION].ne, n->pix, gc, 0, 0, visual.corner, visual.corner, n->w - visual.corner, 0); + XCopyArea(dpy, visual.decor[style][NOTIFICATION].sw, n->pix, gc, 0, 0, visual.corner, visual.corner, 0, n->h - visual.corner); + XCopyArea(dpy, visual.decor[style][NOTIFICATION].se, n->pix, gc, 0, 0, visual.corner, visual.corner, n->w - visual.corner, n->h - visual.corner); + + val.fill_style = FillSolid; + val.foreground = visual.decor[style][NOTIFICATION].bg; + XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); + XFillRectangle(dpy, n->pix, gc, visual.border, visual.border, w, h); + + XCopyArea(dpy, n->pix, n->frame, gc, 0, 0, n->w, n->h, 0, 0); +} + +/* place notifications */ +static void +notifplace(void) +{ + struct Notification *n; + int x, y, h; + + h = 0; + for (n = wm.nhead; n; n = n->next) { + x = wm.monhead->wx; + y = wm.monhead->wy; + switch (config.gravity) { + case NorthWestGravity: + break; + case NorthGravity: + x += (wm.monhead->ww - n->w) / 2; + break; + case NorthEastGravity: + x += wm.monhead->ww - n->w; + break; + case WestGravity: + y += (wm.monhead->wh - n->h) / 2; + break; + case CenterGravity: + x += (wm.monhead->ww - n->w) / 2; + y += (wm.monhead->wh - n->h) / 2; + break; + case EastGravity: + x += wm.monhead->ww - n->w; + y += (wm.monhead->wh - n->h) / 2; + break; + case SouthWestGravity: + y += wm.monhead->wh - n->h; + break; + case SouthGravity: + x += (wm.monhead->ww - n->w) / 2; + y += wm.monhead->wh - n->h; + break; + case SouthEastGravity: + x += wm.monhead->ww - n->w; + y += wm.monhead->wh - n->h; + break; + } + + if (config.direction == DOWNWARDS) + y += h; + else + y -= h; + h += n->h + config.notifgap + visual.border * 2; + + XMoveResizeWindow(dpy, n->frame, x, y, n->w, n->h); + XMoveResizeWindow(dpy, n->win, visual.border, visual.border, n->w - 2 * visual.border, n->h - 2 * visual.border); + XMapWindow(dpy, n->frame); + winnotify(n->win, x + visual.border, y + visual.border, n->w - 2 * visual.border, n->h - 2 * visual.border); + if (n->pw != n->w || n->ph != n->h) { + notifdecorate(n, FOCUSED); + } + } +} + +/* delete notification */ +static void +notifdel(struct Notification *n) +{ + if (n->next) + n->next->prev = n->prev; + else + wm.ntail = n->prev; + if (n->prev) + n->prev->next = n->next; + else + wm.nhead = n->next; + if (n->pix != None) + XFreePixmap(dpy, n->pix); + XDestroyWindow(dpy, n->frame); + free(n); + notifplace(); +} + +/* call the proper decorate function */ +static void +decorate(struct Winres *res) +{ + int fullw, fullh; + + if (res->n) { + XCopyArea(dpy, res->n->pix, res->n->frame, gc, 0, 0, res->n->w, res->n->h, 0, 0); + } else if (res->d != NULL) { + fullw = res->d->w + 2 * visual.border; + fullh = res->d->h + 2 * visual.border; + XCopyArea(dpy, res->d->pix, res->d->frame, gc, 0, 0, fullw, fullh, 0, 0); + } else if (res->t != NULL) { + XCopyArea(dpy, res->t->pixtitle, res->t->title, gc, 0, 0, res->t->w, visual.tab, 0, 0); + XCopyArea(dpy, res->t->pix, res->t->frame, gc, 0, 0, res->t->winw, res->t->winh, 0, 0); + } else if (res->row != NULL) { + XCopyArea(dpy, res->row->pixbl, res->row->bl, gc, 0, 0, visual.button, visual.button, 0, 0); + XCopyArea(dpy, res->row->pixbr, res->row->br, gc, 0, 0, visual.button, visual.button, 0, 0); + } else if (res->c != NULL) { + fullw = res->c->w; + fullh = res->c->h; + XCopyArea(dpy, res->c->pix, res->c->frame, gc, 0, 0, fullw, fullh, 0, 0); + } +} + +/* map prompt, give it focus, wait for it to close, then revert focus to previously focused window */ +static void +manageprompt(Window win, int w, int h) +{ + struct Prompt prompt; + struct Winres res; + XEvent ev; + int x, y, fw, fh; + + promptcalcgeom(&x, &y, &w, &h, &fw, &fh); + prompt.frame = XCreateWindow(dpy, root, x, y, fw, fh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &clientswa); + XReparentWindow(dpy, win, prompt.frame, visual.border, 0); + XMapWindow(dpy, win); + XMapWindow(dpy, prompt.frame); + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + prompt.win = win; + while (!XIfEvent(dpy, &ev, promptvalidevent, (XPointer)&prompt)) { + switch (ev.type) { + case Expose: + if (ev.xexpose.count == 0) { + if (ev.xexpose.window == prompt.frame) { + promptdecorate(prompt.frame, w, h); + } else { + res = getwin(ev.xexpose.window); + decorate(&res); + } + } + break; + case DestroyNotify: + case UnmapNotify: + goto done; + break; + case ConfigureRequest: + w = ev.xconfigurerequest.width; + h = ev.xconfigurerequest.height; + promptcalcgeom(&x, &y, &w, &h, &fw, &fh); + XMoveResizeWindow(dpy, prompt.frame, x, y, fw, fh); + XMoveResizeWindow(dpy, win, visual.border, 0, w, h); + break; + case ButtonPress: + if (ev.xbutton.window != win && ev.xbutton.window != prompt.frame) + windowclose(win); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + break; + } + } +done: + XReparentWindow(dpy, win, root, 0, 0); + XDestroyWindow(dpy, prompt.frame); + if (wm.focused) { + tabfocus(wm.focused->selcol->selrow->seltab, 0); + } else { + tabfocus(NULL, 0); + } +} + +/* map desktop window */ +static void +managedesktop(Window win) +{ + Window wins[2] = {win, wm.layerwins[LAYER_DESKTOP]}; + + XRestackWindows(dpy, wins, sizeof wins); + XMapWindow(dpy, win); +} + +/* add notification window into notification queue; and update notification placement */ +static void +managenotif(Window win, int w, int h) +{ + notifnew(win, w, h); + notifplace(); +} + +/* call one of the manage- functions */ +static void +manage(Window win, XWindowAttributes *wa, int ignoreunmap) +{ + struct Winres res; + struct Tab *t; + struct Row *row; + struct Column *col; + struct Container *c; + struct Dialog *d; + Atom prop; + int userplaced; + + res = getwin(win); + if (res.c != NULL) + return; + preparewin(win); + prop = getatomprop(win, atoms[_NET_WM_WINDOW_TYPE]); + t = getdialogfor(win); + if (prop == atoms[_NET_WM_WINDOW_TYPE_DESKTOP]) { + managedesktop(win); + } else if (prop == atoms[_NET_WM_WINDOW_TYPE_NOTIFICATION]) { + managenotif(win, wa->width, wa->height); + } else if (prop == atoms[_NET_WM_WINDOW_TYPE_PROMPT]) { + manageprompt(win, wa->width, wa->height); + } else if (t != NULL) { + wm.nclients++; + d = dialognew(win, wa->width, wa->height, ignoreunmap); + tabadddialog(t, d); + ewmhsetclients(); + ewmhsetclientsstacking(); + } else { + wm.nclients++; + userplaced = isuserplaced(win); + t = tabnew(win, ignoreunmap); + tabupdatetitle(t); + tabupdateclass(t); + row = rownew(); + col = colnew(); + c = containernew(wa->x, wa->y, wa->width, wa->height); + containeraddcol(c, col, 0); + coladdrow(col, row, 0); + rowaddtab(row, t, 0); + containersendtodesk(c, wm.selmon->seldesk, 1, userplaced); + tabfocus(t, 0); + containermoveresize(c); + containerhide(c, 0); + XMapSubwindows(dpy, c->frame); + XMapWindow(dpy, c->frame); + ewmhsetclients(); + ewmhsetclientsstacking(); + } +} + +/* unmanage tab (and delete its row if it is the only tab) */ +static void +unmanage(struct Tab *t) +{ + struct Container *c, *focus; + struct Column *col; + struct Row *row; + struct Desktop *desk; + int moveresize; + + row = t->row; + col = row->col; + c = col->c; + desk = c->desk; + moveresize = 1; + focus = c; + tabdel(t); + if (row->ntabs == 0) { + rowdel(row); + if (col->nrows == 0) { + coldel(col); + if (c->ncols == 0) { + containerdel(c); + focus = getnextfocused(desk); + moveresize = 0; + } + } + } + if (moveresize) { + containermoveresize(c); + } + if (focus != NULL) { + tabfocus(focus->selcol->selrow->seltab, 0); + } +} + +/* scan for already existing windows and adopt them */ +static void +scan(void) +{ + unsigned int i, num; + Window d1, d2, transwin, *wins = NULL; + XWindowAttributes wa; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) { + manage(wins[i], &wa, IGNOREUNMAP); + } + } + for (i = 0; i < num; i++) { /* now the transients */ + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (XGetTransientForHint(dpy, wins[i], &transwin) && + (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) { + manage(wins[i], &wa, IGNOREUNMAP); + } + } + if (wins != NULL) { + XFree(wins); + } + } +} + +/* map and hide focus window */ +static void +mapfocuswin(void) +{ + XMoveWindow(dpy, wm.focuswin, -1, 0); + XMapWindow(dpy, wm.focuswin); +} + +/* draw outline while resizing */ +static void +outlinedraw(int x, int y, int w, int h) +{ + static int oldx, oldy, oldw, oldh; + XGCValues val; + XRectangle rects[4]; + + val.function = GXinvert; + val.subwindow_mode = IncludeInferiors; + val.foreground = 1; + val.fill_style = FillSolid; + XChangeGC(dpy, gc, GCFunction | GCSubwindowMode | GCForeground | GCFillStyle, &val); + if (oldw != 0 && oldh != 0) { + rects[0].x = oldx + 1; + rects[0].y = oldy; + rects[0].width = oldw - 2; + rects[0].height = 1; + rects[1].x = oldx; + rects[1].y = oldy; + rects[1].width = 1; + rects[1].height = oldh; + rects[2].x = oldx + 1; + rects[2].y = oldy + oldh - 1; + rects[2].width = oldw - 2; + rects[2].height = 1; + rects[3].x = oldx + oldw - 1; + rects[3].y = oldy; + rects[3].width = 1; + rects[3].height = oldh; + XFillRectangles(dpy, root, gc, rects, 4); + } + if (w != 0 && h != 0) { + rects[0].x = x + 1; + rects[0].y = y; + rects[0].width = w - 2; + rects[0].height = 1; + rects[1].x = x; + rects[1].y = y; + rects[1].width = 1; + rects[1].height = h; + rects[2].x = x + 1; + rects[2].y = y + h - 1; + rects[2].width = w - 2; + rects[2].height = 1; + rects[3].x = x + w - 1; + rects[3].y = y; + rects[3].width = 1; + rects[3].height = h; + XFillRectangles(dpy, root, gc, rects, 4); + } + oldx = x; + oldy = y; + oldw = w; + oldh = h; + val.function = GXcopy; + val.subwindow_mode = ClipByChildren; + XChangeGC(dpy, gc, GCFunction | GCSubwindowMode, &val); +} + +/* resize container with mouse */ +static void +mouseresize(struct Container *c, int xroot, int yroot, enum Octant o) +{ + struct Winres res; + Cursor curs; + XEvent ev; + int x, y, dx, dy; + + switch (o) { + case NW: + curs = visual.cursors[CURSOR_NW]; + break; + case NE: + curs = visual.cursors[CURSOR_NE]; + break; + case SW: + curs = visual.cursors[CURSOR_SW]; + break; + case SE: + curs = visual.cursors[CURSOR_SE]; + break; + case N: + curs = visual.cursors[CURSOR_N]; + break; + case S: + curs = visual.cursors[CURSOR_S]; + break; + case W: + curs = visual.cursors[CURSOR_W]; + break; + case E: + curs = visual.cursors[CURSOR_E]; + break; + default: + curs = None; + break; + } + if (o & W) + x = xroot - c->x - c->b; + else if (o & E) + x = c->x + c->w - c->b - xroot; + else + x = 0; + if (o & N) + y = yroot - c->y - c->b; + else if (o & S) + y = c->y + c->h - c->b - yroot; + else + y = 0; + XGrabPointer(dpy, c->frame, False, + ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, curs, CurrentTime); + while (!XMaskEvent(dpy, ButtonReleaseMask | PointerMotionMask | ExposureMask, &ev)) { + switch (ev.type) { + case Expose: + if (ev.xexpose.count == 0) { + res = getwin(ev.xexpose.window); + decorate(&res); + } + break; + case ButtonRelease: + goto done; + break; + case MotionNotify: + if (x > c->w) + x = 0; + if (y > c->h) + y = 0; + if (o & W && + ((ev.xmotion.x_root < xroot && x > ev.xmotion.x_root - c->nx) || + (ev.xmotion.x_root > xroot && x < ev.xmotion.x_root - c->nx))) { + dx = xroot - ev.xmotion.x_root; + if (c->nw + dx >= visual.center + 2 * c->b) { + c->nx -= dx; + c->nw += dx; + } + } else if (o & E && + ((ev.xmotion.x_root > xroot && x > c->nx + c->nw - ev.xmotion.x_root) || + (ev.xmotion.x_root < xroot && x < c->nx + c->nw - ev.xmotion.x_root))) { + dx = ev.xmotion.x_root - xroot; + if (c->nw + dx >= visual.center + 2 * c->b) { + c->nw += dx; + } + } + if (o & N && + ((ev.xmotion.y_root < yroot && y > ev.xmotion.y_root - c->ny) || + (ev.xmotion.y_root > yroot && y < ev.xmotion.y_root - c->ny))) { + dy = yroot - ev.xmotion.y_root; + if (c->nh + dy >= visual.center + 2 * c->b) { + c->ny -= dy; + c->nh += dy; + } + } else if (o & S && + ((ev.xmotion.y_root > yroot && c->ny + c->nh - ev.xmotion.y_root < y) || + (ev.xmotion.y_root < yroot && c->ny + c->nh - ev.xmotion.y_root > y))) { + dy = ev.xmotion.y_root - yroot; + if (c->nh + dy >= visual.center + 2 * c->b) { + c->nh += dy; + } + } + outlinedraw(c->nx, c->ny, c->nw, c->nh); + xroot = ev.xmotion.x_root; + yroot = ev.xmotion.y_root; + break; + } + } +done: + outlinedraw(0, 0, 0, 0); + containercalccols(c, 1); + containermoveresize(c); + XUngrabPointer(dpy, CurrentTime); +} + +/* move container with mouse */ +static void +mousemove(struct Container *c, int xroot, int yroot) +{ + struct Winres res; + XEvent ev; + int x, y; + + x = y = 0; + XGrabPointer(dpy, c->frame, False, + ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, visual.cursors[CURSOR_MOVE], CurrentTime); + while (!XMaskEvent(dpy, ButtonReleaseMask | PointerMotionMask | ExposureMask, &ev)) { + switch (ev.type) { + case Expose: + if (ev.xexpose.count == 0) { + res = getwin(ev.xexpose.window); + decorate(&res); + } + break; + case ButtonRelease: + goto done; + break; + case MotionNotify: + x = ev.xmotion.x_root - xroot; + y = ev.xmotion.y_root - yroot; + containerincrmove(c, x, y); + xroot = ev.xmotion.x_root; + yroot = ev.xmotion.y_root; + break; + } + } +done: + XUngrabPointer(dpy, CurrentTime); +} + +/* press button with mouse */ +static void +mousebutton(struct Row *row, int b) +{ + struct Winres res; + Window win; + XEvent ev; + + win = (b == BUTTON_RIGHT) ? row->br : row->bl; + XGrabPointer(dpy, win, False, ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + while (!XMaskEvent(dpy, ButtonReleaseMask | ExposureMask, &ev)) { + switch(ev.type) { + case Expose: + if (ev.xexpose.count == 0) { + res = getwin(ev.xexpose.window); + decorate(&res); + } + break; + case ButtonRelease: + // TODO + goto done; + } + } +done: + XUngrabPointer(dpy, CurrentTime); +} + +/* handle mouse operation, focusing and raising */ +static void +xeventbuttonpress(XEvent *e) +{ + struct Winres res; + struct Monitor *mon; + struct Container *c; + struct Tab *t; + enum Octant o; + XButtonPressedEvent *ev; + + ev = &e->xbutton; + res = getwin(ev->window); + + /* if user clicked in no window, focus the monitor below cursor */ + c = res.c; + if (c == NULL) { + mon = getmon(ev->x_root, ev->y_root); + if (mon) + deskfocus(mon->seldesk); + goto done; + } + + if (res.t != NULL) { + t = res.t; + } else if (res.d != NULL) { + t = res.d->t; + } else if (res.row != NULL) { + t = res.row->seltab; + } else { + t = c->selcol->selrow->seltab; + } + if (t == NULL) { + goto done; + } + + //octant = frameoctant(c, ev->window, ev->x, ev->y); + + /* focus client */ + if ((wm.focused == NULL || t != wm.focused->selcol->selrow->seltab) && + ((ev->window == t->title && ev->button == Button1) || + (ev->button == Button1 && config.focusbuttons & 1 << 0) || + (ev->button == Button2 && config.focusbuttons & 1 << 1) || + (ev->button == Button3 && config.focusbuttons & 1 << 2) || + (ev->button == Button4 && config.focusbuttons & 1 << 3) || + (ev->button == Button5 && config.focusbuttons & 1 << 4))) + tabfocus(t, 1); + + /* raise client */ + if ((c != wm.abovelist || c != wm.centerlist || c != wm.belowlist) && + ((ev->button == Button1 && config.raisebuttons & 1 << 0) || + (ev->button == Button2 && config.raisebuttons & 1 << 1) || + (ev->button == Button3 && config.raisebuttons & 1 << 2) || + (ev->button == Button4 && config.raisebuttons & 1 << 3) || + (ev->button == Button5 && config.raisebuttons & 1 << 4))) + containerraise(c); + + /* do action performed by mouse on non-maximized windows */ + if (ev->window == t->title && ev->button == Button3) { + // TODO: mouseretab + } else if (res.row != NULL && ev->window == res.row->bl && ev->button == Button1) { + buttondecorate(res.row, BUTTON_LEFT, 1); + mousebutton(res.row, BUTTON_LEFT); + buttondecorate(res.row, BUTTON_LEFT, 0); + } else if (res.row != NULL && ev->window == res.row->br && ev->button == Button1) { + buttondecorate(res.row, BUTTON_RIGHT, 1); + mousebutton(res.row, BUTTON_RIGHT); + buttondecorate(res.row, BUTTON_RIGHT, 0); + } else if (!c->isfullscreen && !c->isminimized && !c->ismaximized) { + o = getoctant(c, ev->window, ev->x, ev->y); + if (ev->state == config.modifier && ev->button == Button1) { + mousemove(c, ev->x_root, ev->y_root); + } else if (ev->window == c->frame && ev->button == Button3) { + containerdecorate(c, 0, o); + mousemove(c, ev->x_root, ev->y_root); + containerdecorate(c, 0, 0); + } else if ((ev->state == config.modifier && ev->button == Button3) || + (ev->window == c->frame && ev->button == Button1)) { + containerdecorate(c, 0, o); + mouseresize(c, ev->x_root, ev->y_root, o); + containerdecorate(c, 0, 0); + } else if (ev->window == t->title && ev->button == Button1) { + tabdecorate(t, 1); + mousemove(c, ev->x_root, ev->y_root); + tabdecorate(t, 0); + } + } + +done: + XAllowEvents(dpy, ReplayPointer, CurrentTime); +} + +/* handle client message event */ +static void +xeventclientmessage(XEvent *e) +{ + (void)e; + // TODO +} + +/* handle configure notify event */ +static void +xeventconfigurenotify(XEvent *e) +{ + XConfigureEvent *ev = &e->xconfigure; + + if (ev->window == root) { + screenw = ev->width; + screenh = ev->height; + monupdate(); + notifplace(); + } +} + +/* handle configure request event */ +static void +xeventconfigurerequest(XEvent *e) +{ + XConfigureRequestEvent *ev; + XWindowChanges wc; + struct Winres res; + + ev = &e->xconfigurerequest; + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + res = getwin(ev->window); + if (res.d != NULL) { + dialogconfigure(res.d, ev->value_mask, &wc); + } else if (res.c != NULL) { + containerconfigure(res.c, ev->value_mask, &wc); + } else if (res.c == NULL){ + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } +} + +/* forget about client */ +static void +xeventdestroynotify(XEvent *e) +{ + XDestroyWindowEvent *ev; + struct Winres res; + + ev = &e->xdestroywindow; + res = getwin(ev->window); + if (res.n && ev->window == res.n->win) { + notifdel(res.n); + return; + } else if (res.d && ev->window == res.d->win) { + dialogdel(res.d); + } else if (res.t && ev->window == res.t->win) { + unmanage(res.t); + } + ewmhsetclients(); + ewmhsetclientsstacking(); +} + +/* focus window when cursor enter it (only if there is no focus button) */ +static void +xevententernotify(XEvent *e) +{ + struct Winres res; + + if (config.focusbuttons) + return; + while (XCheckTypedEvent(dpy, EnterNotify, e)) + ; + res = getwin(e->xcrossing.window); + if (res.t != NULL) { + tabfocus(res.t, 1); + } +} + +/* redraw window decoration */ +static void +xeventexpose(XEvent *e) +{ + XExposeEvent *ev; + struct Winres res; + + ev = &e->xexpose; + if (ev->count == 0) { + res = getwin(ev->window); + decorate(&res); + } +} + +/* handle focusin event */ +static void +xeventfocusin(XEvent *e) +{ + XFocusChangeEvent *ev; + struct Winres res; + + ev = &e->xfocus; + res = getwin(ev->window); + if (wm.focused == NULL) { + tabfocus(NULL, 0); + } else if (wm.focused != res.c) { + tabfocus(wm.focused->selcol->selrow->seltab, 1); + } +} + +/* manage window */ +static void +xeventmaprequest(XEvent *e) +{ + XMapRequestEvent *ev; + XWindowAttributes wa; + + ev = &e->xmaprequest; + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) + return; + manage(ev->window, &wa, 0); +} + +/* change mouse cursor */ +static void +xeventmotionnotify(XEvent *e) +{ + XMotionEvent *ev; + struct Container *c; + struct Winres res; + enum Octant o; + + ev = &e->xmotion; + res = getwin(ev->window); + if (res.c == NULL || ev->subwindow != res.c->curswin) + return; + c = res.c; + o = getoctant(c, ev->window, ev->x, ev->y); + switch (o) { + case NW: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_NW]); + break; + case NE: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_NE]); + break; + case SW: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_SW]); + break; + case SE: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_SE]); + break; + case N: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_N]); + break; + case S: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_S]); + break; + case W: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_W]); + break; + case E: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_E]); + break; + default: + XDefineCursor(dpy, c->curswin, visual.cursors[CURSOR_NORMAL]); + break; + } +} + +/* update client properties */ +static void +xeventpropertynotify(XEvent *e) +{ + XPropertyEvent *ev; + struct Winres res; + + ev = &e->xproperty; + if (ev->state == PropertyDelete) + return; + res = getwin(ev->window); + if (res.t == NULL || ev->window != res.t->win) + return; + if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) { + tabupdatetitle(res.t); + tabdecorate(res.t, 0); + } else if (ev->atom == XA_WM_CLASS) { + tabupdateclass(res.t); + } else if (ev->atom == XA_WM_HINTS) { + tabupdateurgency(res.t, winisurgent(res.t->win)); + } +} + +/* forget about client */ +static void +xeventunmapnotify(XEvent *e) +{ + XUnmapEvent *ev; + struct Winres res; + + ev = &e->xunmap; + res = getwin(ev->window); + if (res.n && ev->window == res.n->win) { + notifdel(res.n); + return; + } else if (res.d && ev->window == res.d->win) { + if (res.d->ignoreunmap) { + res.d->ignoreunmap--; + return; + } else { + dialogdel(res.d); + } + } else if (res.t && ev->window == res.t->win) { + if (res.t->ignoreunmap) { + res.t->ignoreunmap--; + return; + } else { + unmanage(res.t); + } + } + ewmhsetclients(); + ewmhsetclientsstacking(); +} + +/* destroy dummy windows */ +static void +cleandummywindows(void) +{ + int i; + + XDestroyWindow(dpy, wm.wmcheckwin); + XDestroyWindow(dpy, wm.focuswin); + for (i = 0; i < LAYER_LAST; i++) { + XDestroyWindow(dpy, wm.layerwins[i]); + } +} + +/* free cursors */ +static void +cleancursors(void) +{ + size_t i; + + for (i = 0; i < CURSOR_LAST; i++) { + XFreeCursor(dpy, visual.cursors[i]); + } +} + +/* clean clients */ +static void +cleancontainers(void) +{ + while (wm.c) { + containerdel(wm.c); + } +} + +/* clean monitors */ +static void +cleanmonitors(void) +{ + while (wm.monhead) { + mondel(wm.monhead); + } +} + +/* free pixmaps */ +static void +cleanpixmaps(void) +{ + int i, j; + + for (i = 0; i < STYLE_LAST; i++) { + for (j = 0; i < DECOR_LAST; i++) { + XFreePixmap(dpy, visual.decor[i][j].bl); + XFreePixmap(dpy, visual.decor[i][j].br); + XFreePixmap(dpy, visual.decor[i][j].tl); + XFreePixmap(dpy, visual.decor[i][j].t); + XFreePixmap(dpy, visual.decor[i][j].tr); + XFreePixmap(dpy, visual.decor[i][j].nw); + XFreePixmap(dpy, visual.decor[i][j].nf); + XFreePixmap(dpy, visual.decor[i][j].n); + XFreePixmap(dpy, visual.decor[i][j].nl); + XFreePixmap(dpy, visual.decor[i][j].ne); + XFreePixmap(dpy, visual.decor[i][j].wf); + XFreePixmap(dpy, visual.decor[i][j].w); + XFreePixmap(dpy, visual.decor[i][j].wl); + XFreePixmap(dpy, visual.decor[i][j].ef); + XFreePixmap(dpy, visual.decor[i][j].e); + XFreePixmap(dpy, visual.decor[i][j].el); + XFreePixmap(dpy, visual.decor[i][j].sw); + XFreePixmap(dpy, visual.decor[i][j].sf); + XFreePixmap(dpy, visual.decor[i][j].s); + XFreePixmap(dpy, visual.decor[i][j].sl); + XFreePixmap(dpy, visual.decor[i][j].se); + } + } +} + +/* free fontset */ +static void +cleanfontset(void) +{ + XFreeFontSet(dpy, visual.fontset); +} + +/* shod window manager */ +int +main(void) +{ + XEvent ev; + void (*xevents[LASTEvent])(XEvent *) = { + [ButtonPress] = xeventbuttonpress, + [ClientMessage] = xeventclientmessage, + [ConfigureNotify] = xeventconfigurenotify, + [ConfigureRequest] = xeventconfigurerequest, + [DestroyNotify] = xeventdestroynotify, + [EnterNotify] = xevententernotify, + [Expose] = xeventexpose, + [FocusIn] = xeventfocusin, + [MapRequest] = xeventmaprequest, + [MotionNotify] = xeventmotionnotify, + [PropertyNotify] = xeventpropertynotify, + [UnmapNotify] = xeventunmapnotify + }; + + /* open connection to server and set X variables */ + if (!setlocale(LC_ALL, "") || !XSupportsLocale()) + warnx("warning: no locale support"); + if ((dpy = XOpenDisplay(NULL)) == NULL) + errx(1, "could not open display"); + screen = DefaultScreen(dpy); + screenw = DisplayWidth(dpy, screen); + screenh = DisplayHeight(dpy, screen); + depth = DefaultDepth(dpy, screen); + root = RootWindow(dpy, screen); + gc = XCreateGC(dpy, root, 0, NULL); + xerrorxlib = XSetErrorHandler(xerror); + XrmInitialize(); + if ((xrm = XResourceManagerString(dpy)) != NULL) + xdb = XrmGetStringDatabase(xrm); + + /* initialize */ + initconfig(); + initsignal(); + initdummywindows(); + initfontset(); + initcursors(); + initatoms(); + initnotif(); + initroot(); + inittheme(); + + /* set up list of monitors */ + monupdate(); + wm.selmon = wm.monhead; + + /* initialize ewmh hints */ + ewmhinit(); + ewmhsetcurrentdesktop(0); + ewmhsetshowingdesktop(0); + ewmhsetclients(); + ewmhsetclientsstacking(); + ewmhsetactivewindow(None); + + /* scan windows */ + scan(); + mapfocuswin(); + + /* run main event loop */ + while (running && !XNextEvent(dpy, &ev)) + if (xevents[ev.type]) + (*xevents[ev.type])(&ev); + + /* clean up */ + cleandummywindows(); + cleancursors(); + cleancontainers(); + cleanmonitors(); + cleanpixmaps(); + cleanfontset(); + + /* clear ewmh hints */ + ewmhsetclients(); + ewmhsetclientsstacking(); + ewmhsetactivewindow(None); + + /* close connection to server */ + XUngrabPointer(dpy, CurrentTime); + XrmDestroyDatabase(xdb); + XCloseDisplay(dpy); + + return 0; +} diff --git a/theme.xpm b/theme.xpm @@ -0,0 +1,175 @@ +/* XPM */ +static char *theme[] = { +/* columns rows colors chars-per-pixel */ +"159 159 10 1 7 17", +" c #2E3436", +". c #555753", +"X c #A40000", +"o c #CC0000", +"O c #EF2929", +"+ c #204A87", +"@ c #3465A4", +"# c #729FCF", +"$ c #888A85", +"% c white", +/* pixels */ +"+++++++++++++++++++++++++++++++++++++++++++++++++++++#####################################################+++++++++++++++++++++++++++++++++++++++++++++++++++++", +"+######################+####+#######################+#++++++++++++++++++++++#++++#+++++++++++++++++++++++#+###################################################+", +"+#####################++###++######################++#+++++++++++++++++++++##+++##++++++++++++++++++++++##+##################################################++", +"+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", +"+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", +"+##@@+++++++++++++++++++#++++#++++++++++++++++++#@@++#++@@###################+####+##################+@@##+##@@+++++++++++++++++++++++++++++++++++++++++++#@@++", +"+##@@++++++++++++++++++++++++++++++++++++++++++##@@++#++@@##########################################++@@##+##@@++++++++++++++++++++++++++++++++++++++++++##@@++", +"+##@@++################+####+################++##@@++#++@@##++++++++++++++++#++++#++++++++++++++++##++@@##+##@@++################+####+################++##@@++", +"+##@@++###############++###++###############+++##@@++#++@@##+++++++++++++++##+++##+++++++++++++++###++@@##+##@@++###############++###++###############+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@###@@@@@##+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+++@@@@@++#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@###@@@@@##+@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@#@@#@@@#@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@+@@@+@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@#@@@#@@+@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@#@@@#@#@@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@@+@+@@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@@#@#@@@+@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@#@@@+@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@+@@@+@@@#@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@#@@@+@@+++##@@++", +"+##@@++##@@########+@@++##@++##@@@#@@@@@+@@@+++##@@++#++@@##++@@++++++++#@@##++@##++@@@+@@@@@#@@@###++@@##+##@@++##@@########+@@++##@++##@@@#@@@@@+@@@+++##@@++", +"+##@@++##@@#@@@@@@@+@@++##@++##@@@@#@@@+@@@@+++##@@++#++@@##++@@+@@@@@@@#@@##++@##++@@@@+@@@#@@@@###++@@##+##@@++##@@#@@@@@@@+@@++##@++##@@@@#@@@+@@@@+++##@@++", +"+##@@++##@@#++++++++@@++##@++##@@@#@@@@@+@@@+++##@@++#++@@##++@@+########@@##++@##++@@@+@@@@@#@@@###++@@##+##@@++##@@#++++++++@@++##@++##@@@#@@@@@+@@@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@+@@@+@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@+@@@#@@@#@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@+@@@+@@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@#@@@+@+@@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@@#@#@@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@@+@+@@@+@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@#@@+@@@+@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@#@@@#@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@+@@@+@@+@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@#++@@@@@+++@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+##@@@@@###@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#++@@@@@+++@+++##@@++", +"+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++", +"+#+++++#++++++++++++++++#++++#+++++++++++++++++#+++++#+#####+################+####+#################+#####+##@@++#++++++++++++++++#++++#+++++++++++++++++##@@++", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++#####################################################+##@@++++++++++++++++++++++++++++++++++++++++++##@@++", +"+#####+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+#####+#+++++#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+++++#+##@@++#######################################+##@@++", +"+####++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+####++#++++##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++++##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+#+++++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+#+++++#+#####%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+#####+##@@++#######################################+##@@++", +"+++++++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+++++++#######%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#######+##@@++#######################################+##@@++", +"+#####+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+#####+#+++++#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+++++#+##@@++#######################################+##@@++", +"+####++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+####++#++++##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++++##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", +"+##@@++++++++++++++++++++++++++++++++++++++++++##@@++#++@@##########################################++@@##+##@@++++++++++++++++++++++++++++++++++++++++++##@@++", +"+##@@+#################+####+####################@@++#++@@#+++++++++++++++++#++++#++++++++++++++++++++@@##+##@@+###########################################@@++", +"+##@@#################++###++####################@@++#++@@+++++++++++++++++##+++##++++++++++++++++++++@@##+##@@############################################@@++", +"+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", +"+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", +"+#++++++++++++++++++++++#++++#+++++++++++++++++++++++#+######################+####+#######################+#+++++++++++++++++++++++++++++++++++++++++++++++++++", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++#####################################################+++++++++++++++++++++++++++++++++++++++++++++++++++++", +" $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", +" $$$$$$$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$$$$$$ $ $ $ $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", +" $$$$$$$$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$$$$$$$$ $ $$ $$ $$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", +" $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", +" $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", +" $$.. $ $ $.. $ ..$$$$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$ ..$$ $$.. $.. ", +" $$.. $$.. $ ..$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ..$$ $$.. $$.. ", +" $$.. $$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$ $$.. $ ..$$ $ $ $$ ..$$ $$.. $$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$ $$.. ", +" $$.. $$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$ $$.. $ ..$$ $$ $$ $$$ ..$$ $$.. $$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$ $$.. ", +" $$.. $$............. $$. $$............. $$.. $ ..$$ .............$$ .$$ .............$$$ ..$$ $$.. $$............. $$. $$............. $$.. ", +" $$.. $$............. $$. $$.$$$.....$$ . $$.. $ ..$$ .............$$ .$$ . ..... $.$$$ ..$$ $$.. $$............. $$. $$.$$$.....$$ . $$.. ", +" $$.. $$............. $$. $$.$..$...$.. . $$.. $ ..$$ .............$$ .$$ . .. ... ..$.$$$ ..$$ $$.. $$............. $$. $$.$..$...$.. . $$.. ", +" $$.. $$............. $$. $$.$...$.$... . $$.. $ ..$$ .............$$ .$$ . ... . ...$.$$$ ..$$ $$.. $$............. $$. $$.$...$.$... . $$.. ", +" $$.. $$............. $$. $$..$...$... .. $$.. $ ..$$ .............$$ .$$ .. ... ...$..$$$ ..$$ $$.. $$............. $$. $$..$...$... .. $$.. ", +" $$.. $$..$$$$$$$$ .. $$. $$...$..... ... $$.. $ ..$$ .. $..$$ .$$ ... .....$...$$$ ..$$ $$.. $$..$$$$$$$$ .. $$. $$...$..... ... $$.. ", +" $$.. $$..$....... .. $$. $$....$... .... $$.. $ ..$$ .. .......$..$$ .$$ .... ...$....$$$ ..$$ $$.. $$..$....... .. $$. $$....$... .... $$.. ", +" $$.. $$..$ .. $$. $$...$..... ... $$.. $ ..$$ .. $$$$$$$$..$$ .$$ ... .....$...$$$ ..$$ $$.. $$..$ .. $$. $$...$..... ... $$.. ", +" $$.. $$............. $$. $$..$... ... .. $$.. $ ..$$ .............$$ .$$ .. ...$...$..$$$ ..$$ $$.. $$............. $$. $$..$... ... .. $$.. ", +" $$.. $$............. $$. $$.$... . ... . $$.. $ ..$$ .............$$ .$$ . ...$.$...$.$$$ ..$$ $$.. $$............. $$. $$.$... . ... . $$.. ", +" $$.. $$............. $$. $$.$.. ... .. . $$.. $ ..$$ .............$$ .$$ . ..$...$..$.$$$ ..$$ $$.. $$............. $$. $$.$.. ... .. . $$.. ", +" $$.. $$............. $$. $$. ..... . $$.. $ ..$$ .............$$ .$$ . $$.....$$$.$$$ ..$$ $$.. $$............. $$. $$. ..... . $$.. ", +" $$.. $$............. $$. $$............. $$.. $ ..$$ .............$$ .$$ .............$$$ ..$$ $$.. $$............. $$. $$............. $$.. ", +" $ $ $ $ $ $ $$$$$ $$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$ $$$$$ $$.. $ $ $ $$.. ", +" $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. $$.. ", +" $$$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$$ $ $%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$ $ $$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $ $ $$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $$$$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$$ $ $%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$ $ $$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", +" $$.. $$.. $ ..$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ..$$ $$.. $$.. ", +" $$.. $$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$$$.. $ ..$ $ $ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.. ", +" $$..$$$$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$$$$$$.. $ .. $$ $$ ..$$ $$..$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.. ", +" $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", +" $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", +" $ $ $ $ $$$$$$$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$$$$$$ $ ", +" $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", +"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", +"XOOOOOOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOOOOOOXOXXXXXXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXXXXXXOXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX", +"XOOOOOOOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOOOOOOOOXXOXXXXXXXXXXXXXXXXXXXXXOOXXXOOXXXXXXXXXXXXXXXXXXXXXXOOXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXX", +"XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", +"XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", +"XOOooXXXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXOooXXOXXooOOOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOXooOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOooXX", +"XOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXXOXXooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXooOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXX", +"XOOooXXOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOXXOOooXXOXXooOOXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXOOXXooOOXOOooXXOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOXXOOooXX", +"XOOooXXOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOXXXOOooXXOXXooOOXXXXXXXXXXXXXXXOOXXXOOXXXXXXXXXXXXXXXOOOXXooOOXOOooXXOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoooooooooooooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoOOOoooooOOXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXXXoooooXXOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOOOoooooOOXoXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoOooOoooOooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXooXoooXooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOooOoooOooXoXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoOoooOoOoooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXoooXoXoooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOoooOoOoooXoXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOooOoooOoooXooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXooXoooXoooOooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOooOoooOoooXooXXXOOooXX", +"XOOooXXOOooOOOOOOOOXooXXOOoXXOOoooOoooooXoooXXXOOooXXOXXooOOXXooXXXXXXXXOooOOXXoOOXXoooXoooooOoooOOOXXooOOXOOooXXOOooOOOOOOOOXooXXOOoXXOOoooOoooooXoooXXXOOooXX", +"XOOooXXOOooOoooooooXooXXOOoXXOOooooOoooXooooXXXOOooXXOXXooOOXXooXoooooooOooOOXXoOOXXooooXoooOooooOOOXXooOOXOOooXXOOooOoooooooXooXXOOoXXOOooooOoooXooooXXXOOooXX", +"XOOooXXOOooOXXXXXXXXooXXOOoXXOOoooOoooooXoooXXXOOooXXOXXooOOXXooXOOOOOOOOooOOXXoOOXXoooXoooooOoooOOOXXooOOXOOooXXOOooOXXXXXXXXooXXOOoXXOOoooOoooooXoooXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOooOoooXoooXooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXooXoooOoooOooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOooOoooXoooXooXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoOoooXoXoooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXoooOoOoooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOoooXoXoooXoXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoOooXoooXooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXooOoooOooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOooXoooXooXoXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoXXXoooooXXXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXOOoooooOOOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoXXXoooooXXXoXXXOOooXX", +"XOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoooooooooooooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXX", +"XOXXXXXOXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXOXXXXXOXOOOOOXOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOXOOOOOXOOooXXOXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXOOooXX", +"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXX", +"XOOOOOX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOOXOXXXXXO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXXOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOOOXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOXXOXXXXOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOXXXXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOXXXXXOXOOOOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXOOOOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XXXXXXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XXXXXXXOOOOOOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OOOOOOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOOOOX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOOXOXXXXXO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXXOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOOOXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOXXOXXXXOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", +"XOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXXOXXooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXooOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXX", +"XOOooXOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOOOooXXOXXooOXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXXXooOOXOOooXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooXX", +"XOOooOOOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOOOOOOooXXOXXooXXXXXXXXXXXXXXXXXOOXXXOOXXXXXXXXXXXXXXXXXXXXooOOXOOooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooXX", +"XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", +"XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", +"XOXXXXXXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXXXXXXOXOOOOOOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOOOOOOXOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", +"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +};