commit 4415c1b736c48e09387811d0042e2414a9d7653f
parent 19fd9059b377dae9c1266ab18c9bb502c7988dd8
Author: seninha <lucas@seninha.org>
Date: Sat, 10 Sep 2022 19:17:35 -0300
split code; refactor; etc
Diffstat:
M | Makefile | | | 37 | +++++++++++++++++++++++++------------ |
A | config.c | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | config.h | | | 63 | --------------------------------------------------------------- |
M | shod.c | | | 6431 | ++----------------------------------------------------------------------------- |
A | shod.h | | | 733 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | shodc.c | | | 181 | ++++++------------------------------------------------------------------------- |
A | xapp.c | | | 2106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xbar.c | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xdock.c | | | 195 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xdraw.c | | | 752 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xevents.c | | | 1627 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xhints.c | | | 295 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xmon.c | | | 257 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xnotif.c | | | 152 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xprompt.c | | | 126 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xsplash.c | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xutil.c | | | 255 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xutil.h | | | 100 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
18 files changed, 6944 insertions(+), 6554 deletions(-)
diff --git a/Makefile b/Makefile
@@ -7,29 +7,42 @@ X11INC ?= /usr/X11R6/include
X11LIB ?= /usr/X11R6/lib
# includes and libs
-INCS += -I${LOCALINC} -I${X11INC} -I/usr/include/freetype2 -I${X11INC}/freetype2
-LIBS += -L${LOCALLIB} -L${X11LIB} -lfontconfig -lXft -lX11 -lXinerama -lXrender
-
-# files
+XCPPFLAGS = -I${LOCALINC} -I${X11INC} -I/usr/include/freetype2 -I${X11INC}/freetype2
+XLDFLAGS = -L${LOCALLIB} -L${X11LIB} -lfontconfig -lXft -lX11 -lXinerama -lXrender
+
+SHOD_OBJS = shod.o config.o \
+ xapp.o xbar.o xdock.o xsplash.o xnotif.o xprompt.o \
+ xhints.o xmon.o xdraw.o xevents.o
+SHODC_OBJS = shodc.o
+SHARED_OBJS = xutil.o
PROGS = shod shodc
-SRCS = shod.c shodc.c config.h
+OBJS = ${SHOD_OBJS} ${SHODC_OBJS} ${SHARED_OBJS}
+INCS = shod.h xutil.h
+SRCS = ${OBJS:.o=.c} ${INCS}
all: ${PROGS}
-shod: shod.o
- ${CC} -o $@ shod.o ${LIBS} ${LDFLAGS}
+shod: ${SHOD_OBJS} ${SHARED_OBJS}
+ ${CC} -o $@ ${SHOD_OBJS} ${SHARED_OBJS} ${XLDFLAGS} ${LDFLAGS}
+
+shodc: ${SHODC_OBJS} ${SHARED_OBJS}
+ ${CC} -o $@ ${SHODC_OBJS} ${SHARED_OBJS} ${XLDFLAGS} ${LDFLAGS}
-shod.o: config.h
+${SHOD_OBJS}: shod.h xutil.h
-shodc: shodc.o
- ${CC} -o $@ shodc.o ${LIBS} ${LDFLAGS}
+${SHODC_OBJS}: xutil.h
+
+${SHARED_OBJS}: xutil.h
.c.o:
- ${CC} ${INCS} ${CFLAGS} ${CPPFLAGS} -c $<
+ ${CC} ${XCPPFLAGS} ${CFLAGS} ${CPPFLAGS} -c $<
tags: ${SRCS}
ctags ${SRCS}
+test: ${PROGS}
+ xinit ${XINITRC} -- `which Xephyr` :1 -screen 1024x768 +xinerama
+
install: all
mkdir -p ${DESTDIR}${PREFIX}/bin
mkdir -p ${DESTDIR}${MANPREFIX}/man1
@@ -43,6 +56,6 @@ uninstall:
rm -f ${DESTDIR}${MANPREFIX}/man1/shod.1
clean:
- rm -f ${PROGS} ${PROGS:=.o} ${PROGS:=.core} tags
+ rm -f ${PROGS} ${PROGS:=.core} ${OBJS} tags
.PHONY: all install uninstall clean
diff --git a/config.c b/config.c
@@ -0,0 +1,57 @@
+#include "shod.h"
+
+struct Config config = {
+ /*
+ * except for the foreground, colors fields are strings
+ * containing three elements delimited by colon:
+ * the body color, the color of the light 3D shadow,
+ * and the color of the dark 3D shadow.
+ */
+
+ /* 0-or-1 flags */
+ .floatdialog = 0, /* set to 1 to use floating dialog windows */
+ .sloppyfocus = 0, /* set to 1 to use sloppy focus */
+ .honorconfig = 0, /* set to 1 to honor configure requests */
+
+ /* general configuration */
+ .modifier = Mod1Mask, /* Modifier button */
+ .snap = 8, /* proximity of container edges to perform snap attraction */
+ .font = "monospace:pixelsize=11", /* font for titles in titlebars */
+ .ndesktops = 10, /* number of desktops per monitor */
+
+ /* dock configuration */
+ .dockwidth = 64, /* width of the dock (or its height, if it is horizontal) */
+ .dockspace = 64, /* size of each dockapp (64 for windowmaker dockapps) */
+ .dockgravity = "E", /* placement of the dock */
+ .dockcolors = {"#121212", "#2E3436"},
+
+ /* notification configuration */
+ .notifgap = 3, /* gap, in pixels, between notifications */
+ .notifgravity = "NE", /* placement of notifications */
+
+ /* title bar */
+ .titlewidth = 17,
+ .foreground = "#FFFFFF",
+
+ /* border */
+ .borderwidth = 6,
+ .bordercolors = {
+ [FOCUSED] = {"#3465A4", "#729FCF", "#204A87"},
+ [UNFOCUSED] = {"#555753", "#888A85", "#2E3436"},
+ [URGENT] = {"#CC0000", "#EF2929", "#A40000"},
+ },
+
+ /* size of 3D shadow effect, must be less than borderwidth */
+ .shadowthickness = 2,
+
+ /* the following are hardcoded rules; use X Resources to set rules without recompiling */
+ .rules = (struct Rule []){
+ /* class instance role type state bitmask */
+
+ /* Although Firefox's PictureInPicture is technically a utility (sub)window, make it a normal one */
+ { NULL, NULL, "PictureInPicture", TYPE_NORMAL, ABOVE },
+
+ /* Last rule must be all NULL! */
+ { NULL, NULL, NULL, TYPE_NORMAL, 0 },
+ }
+};
diff --git a/config.h b/config.h
@@ -1,63 +0,0 @@
-struct Config config = {
- /*
- * except for the foreground, colors fields are strings
- * containing three elements delimited by colon:
- * the body color, the color of the light 3D shadow,
- * and the color of the dark 3D shadow.
- */
-
- /* 0-or-1 flags */
- .floatdialog = 0, /* set to 1 to use floating dialog windows */
- .sloppyfocus = 0, /* set to 1 to use sloppy focus */
- .honorconfig = 0, /* set to 1 to honor configure requests */
-
- /* general configuration */
- .modifier = Mod1Mask, /* Modifier button */
- .snap = 8, /* proximity of container edges to perform snap attraction */
- .font = "monospace:pixelsize=11", /* font for titles in titlebars */
- .ndesktops = 10, /* number of desktops per monitor */
-
- /* dock configuration */
- .dockwidth = 64, /* width of the dock (or its height, if it is horizontal) */
- .dockspace = 64, /* size of each dockapp (64 for windowmaker dockapps) */
- .dockgravity = "E", /* placement of the dock */
- .dockcolors = {"#121212", "#2E3436", "#000000"},
-
- /* notification configuration */
- .notifgap = 3, /* gap, in pixels, between notifications */
- .notifgravity = "NE", /* placement of notifications */
- .notifcolors = {"#3465A4", "#729FCF", "#204A87"},
-
- /* prompt configuration */
- .promptcolors = {"#3465A4", "#729FCF", "#204A87"},
-
- /* title bar */
- .titlewidth = 17,
- .foreground = {
- [FOCUSED] = "#FFFFFF",
- [UNFOCUSED] = "#FFFFFF",
- [URGENT] = "#FFFFFF",
- },
-
- /* border */
- .borderwidth = 6,
- .bordercolors = {
- [FOCUSED] = {"#3465A4", "#729FCF", "#204A87"},
- [UNFOCUSED] = {"#555753", "#888A85", "#2E3436"},
- [URGENT] = {"#CC0000", "#EF2929", "#A40000"},
- },
-
- /* size of 3D shadow effect, must be less than borderwidth */
- .shadowthickness = 2,
-
- /* the following are hardcoded rules; use X Resources to set rules without recompiling */
- .rules = (struct Rule []){
- /* class instance role type state bitmask */
-
- /* Although Firefox's PictureInPicture is technically a utility (sub)window, make it a normal one */
- { NULL, NULL, "PictureInPicture", TYPE_NORMAL, ABOVE },
-
- /* Last rule must be all NULL! */
- { NULL, NULL, NULL, TYPE_NORMAL, 0 },
- }
-};
diff --git a/shod.c b/shod.c
@@ -1,4 +1,3 @@
-#include <sys/queue.h>
#include <sys/wait.h>
#include <err.h>
@@ -10,584 +9,21 @@
#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 <X11/extensions/Xrender.h>
-#include <X11/Xft/Xft.h>
+#include "shod.h"
-#define SHELL "SHELL"
-#define DEF_SHELL "sh"
-#define DNDDIFF 10 /* pixels from pointer to place dnd marker */
-#define DIV 15 /* see containerplace() for details */
-#define IGNOREUNMAP 6 /* number of unmap notifies to ignore while scanning existing clients */
-#define NAMEMAXLEN 256 /* maximum length of window's name */
-#define DROPPIXELS 30 /* number of pixels from the border where a tab can be dropped in */
-#define RESIZETIME 64 /* time to redraw containers during resizing */
-#define MOVETIME 32 /* time to redraw containers during moving */
-#define MOUSEEVENTMASK (ButtonReleaseMask | PointerMotionMask | ExposureMask)
-#define DOCKBORDER 2
-#define LEN(x) (sizeof(x) / sizeof((x)[0]))
-#define _SHOD_MOVERESIZE_RELATIVE ((long)(1 << 16))
+#define WMNAME "shod"
-#define TITLEWIDTH(c) (((c)->isfullscreen && (c)->ncols == 1 && TAILQ_FIRST(&(c)->colq)->nrows == 1) ? 0 : config.titlewidth)
-
-/* window type */
-enum {
- TYPE_UNKNOWN,
- TYPE_NORMAL,
- TYPE_DESKTOP,
- TYPE_DOCK,
- TYPE_MENU,
- TYPE_DIALOG,
- TYPE_NOTIFICATION,
- TYPE_PROMPT,
- TYPE_SPLASH,
- TYPE_DOCKAPP,
- TYPE_LAST
-};
-
-/* floating object type */
-enum {
- FLOAT_CONTAINER,
- FLOAT_MENU,
-};
-
-/* container states */
-enum {
- ABOVE = 0x01,
- BELOW = 0x02,
- FULLSCREEN = 0x04,
- MAXIMIZED = 0x08,
- MINIMIZED = 0x10,
- SHADED = 0x20,
- STICKY = 0x40,
-};
-
-/* container state action */
-enum {
- REMOVE = 0,
- ADD = 1,
- TOGGLE = 2
-};
-
-/* colors */
-enum {
- COLOR_MID,
- COLOR_LIGHT,
- COLOR_DARK,
- COLOR_LAST
-};
-
-/* decoration style */
-enum {
- FOCUSED,
- UNFOCUSED,
- URGENT,
- STYLE_LAST
-};
-
-/* cursor types */
-enum {
- CURSOR_NORMAL,
- CURSOR_MOVE,
- CURSOR_NW,
- CURSOR_NE,
- CURSOR_SW,
- CURSOR_SE,
- CURSOR_N,
- CURSOR_S,
- CURSOR_W,
- CURSOR_E,
- CURSOR_V,
- CURSOR_H,
- CURSOR_HAND,
- CURSOR_PIRATE,
- CURSOR_LAST
-};
-
-/* window layers */
-enum {
- LAYER_DESKTOP,
- LAYER_BELOW,
- LAYER_NORMAL,
- LAYER_ABOVE,
- LAYER_DOCK,
- LAYER_SPLASH,
- LAYER_FULLSCREEN,
- LAYER_LAST
-};
-
-/* atoms */
-enum {
- /* utf8 */
- UTF8_STRING,
-
- /* ICCCM atoms */
- WM_DELETE_WINDOW,
- WM_WINDOW_ROLE,
- WM_TAKE_FOCUS,
- WM_PROTOCOLS,
- WM_STATE,
- WM_CLIENT_LEADER,
-
- /* 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_SHADED,
- _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,
- _NET_WM_STRUT,
- _NET_WM_STRUT_PARTIAL,
- _NET_REQUEST_FRAME_EXTENTS,
- _NET_FRAME_EXTENTS,
- _NET_WM_FULL_PLACEMENT,
-
- /* motif atoms */
- _MOTIF_WM_HINTS,
-
- /* shod atoms */
- _SHOD_GROUP_TAB,
- _SHOD_GROUP_CONTAINER,
-
- ATOM_LAST
-};
-
-/* moveresize action */
-enum {
- _NET_WM_MOVERESIZE_SIZE_TOPLEFT = 0,
- _NET_WM_MOVERESIZE_SIZE_TOP = 1,
- _NET_WM_MOVERESIZE_SIZE_TOPRIGHT = 2,
- _NET_WM_MOVERESIZE_SIZE_RIGHT = 3,
- _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4,
- _NET_WM_MOVERESIZE_SIZE_BOTTOM = 5,
- _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6,
- _NET_WM_MOVERESIZE_SIZE_LEFT = 7,
- _NET_WM_MOVERESIZE_MOVE = 8, /* movement only */
- _NET_WM_MOVERESIZE_SIZE_KEYBOARD = 9, /* size via keyboard */
- _NET_WM_MOVERESIZE_MOVE_KEYBOARD = 10, /* move via keyboard */
- _NET_WM_MOVERESIZE_CANCEL = 11, /* cancel operation */
-};
-
-/* focus relative direction */
-enum {
- _SHOD_FOCUS_ABSOLUTE = 0,
- _SHOD_FOCUS_LEFT_CONTAINER = 1,
- _SHOD_FOCUS_RIGHT_CONTAINER = 2,
- _SHOD_FOCUS_TOP_CONTAINER = 3,
- _SHOD_FOCUS_BOTTOM_CONTAINER = 4,
- _SHOD_FOCUS_PREVIOUS_CONTAINER = 5,
- _SHOD_FOCUS_NEXT_CONTAINER = 6,
- _SHOD_FOCUS_LEFT_WINDOW = 7,
- _SHOD_FOCUS_RIGHT_WINDOW = 8,
- _SHOD_FOCUS_TOP_WINDOW = 9,
- _SHOD_FOCUS_BOTTOM_WINDOW = 10,
- _SHOD_FOCUS_PREVIOUS_WINDOW = 11,
- _SHOD_FOCUS_NEXT_WINDOW = 12,
-};
-
-/* strut elements */
-enum {
- STRUT_LEFT = 0,
- STRUT_RIGHT = 1,
- STRUT_TOP = 2,
- STRUT_BOTTOM = 3,
- STRUT_LEFT_START_Y = 4,
- STRUT_LEFT_END_Y = 5,
- STRUT_RIGHT_START_Y = 6,
- STRUT_RIGHT_END_Y = 7,
- STRUT_TOP_START_X = 8,
- STRUT_TOP_END_X = 9,
- STRUT_BOTTOM_START_X = 10,
- STRUT_BOTTOM_END_X = 11,
- STRUT_LAST = 12,
-};
-
-/* border */
-enum {
- BORDER_N,
- BORDER_S,
- BORDER_W,
- BORDER_E,
- BORDER_NW,
- BORDER_NE,
- BORDER_SW,
- BORDER_SE,
- BORDER_LAST
-};
-
-/* motif constants, mostly unused */
-enum {
- /*
- * Constants copied from lib/Xm/MwmUtil.h on motif's source code.
- */
-
- PROP_MOTIF_WM_HINTS_ELEMENTS = 5,
- PROP_MWM_HINTS_ELEMENTS = PROP_MOTIF_WM_HINTS_ELEMENTS,
-
- /* bit definitions for MwmHints.flags */
- MWM_HINTS_FUNCTIONS = (1 << 0),
- MWM_HINTS_DECORATIONS = (1 << 1),
- MWM_HINTS_INPUT_MODE = (1 << 2),
- MWM_HINTS_STATUS = (1 << 3),
-
- /* bit definitions for MwmHints.functions */
- MWM_FUNC_ALL = (1 << 0),
- MWM_FUNC_RESIZE = (1 << 1),
- MWM_FUNC_MOVE = (1 << 2),
- MWM_FUNC_MINIMIZE = (1 << 3),
- MWM_FUNC_MAXIMIZE = (1 << 4),
- MWM_FUNC_CLOSE = (1 << 5),
-
- /* bit definitions for MwmHints.decorations */
- MWM_DECOR_ALL = (1 << 0),
- MWM_DECOR_BORDER = (1 << 1),
- MWM_DECOR_RESIZEH = (1 << 2),
- MWM_DECOR_TITLE = (1 << 3),
- MWM_DECOR_MENU = (1 << 4),
- MWM_DECOR_MINIMIZE = (1 << 5),
- MWM_DECOR_MAXIMIZE = (1 << 6),
-
- /* values for MwmHints.input_mode */
- MWM_INPUT_MODELESS = 0,
- MWM_INPUT_PRIMARY_APPLICATION_MODAL = 1,
- MWM_INPUT_SYSTEM_MODAL = 2,
- MWM_INPUT_FULL_APPLICATION_MODAL = 3,
-
- /* bit definitions for MwmHints.status */
- MWM_TEAROFF_WINDOW = (1 << 0),
-};
-
-/* 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),
-};
-
-/* data of a monitor */
-TAILQ_HEAD(MonitorQueue, Monitor);
-struct Monitor {
- TAILQ_ENTRY(Monitor) entry;
- int seldesk; /* focused desktop on that monitor */
- int mx, my, mw, mh; /* monitor size */
- int wx, wy, ww, wh; /* window area */
-};
-
-/* managed object */
-TAILQ_HEAD(Queue, Object);
-struct Object {
- TAILQ_ENTRY(Object) entry;
- Window win;
- int type;
-};
-
-/* row of tiled container */
-TAILQ_HEAD(RowQueue, Row);
-struct Row {
- TAILQ_ENTRY(Row) entry;
- struct Queue tabq; /* list of tabs */
- struct Column *col; /* pointer to parent column */
- struct Tab *seltab; /* pointer to selected tab */
- Window div; /* horizontal division between rows */
- Window frame; /* where tab frames are */
- Window bar; /* title bar frame */
- Window bl; /* left button */
- Window br; /* right button */
- Pixmap pixbar; /* pixmap for the title bar */
- Pixmap pixbl; /* pixmap for left button */
- Pixmap pixbr; /* pixmap for right button */
- double fact; /* factor of height relative to container */
- int ntabs; /* number of tabs */
- int y, h; /* row geometry */
- int pw;
-};
-
-/* column of tiled container */
-TAILQ_HEAD(ColumnQueue, Column);
-struct Column {
- TAILQ_ENTRY(Column) entry;
- struct RowQueue rowq; /* list of rows */
- struct Container *c; /* pointer to parent container */
- struct Row *selrow; /* pointer to selected row */
- struct Row *maxrow; /* maximized row */
- Window div; /* vertical division between columns */
- double fact; /* factor of width relative to container */
- int nrows; /* number of rows */
- int x, w; /* column geometry */
-};
-
-/* container structure */
-TAILQ_HEAD(ContainerQueue, Container);
-struct Container {
- TAILQ_ENTRY(Container) entry;
- TAILQ_ENTRY(Container) raiseentry;
- struct ColumnQueue colq; /* list of columns in container */
- struct Monitor *mon; /* monitor container is on */
- struct Column *selcol; /* pointer to selected container */
- Window curswin[BORDER_LAST]; /* 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 desk; /* desktop container is on */
- int ncols; /* number of columns */
- int ismaximized, issticky; /* window states */
- int isminimized, isshaded; /* 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 */
-};
-
-/* tab structure */
-struct Tab {
- struct Object obj;
- struct Queue dialq; /* queue of dialogs */
- struct Queue menuq; /* queue of menus */
- struct Row *row; /* pointer to parent row */
- Window title; /* title bar */
- Window frame; /* window to reparent the client window */
- Window leader; /* the group leader of the 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 */
- 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 */
-};
-
-/* dialog window structure */
-struct Dialog {
- struct Object obj;
- struct Tab *tab; /* pointer to parent tab */
- Window frame; /* window to reparent the 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 */
-};
-
-/* menu structure */
-struct Menu {
- struct Object obj;
- struct Tab *tab; /* pointer to parent tab */
- Window titlebar; /* close button */
- Window button; /* close button */
- Window frame; /* frame window */
- Pixmap pix; /* pixmap to draw the frame */
- Pixmap pixbutton; /* pixmap to draw the button */
- Pixmap pixtitlebar; /* pixmap to draw the titlebar */
- int x, y, w, h; /* geometry of the menu window + the frame */
- int pw, ph; /* pixmap size */
- int tw, th; /* titlebar pixmap size */
- int ignoreunmap; /* number of unmapnotifys to ignore */
- char *name; /* client name */
-};
-
-/* bar (aka dock or panel) */
-struct Bar {
- struct Object obj;
- int strut[STRUT_LAST]; /* strut values */
- int partial; /* strut has 12 elements rather than 4 */
-};
-
-/* docked application */
-struct Dockapp {
- struct Object obj;
- int x, y, w, h; /* dockapp position and size */
- int ignoreunmap; /* number of unmap requests to ignore */
- int dockpos; /* position of the dockapp in the dock */
-};
-
-/* splash screen window */
-struct Splash {
- struct Object obj;
- int x, y, w, h; /* splash screen geometry */
-};
-
-/* notification structure */
-struct Notification {
- struct Object obj;
- Window frame; /* window to reparent the 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 */
-};
-
-/* structure returned by getwintype */
-struct Wintype {
- struct Tab *parent; /* window parent tab */
- Window leader; /* window leader */
- int type; /* window type */
- int dockpos; /* position of the dockapp in the dock */
- int state; /* bitmask of container states */
-};
-
-/* prompt structure, used only when calling promptisvalid() */
-struct Prompt {
- Window win, frame; /* actual client window and its frame */
- Pixmap pix; /* pixmap to draw the frame */
- int pw, ph; /* pixmap width and height */
-};
-
-/* cursors, fonts, decorations, and decoration sizes */
-struct Theme {
- Cursor cursors[CURSOR_LAST];
- XftFont *font;
- XftColor fg[STYLE_LAST][2];
- unsigned long title[STYLE_LAST][COLOR_LAST];
- unsigned long border[STYLE_LAST][COLOR_LAST];
- unsigned long dock[COLOR_LAST];
- unsigned long notif[COLOR_LAST];
- unsigned long prompt[COLOR_LAST];
-};
-
-/* the dock */
-struct Dock {
- struct Queue dappq;
- Window win; /* dock window */
- Pixmap pix; /* dock pixmap */
- int x, y, w, h; /* dock geometry */
- int pw, ph; /* dock pixmap size */
- int mapped; /* whether dock is mapped */
-};
-
-/* motif window manager (Mwm) hints */
-struct MwmHints {
- unsigned long flags;
- unsigned long functions;
- unsigned long decorations;
- long inputMode;
- unsigned long status;
-};
-
-/* window manager stuff */
-struct WM {
- struct MonitorQueue monq; /* queue of monitors */
- struct Queue barq; /* queue of bars */
- struct Queue splashq; /* queue of splash screen windows */
- struct Queue notifq; /* queue of notifications */
- struct ContainerQueue focusq; /* queue of containers ordered by focus history */
- struct ContainerQueue fullq; /* queue of containers ordered from topmost to bottommost */
- struct ContainerQueue aboveq; /* queue of containers ordered from topmost to bottommost */
- struct ContainerQueue centerq; /* queue of containers ordered from topmost to bottommost */
- struct ContainerQueue belowq; /* queue 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 retabwin; /* window to drag-and-drop when retabbing */
- Window layerwins[LAYER_LAST]; /* dummy windows used to set stacking order */
- Pixmap retabpix;
- int nclients; /* total number of client windows */
- int showingdesk; /* whether the desktop is being shown */
- int minsize; /* minimum size of a container */
-};
-
-/* configuration */
-struct Config {
- unsigned int modifier;
- int floatdialog;
- int honorconfig;
- int sloppyfocus;
- int ndesktops;
- int notifgap;
- int dockwidth, dockspace;
- int snap;
- int borderwidth;
- int titlewidth;
- int shadowthickness;
- const char *font;
- const char *notifgravity;
- const char *dockgravity;
- const char *foreground[STYLE_LAST];
- const char *bordercolors[STYLE_LAST][COLOR_LAST];
- const char *dockcolors[COLOR_LAST];
- const char *notifcolors[COLOR_LAST];
- const char *promptcolors[COLOR_LAST];
- struct Rule {
- const char *class;
- const char *instance;
- const char *role;
- int type;
- int state;
- } *rules;
-
- /* the values below are computed from the values above */
- int corner;
- int divwidth;
-};
+static int (*xerrorxlib)(Display *, XErrorEvent *);
+volatile sig_atomic_t running = 1;
-/* global variables */
-static XSetWindowAttributes clientswa = {
+/* shared variables */
+unsigned long clientmask = CWEventMask | CWColormap | CWBackPixel | CWBorderPixel;
+XSetWindowAttributes clientswa = {
.event_mask = SubstructureNotifyMask | ExposureMask
| SubstructureRedirectMask | ButtonPressMask | FocusChangeMask
};
-static int (*xerrorxlib)(Display *, XErrorEvent *);
-static XrmDatabase xdb;
-static Display *dpy;
-static Visual *visual;
-static Colormap colormap;
-static Window root;
-static GC gc;
-static Atom atoms[ATOM_LAST];
-static struct Theme theme;
-static struct WM wm = {};
-static struct Dock dock;
-static unsigned long clientmask = CWEventMask | CWColormap | CWBackPixel | CWBorderPixel;
-static unsigned int depth;
-static int screen, screenw, screenh;
-static char *wmname;
-static char *xrm;
-volatile sig_atomic_t running = 1;
-
-#include "config.h"
+struct WM wm = {};
+struct Dock dock;
/* show usage and exit */
static void
@@ -597,53 +33,6 @@ usage(void)
exit(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;
-}
-
/* call fork checking for error; exit on error */
static pid_t
efork(void)
@@ -673,38 +62,6 @@ execshell(char *filename)
}
}
-/* get color from color string */
-static unsigned long
-ealloccolor(const char *s)
-{
- XColor color;
-
- if(!XAllocNamedColor(dpy, colormap, s, &color, &color)) {
- warnx("could not allocate color: %s", s);
- return BlackPixel(dpy, screen);
- }
- return color.pixel;
-}
-
-/* get XftColor from color string */
-static void
-eallocxftcolor(const char *s, XftColor *color)
-{
- if(!XftColorAllocName(dpy, visual, colormap, s, color))
- errx(1, "could not allocate color: %s", s);
-}
-
-/* draw text into drawable */
-static void
-drawtext(Drawable pix, XftColor *color, XftFont *font, int x, int y, const char *text, int len)
-{
- XftDraw *draw;
-
- draw = XftDrawCreate(dpy, pix, visual, colormap);
- XftDrawStringUtf8(draw, color, font, x, y, text, len);
- XftDrawDestroy(draw);
-}
-
/* error handler */
static int
xerror(Display *dpy, XErrorEvent *e)
@@ -772,32 +129,13 @@ getresources(void)
if (XrmGetResource(xdb, "shod.faceName", "*", &type, &xval) == True)
config.font = xval.addr;
- if (XrmGetResource(xdb, "shod.foreground", "*", &type, &xval) == True) {
- config.foreground[0] = xval.addr;
- config.foreground[1] = xval.addr;
- config.foreground[2] = xval.addr;
- }
+ if (XrmGetResource(xdb, "shod.foreground", "*", &type, &xval) == True)
+ config.foreground = xval.addr;
if (XrmGetResource(xdb, "shod.dockBackground", "*", &type, &xval) == True)
- config.dockcolors[COLOR_MID] = xval.addr;
- if (XrmGetResource(xdb, "shod.dockTopShadowColor", "*", &type, &xval) == True)
- config.dockcolors[COLOR_LIGHT] = xval.addr;
- if (XrmGetResource(xdb, "shod.dockBottomShadowColor", "*", &type, &xval) == True)
- config.dockcolors[COLOR_DARK] = xval.addr;
-
- if (XrmGetResource(xdb, "shod.notifBackground", "*", &type, &xval) == True)
- config.notifcolors[COLOR_MID] = xval.addr;
- if (XrmGetResource(xdb, "shod.notifTopShadowColor", "*", &type, &xval) == True)
- config.notifcolors[COLOR_LIGHT] = xval.addr;
- if (XrmGetResource(xdb, "shod.notifBottomShadowColor", "*", &type, &xval) == True)
- config.notifcolors[COLOR_DARK] = xval.addr;
-
- if (XrmGetResource(xdb, "shod.promptBackground", "*", &type, &xval) == True)
- config.promptcolors[COLOR_MID] = xval.addr;
- if (XrmGetResource(xdb, "shod.promptTopShadowColor", "*", &type, &xval) == True)
- config.promptcolors[COLOR_LIGHT] = xval.addr;
- if (XrmGetResource(xdb, "shod.promptBottomShadowColor", "*", &type, &xval) == True)
- config.promptcolors[COLOR_DARK] = xval.addr;
+ config.dockcolors[COLOR_DEF] = xval.addr;
+ if (XrmGetResource(xdb, "shod.dockBorder", "*", &type, &xval) == True)
+ config.dockcolors[COLOR_ALT] = xval.addr;
if (XrmGetResource(xdb, "shod.activeBackground", "*", &type, &xval) == True)
config.bordercolors[FOCUSED][COLOR_MID] = xval.addr;
@@ -856,10 +194,6 @@ getoptions(int argc, char *argv[])
{
int c;
- if ((wmname = strrchr(argv[0], '/')) != NULL)
- wmname++;
- else
- wmname = argv[0];
while ((c = getopt(argc, argv, "cdm:s")) != -1) {
switch (c) {
case 'c':
@@ -886,44 +220,6 @@ getoptions(int argc, char *argv[])
return *argv;
}
-/* initialize visual and depth */
-static void
-xinitvisual(void)
-{
- XVisualInfo tpl = {
- .screen = screen,
- .depth = 32,
- .class = TrueColor
- };
- XVisualInfo *infos;
- XRenderPictFormat *fmt;
- long masks = VisualScreenMask | VisualDepthMask | VisualClassMask;
- int nitems;
- int i;
-
- visual = NULL;
- if ((infos = XGetVisualInfo(dpy, masks, &tpl, &nitems)) != NULL) {
- for (i = 0; i < nitems; i++) {
- fmt = XRenderFindVisualFormat(dpy, infos[i].visual);
- if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
- depth = infos[i].depth;
- visual = infos[i].visual;
- colormap = XCreateColormap(dpy, root, visual, AllocNone);
- break;
- }
- }
- XFree(infos);
- }
- if (visual == NULL) {
- depth = DefaultDepth(dpy, screen);
- visual = DefaultVisual(dpy, screen);
- colormap = DefaultColormap(dpy, screen);
- }
- clientswa.colormap = colormap;
- clientswa.border_pixel = BlackPixel(dpy, screen);
- clientswa.background_pixel = BlackPixel(dpy, screen);
-}
-
/* initialize signals */
static void
initsignal(void)
@@ -945,105 +241,24 @@ initsignal(void)
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;
- swa.background_pixel = BlackPixel(dpy, screen);
- swa.border_pixel = BlackPixel(dpy, screen);
- swa.colormap = colormap;
- wm.wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
- wm.focuswin = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
- depth, CopyFromParent, visual,
- CWDontPropagate | CWColormap | CWBackPixel | CWBorderPixel, &swa);
- for (i = 0; i < LAYER_LAST; i++) {
- wm.layerwins[i] = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
- XRaiseWindow(dpy, wm.layerwins[i]);
- }
- gc = XCreateGC(dpy, wm.focuswin, GCFillStyle, &(XGCValues){.fill_style = FillSolid});
-}
-
/* initialize cursors */
static void
initcursors(void)
{
- theme.cursors[CURSOR_NORMAL] = XCreateFontCursor(dpy, XC_left_ptr);
- theme.cursors[CURSOR_MOVE] = XCreateFontCursor(dpy, XC_fleur);
- theme.cursors[CURSOR_NW] = XCreateFontCursor(dpy, XC_top_left_corner);
- theme.cursors[CURSOR_NE] = XCreateFontCursor(dpy, XC_top_right_corner);
- theme.cursors[CURSOR_SW] = XCreateFontCursor(dpy, XC_bottom_left_corner);
- theme.cursors[CURSOR_SE] = XCreateFontCursor(dpy, XC_bottom_right_corner);
- theme.cursors[CURSOR_N] = XCreateFontCursor(dpy, XC_top_side);
- theme.cursors[CURSOR_S] = XCreateFontCursor(dpy, XC_bottom_side);
- theme.cursors[CURSOR_W] = XCreateFontCursor(dpy, XC_left_side);
- theme.cursors[CURSOR_E] = XCreateFontCursor(dpy, XC_right_side);
- theme.cursors[CURSOR_V] = XCreateFontCursor(dpy, XC_sb_v_double_arrow);
- theme.cursors[CURSOR_H] = XCreateFontCursor(dpy, XC_sb_h_double_arrow);
- theme.cursors[CURSOR_HAND] = XCreateFontCursor(dpy, XC_hand2);
- theme.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",
- [WM_CLIENT_LEADER] = "WM_CLIENT_LEADER",
- [_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_SHADED] = "_NET_WM_STATE_SHADED",
- [_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",
- [_NET_WM_STRUT] = "_NET_WM_STRUT",
- [_NET_WM_STRUT_PARTIAL] = "_NET_WM_STRUT_PARTIAL",
- [_NET_REQUEST_FRAME_EXTENTS] = "_NET_REQUEST_FRAME_EXTENTS",
- [_NET_FRAME_EXTENTS] = "_NET_FRAME_EXTENTS",
- [_NET_WM_FULL_PLACEMENT] = "_NET_WM_FULL_PLACEMENT",
- [_MOTIF_WM_HINTS] = "_MOTIF_WM_HINTS",
- [_SHOD_GROUP_TAB] = "_SHOD_GROUP_TAB",
- [_SHOD_GROUP_CONTAINER] = "_SHOD_GROUP_CONTAINER",
- };
-
- XInternAtoms(dpy, atomnames, ATOM_LAST, False, atoms);
+ wm.cursors[CURSOR_NORMAL] = XCreateFontCursor(dpy, XC_left_ptr);
+ wm.cursors[CURSOR_MOVE] = XCreateFontCursor(dpy, XC_fleur);
+ wm.cursors[CURSOR_NW] = XCreateFontCursor(dpy, XC_top_left_corner);
+ wm.cursors[CURSOR_NE] = XCreateFontCursor(dpy, XC_top_right_corner);
+ wm.cursors[CURSOR_SW] = XCreateFontCursor(dpy, XC_bottom_left_corner);
+ wm.cursors[CURSOR_SE] = XCreateFontCursor(dpy, XC_bottom_right_corner);
+ wm.cursors[CURSOR_N] = XCreateFontCursor(dpy, XC_top_side);
+ wm.cursors[CURSOR_S] = XCreateFontCursor(dpy, XC_bottom_side);
+ wm.cursors[CURSOR_W] = XCreateFontCursor(dpy, XC_left_side);
+ wm.cursors[CURSOR_E] = XCreateFontCursor(dpy, XC_right_side);
+ wm.cursors[CURSOR_V] = XCreateFontCursor(dpy, XC_sb_v_double_arrow);
+ wm.cursors[CURSOR_H] = XCreateFontCursor(dpy, XC_sb_h_double_arrow);
+ wm.cursors[CURSOR_HAND] = XCreateFontCursor(dpy, XC_hand2);
+ wm.cursors[CURSOR_PIRATE] = XCreateFontCursor(dpy, XC_pirate);
}
/* set up root window */
@@ -1053,7 +268,7 @@ initroot(void)
XSetWindowAttributes swa;
/* Select SubstructureRedirect events on root window */
- swa.cursor = theme.cursors[CURSOR_NORMAL];
+ swa.cursor = wm.cursors[CURSOR_NORMAL];
swa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask
| StructureNotifyMask | ButtonPressMask;
XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &swa);
@@ -1062,36 +277,6 @@ initroot(void)
XSetInputFocus(dpy, root, RevertToParent, CurrentTime);
}
-/* initialize decoration pixmap */
-static void
-inittheme(void)
-{
- int i, j;
-
- config.corner = config.borderwidth + config.titlewidth;
- config.divwidth = config.borderwidth;
- wm.minsize = config.corner * 2 + 10;
- for (i = 0; i < STYLE_LAST; i++) {
- for (j = 0; j < COLOR_LAST; j++) {
- theme.border[i][j] = ealloccolor(config.bordercolors[i][j]);
- }
- eallocxftcolor(config.bordercolors[i][COLOR_LIGHT], &theme.fg[i][0]);
- eallocxftcolor(config.foreground[i], &theme.fg[i][1]);
- }
- for (j = 0; j < COLOR_LAST; j++) {
- theme.dock[j] = ealloccolor(config.dockcolors[j]);
- theme.notif[j] = ealloccolor(config.notifcolors[j]);
- theme.prompt[j] = ealloccolor(config.promptcolors[j]);
- }
- theme.font = XftFontOpenXlfd(dpy, screen, config.font);
- if (theme.font == NULL) {
- theme.font = XftFontOpenName(dpy, screen, config.font);
- if (theme.font == NULL) {
- errx(1, "could not open font: %s", config.font);
- }
- }
-}
-
/* create dock window */
static void
initdock(void)
@@ -1108,5502 +293,131 @@ initdock(void)
depth, InputOutput, visual, clientmask, &swa);
}
-#define GETMANAGED(head, p, w) \
- TAILQ_FOREACH(p, &(head), entry) \
- if (p->win == w) \
- return (p); \
-
-/* get pointer to managed object given a window */
-static struct Object *
-getmanaged(Window win)
+/* create dummy windows used for controlling focus and the layer of clients */
+static void
+initdummywindows(void)
{
- struct Object *p, *tab, *dial, *menu;
- struct Container *c;
- struct Column *col;
- struct Row *row;
int i;
- GETMANAGED(dock.dappq, p, win)
- GETMANAGED(wm.barq, p, win)
- GETMANAGED(wm.notifq, p, win)
- GETMANAGED(wm.splashq, p, win)
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- if (c->frame == win)
- return (struct Object *)c->selcol->selrow->seltab;
- for (i = 0; i < BORDER_LAST; i++)
- if (c->curswin[i] == win)
- return (struct Object *)c->selcol->selrow->seltab;
- TAILQ_FOREACH(col, &(c)->colq, entry) {
- TAILQ_FOREACH(row, &col->rowq, entry) {
- if (row->bar == win || row->bl == win || row->br == win)
- return (struct Object *)row->seltab;
- TAILQ_FOREACH(tab, &row->tabq, entry) {
- if (tab->win == win ||
- ((struct Tab *)tab)->frame == win ||
- ((struct Tab *)tab)->title == win)
- return tab;
- TAILQ_FOREACH(dial, &((struct Tab *)tab)->dialq, entry) {
- if (dial->win == win ||
- ((struct Dialog *)dial)->frame == win)
- return dial;
- }
- TAILQ_FOREACH(menu, &((struct Tab *)tab)->menuq, entry) {
- if (menu->win == win ||
- ((struct Menu *)menu)->frame == win ||
- ((struct Menu *)menu)->button == win ||
- ((struct Menu *)menu)->titlebar == win)
- return menu;
- }
- }
- }
- }
+ for (i = 0; i < LAYER_LAST; i++) {
+ wm.layerwins[i] = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
+ XRaiseWindow(dpy, wm.layerwins[i]);
}
- return NULL;
+ wm.wmcheckwin = XCreateWindow(
+ dpy, root,
+ - (2 * config.borderwidth + config.titlewidth),
+ - (2 * config.borderwidth + config.titlewidth),
+ 2 * config.borderwidth + config.titlewidth,
+ 2 * config.borderwidth + config.titlewidth,
+ 0, depth, CopyFromParent, visual,
+ clientmask, &clientswa
+ );
+ wm.wmcheckpix = XCreatePixmap(
+ dpy, wm.wmcheckwin,
+ 2 * config.borderwidth + config.titlewidth,
+ 2 * config.borderwidth + config.titlewidth,
+ depth
+ );
}
-/* win was exposed, return the pixmap of its contents and the pixmap's size */
-static int
-getexposed(Window win, Pixmap *pix, int *pw, int *ph)
+/* map and hide dummy windows */
+static void
+mapdummywins(void)
{
- struct Object *n, *t, *d, *m;
- struct Container *c;
- struct Column *col;
- struct Row *row;
- struct Tab *tab;
- struct Dialog *dial;
- struct Menu *menu;
- struct Notification *notif;
-
- if (wm.retabwin == win) {
- *pix = wm.retabpix;
- *pw = 2 * config.borderwidth + config.titlewidth;
- *ph = 2 * config.borderwidth + config.titlewidth;
- return 1;
- }
- if (dock.win == win) {
- *pix = dock.pix;
- *pw = dock.w;
- *ph = dock.h;
- return 1;
- }
- TAILQ_FOREACH(n, &wm.notifq, entry) {
- notif = (struct Notification *)n;
- if (notif->frame == win) {
- *pix = notif->frame;
- *pw = notif->pw;
- *ph = notif->ph;
- return 1;
- }
- }
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- if (c->frame == win) {
- *pix = c->pix;
- *pw = c->pw;
- *ph = c->ph;
- return 1;
- }
- TAILQ_FOREACH(col, &(c)->colq, entry) {
- TAILQ_FOREACH(row, &col->rowq, entry) {
- if (row->bar == win) {
- *pix = row->pixbar;
- *pw = row->pw;
- *ph = config.titlewidth;
- return 1;
- }
- if (row->bl == win) {
- *pix = row->pixbl;
- *pw = config.titlewidth;
- *ph = config.titlewidth;
- return 1;
- }
- if (row->br == win) {
- *pix = row->pixbr;
- *pw = config.titlewidth;
- *ph = config.titlewidth;
- return 1;
- }
- TAILQ_FOREACH(t, &row->tabq, entry) {
- tab = (struct Tab *)t;
- if (tab->frame == win) {
- *pix = tab->pix;
- *pw = tab->pw;
- *ph = tab->ph;
- return 1;
- }
- if (tab->title == win) {
- *pix = tab->pixtitle;
- *pw = tab->ptw;
- *ph = config.titlewidth;
- return 1;
- }
- TAILQ_FOREACH(d, &tab->dialq, entry) {
- dial = (struct Dialog *)d;
- if (dial->frame == win) {
- *pix = dial->pix;
- *pw = dial->pw;
- *ph = dial->ph;
- return 1;
- }
- }
- TAILQ_FOREACH(m, &tab->menuq, entry) {
- menu = (struct Menu *)m;
- if (menu->frame == win) {
- *pix = menu->pix;
- *pw = menu->pw;
- *ph = menu->ph;
- return 1;
- }
- if (menu->titlebar == win) {
- *pix = menu->pixtitlebar;
- *pw = menu->tw;
- *ph = menu->th;
- return 1;
- }
- if (menu->button == win) {
- *pix = menu->pixbutton;
- *pw = config.titlewidth;
- *ph = config.titlewidth;
- return 1;
- }
- }
- }
- }
- }
- }
- return 0;
+ XMapWindow(dpy, wm.wmcheckwin);
}
-/* get monitor given coordinates */
-static struct Monitor *
-getmon(int x, int y)
+/* run stdin on sh */
+static void
+autostart(char *filename)
{
- struct Monitor *mon;
+ pid_t pid;
- TAILQ_FOREACH(mon, &wm.monq, entry)
- if (x >= mon->mx && x <= mon->mx + mon->mw && y >= mon->my && y <= mon->my + mon->mh)
- return mon;
- return NULL;
+ if (filename == NULL)
+ return;
+ if ((pid = efork()) == 0) {
+ if (efork() == 0)
+ execshell(filename);
+ exit(0);
+ }
+ waitpid(pid, NULL, 0);
}
-/* get text property from window; return `Success` on success */
-static int
-gettextprop(Window win, Atom atom, char *buf, size_t size)
+/* destroy dummy windows */
+static void
+cleandummywindows(void)
{
- XTextProperty tprop;
- int count;
- char **list = NULL;
+ int i;
- if (buf == NULL || size == 0)
- return BadLength;
- buf[0] = '\0';
- if (!XGetTextProperty(dpy, win, &tprop, atom) || tprop.nitems == 0)
- return BadAlloc;
- if (XmbTextPropertyToTextList(dpy, &tprop, &list, &count) != Success ||
- count < 1 || list == NULL || *list == NULL)
- return BadAlloc;
- strncpy(buf, *list, size - 1);
- buf[size - 1] = '\0';
- XFreeStringList(list);
- XFree(tprop.value);
- return Success;
+ XFreePixmap(dpy, wm.wmcheckpix);
+ XDestroyWindow(dpy, wm.wmcheckwin);
+ for (i = 0; i < LAYER_LAST; i++) {
+ XDestroyWindow(dpy, wm.layerwins[i]);
+ }
}
-/* get window name */
-static char *
-getwinname(Window win)
+/* free cursors */
+static void
+cleancursors(void)
{
- XTextProperty tprop;
- char **list = NULL;
- char *name = NULL;
- unsigned char *p = NULL;
- unsigned long size, dl;
- int di;
- Atom da;
+ size_t i;
- 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);
+ for (i = 0; i < CURSOR_LAST; i++) {
+ XFreeCursor(dpy, wm.cursors[i]);
}
- return name;
}
-/* get atom property from window */
-static Atom
-getatomprop(Window win, Atom prop)
+/* clean window manager structures */
+static void
+cleanwm(void)
{
- int di;
- unsigned long dl;
- unsigned char *p = NULL;
- Atom da, atom = None;
+ struct Monitor *mon;
+ struct Object *obj;
+ struct Container *c;
- 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;
+ while ((c = TAILQ_FIRST(&wm.focusq)) != NULL)
+ containerdel(c);
+ while ((obj = TAILQ_FIRST(&wm.notifq)) != NULL)
+ (void)unmanagenotif(obj, 0);
+ while ((obj = TAILQ_FIRST(&wm.barq)) != NULL)
+ (void)unmanagebar(obj, 0);
+ while ((obj = TAILQ_FIRST(&wm.splashq)) != NULL)
+ (void)unmanagesplash(obj, 0);
+ while ((obj = TAILQ_FIRST(&dock.dappq)) != NULL)
+ (void)unmanagedockapp(obj, 0);
+ while ((mon = TAILQ_FIRST(&wm.monq)) != NULL)
+ mondel(mon);
+ if (dock.pix != None)
+ XFreePixmap(dpy, dock.pix);
+ XDestroyWindow(dpy, dock.win);
}
-/* get bitmask of container state from given window */
-static int
-getwinstate(Window win)
+/* shod window manager */
+int
+main(int argc, char *argv[])
{
- int state;
- unsigned long i, nstates;
- unsigned char *list;
- unsigned long dl; /* dummy variable */
- int di; /* dummy variable */
- Atom da; /* dummy variable */
- Atom *as;
+ XEvent ev;
+ char *filename, *wmname;
- list = NULL;
- if (XGetWindowProperty(dpy, win, atoms[_NET_WM_STATE], 0L, 1024, False, XA_ATOM, &da, &di, &nstates, &dl, &list) != Success || list == NULL) {
- XFree(list);
- return 0;
- }
- as = (Atom *)list;
- state = 0;
- for (i = 0; i < nstates; i++) {
- if (as[i] == atoms[_NET_WM_STATE_STICKY]) {
- state |= STICKY;
- } else if (as[i] == atoms[_NET_WM_STATE_MAXIMIZED_VERT]) {
- state |= MAXIMIZED;
- } else if (as[i] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ]) {
- state |= MAXIMIZED;
- } else if (as[i] == atoms[_NET_WM_STATE_HIDDEN]) {
- state |= MINIMIZED;
- } else if (as[i] == atoms[_NET_WM_STATE_SHADED]) {
- state |= SHADED;
- } else if (as[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
- state |= FULLSCREEN;
- } else if (as[i] == atoms[_NET_WM_STATE_ABOVE]) {
- state |= ABOVE;
- } else if (as[i] == atoms[_NET_WM_STATE_BELOW]) {
- state |= BELOW;
- }
- }
- XFree(list);
- return state;
-}
-
-/* get motif window manager hints property from window */
-static struct MwmHints *
-getmwmhints(Window win)
-{
- struct MwmHints *mwmhints;
- unsigned long dl;
- Atom type;
- int di;
- int ret;
-
- ret = XGetWindowProperty(dpy, win, atoms[_MOTIF_WM_HINTS],
- 0L, PROP_MWM_HINTS_ELEMENTS,
- False, atoms[_MOTIF_WM_HINTS],
- &type, &di, &dl, &dl,
- (unsigned char **)&mwmhints);
- if ((ret == Success) && (type == atoms[_MOTIF_WM_HINTS]))
- return mwmhints;
- if (mwmhints != NULL)
- XFree(mwmhints);
- return NULL;
-}
-
-/* get tab given window is a dialog for */
-static struct Tab *
-getdialogfor(Window win)
-{
- struct Object *obj;
- Window tmpwin;
-
- if (XGetTransientForHint(dpy, win, &tmpwin))
- if ((obj = getmanaged(tmpwin)) != NULL && obj->type == TYPE_NORMAL)
- return (struct Tab *)obj;
- return NULL;
-}
-
-#define TAB_FOREACH_BEGIN(c, tab) { \
- struct Column *col; \
- struct Row *row; \
- TAILQ_FOREACH(col, &(c)->colq, entry) { \
- TAILQ_FOREACH(row, &col->rowq, entry) { \
- TAILQ_FOREACH(tab, &row->tabq, entry)
-#define TAB_FOREACH_END } \
- } \
- }
-
-/* get tab equal to leader or having leader as group leader */
-static struct Tab *
-getleaderof(Window leader)
-{
- struct Container *c;
- struct Object *tab;
-
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- TAB_FOREACH_BEGIN(c, tab){
- if (tab->win == leader || ((struct Tab *)tab)->leader == leader)
- return (struct Tab *)tab;
- }TAB_FOREACH_END
- }
- return NULL;
-}
-
-/* get window info based on its type */
-static void
-getwintype(Window win, struct Wintype *wintype)
-{
- /* rules for identifying windows */
- enum { CLASS = 0, INSTANCE = 1, ROLE = 2 };
- char *rule[] = { "_", "_", "_" };
-
- struct MwmHints *mwmhints;
- XClassHint classh;
- XWMHints *wmhints;
- XrmValue xval;
- Atom prop;
- size_t i;
- long n;
- int isdockapp, ismenu;
- char *ds;
- char buf[NAMEMAXLEN];
- char role[NAMEMAXLEN];
-
- *wintype = (struct Wintype){
- .parent = NULL,
- .leader = None,
- .type = TYPE_UNKNOWN,
- .dockpos = INT_MAX,
- .state = 0,
- };
- classh.res_class = NULL;
- classh.res_name = NULL;
- if (gettextprop(win, atoms[WM_WINDOW_ROLE], role, NAMEMAXLEN) == Success)
- rule[ROLE] = role;
- if (XGetClassHint(dpy, win, &classh)) {
- rule[CLASS] = classh.res_class;
- rule[INSTANCE] = classh.res_name;
- }
-
- /* get window state requested by application */
- wintype->state = getwinstate(win);
-
- /* get window type (and other info) from default (hardcoded) rules */
- for (i = 0; config.rules[i].class != NULL || config.rules[i].instance != NULL || config.rules[i].role != NULL; i++) {
- if ((config.rules[i].class == NULL || strcmp(config.rules[i].class, rule[CLASS]) == 0)
- && (config.rules[i].instance == NULL || strcmp(config.rules[i].instance, rule[INSTANCE]) == 0)
- && (config.rules[i].role == NULL || strcmp(config.rules[i].role, rule[ROLE]) == 0)) {
- if (config.rules[i].type != TYPE_MENU && config.rules[i].type != TYPE_DIALOG) {
- wintype->type = config.rules[i].type;
- }
- if (config.rules[i].state >= 0) {
- wintype->state = config.rules[i].state;
- }
- }
- }
-
- /* get window type (and other info) from X resources */
- if (xrm != NULL && xdb != NULL) {
- /* check for window type */
- (void)snprintf(buf, NAMEMAXLEN, "shod.%s.%s.%s.type", rule[CLASS], rule[INSTANCE], rule[ROLE]);
- if (XrmGetResource(xdb, buf, "*", &ds, &xval) == True && xval.addr != NULL) {
- if (strcasecmp(xval.addr, "DESKTOP") == 0) {
- wintype->type = TYPE_DESKTOP;
- } else if (strcasecmp(xval.addr, "DOCKAPP") == 0) {
- wintype->type = TYPE_DOCKAPP;
- } else if (strcasecmp(xval.addr, "PROMPT") == 0) {
- wintype->type = TYPE_PROMPT;
- } else if (strcasecmp(xval.addr, "NORMAL") == 0) {
- wintype->type = TYPE_NORMAL;
- }
- }
-
- /* check for window state */
- (void)snprintf(buf, NAMEMAXLEN, "shod.%s.%s.%s.state", rule[CLASS], rule[INSTANCE], rule[ROLE]);
- if (XrmGetResource(xdb, buf, "*", &ds, &xval) == True && xval.addr != NULL) {
- wintype->state = 0;
- if (strcasestr(xval.addr, "above") != NULL) {
- wintype->state |= ABOVE;
- }
- if (strcasestr(xval.addr, "below") != NULL) {
- wintype->state |= BELOW;
- }
- if (strcasestr(xval.addr, "fullscreen") != NULL) {
- wintype->state |= FULLSCREEN;
- }
- if (strcasestr(xval.addr, "maximized") != NULL) {
- wintype->state |= MAXIMIZED;
- }
- if (strcasestr(xval.addr, "minimized") != NULL) {
- wintype->state |= MINIMIZED;
- }
- if (strcasestr(xval.addr, "shaded") != NULL) {
- wintype->state |= SHADED;
- }
- if (strcasestr(xval.addr, "sticky") != NULL) {
- wintype->state |= STICKY;
- }
- }
-
- /* check for dockapp position */
- (void)snprintf(buf, NAMEMAXLEN, "shod.%s.%s.%s.dockpos", rule[CLASS], rule[INSTANCE], rule[ROLE]);
- if (XrmGetResource(xdb, buf, "*", &ds, &xval) == True) {
- if ((n = strtol(xval.addr, NULL, 10)) >= 0 && n < INT_MAX) {
- wintype->dockpos = n;
- }
- }
- }
-
- XFree(classh.res_class);
- XFree(classh.res_name);
-
- /* we already got the type of the window, return */
- if (wintype->type != TYPE_UNKNOWN)
- return;
-
- /* try to guess window type */
- wintype->type = TYPE_NORMAL;
- prop = getatomprop(win, atoms[_NET_WM_WINDOW_TYPE]);
- wmhints = XGetWMHints(dpy, win);
- mwmhints = getmwmhints(win);
- ismenu = mwmhints != NULL && (mwmhints->flags & MWM_HINTS_STATUS) && (mwmhints->status & MWM_TEAROFF_WINDOW);
- isdockapp = (wmhints && (wmhints->flags & (IconWindowHint | StateHint)) && wmhints->initial_state == WithdrawnState);
- wintype->leader = (wmhints != NULL && (wmhints->flags & WindowGroupHint)) ? wmhints->window_group : None;
- wintype->parent = getdialogfor(win);
- XFree(mwmhints);
- XFree(wmhints);
- if (isdockapp) {
- wintype->type = TYPE_DOCKAPP;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_DESKTOP]) {
- wintype->type = TYPE_DESKTOP;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
- wintype->type = TYPE_DOCK;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_NOTIFICATION]) {
- wintype->type = TYPE_NOTIFICATION;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_PROMPT]) {
- wintype->type = TYPE_PROMPT;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
- wintype->type = TYPE_SPLASH;
- } else if (ismenu ||
- prop == atoms[_NET_WM_WINDOW_TYPE_MENU] ||
- prop == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
- prop == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) {
- if (wintype->parent == NULL) {
- wintype->parent = getleaderof(wintype->leader);
- }
- if (wintype->parent != NULL) {
- wintype->type = TYPE_MENU;
- }
- } else if (wintype->parent != NULL) {
- wintype->type = config.floatdialog ? TYPE_MENU : TYPE_DIALOG;
- } else {
- wintype->type = TYPE_NORMAL;
- }
-}
-
-/* get atom property from window */
-static unsigned long
-getcardprop(Window win, Atom prop, unsigned long **array)
-{
- int di;
- unsigned long len;
- unsigned long dl;
- unsigned char *p = NULL;
- Atom da = None;
-
- if (XGetWindowProperty(dpy, win, prop, 0L, 1024, False, XA_CARDINAL, &da, &di, &len, &dl, &p) != Success || p == NULL) {
- *array = NULL;
- return 0;
- }
- *array = (unsigned long *)p;
- return len;
-}
-
-/* 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;
-}
-
-/* increment number of clients */
-static void
-clientsincr(void)
-{
- wm.nclients++;
-}
-
-/* decrement number of clients */
-static void
-clientsdecr(void)
-{
- wm.nclients--;
-}
-
-/* get focused fullscreen window in given monitor and desktop */
-static struct Container *
-getfullscreen(struct Monitor *mon, int desk)
-{
- struct Container *c;
-
- TAILQ_FOREACH(c, &wm.fullq, raiseentry)
- 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 Monitor *mon, int desk)
-{
- struct Container *c;
-
- TAILQ_FOREACH_REVERSE(c, &wm.focusq, ContainerQueue, entry)
- if (!c->isminimized && c->mon == mon && (c->issticky || c->desk == desk))
- return c;
- return NULL;
-}
-
-/* get frame handle (NW/N/NE/W/E/SW/S/SE) the pointer is on */
-static enum Octant
-getframehandle(int w, int h, int x, int y)
-{
- if ((y < config.borderwidth && x <= config.corner) || (x < config.borderwidth && y <= config.corner))
- return NW;
- else if ((y < config.borderwidth && x >= w - config.corner) || (x > w - config.borderwidth && y <= config.corner))
- return NE;
- else if ((y > h - config.borderwidth && x <= config.corner) || (x < config.borderwidth && y >= h - config.corner))
- return SW;
- else if ((y > h - config.borderwidth && x >= w - config.corner) || (x > w - config.borderwidth && y >= h - config.corner))
- return SE;
- else if (y < config.borderwidth)
- return N;
- else if (y >= h - config.borderwidth)
- return S;
- else if (x < config.borderwidth)
- return W;
- else if (x >= w - config.borderwidth)
- return E;
- return C;
-}
-
-/* get quadrant (NW/NE/SW/SE) the pointer is on */
-static enum Octant
-getquadrant(int w, int h, int x, int y)
-{
- if (x >= w / 2 && y >= h / 2)
- return SE;
- if (x >= w / 2 && y <= h / 2)
- return NE;
- if (x <= w / 2 && y >= h / 2)
- return SW;
- if (x <= w / 2 && y <= h / 2)
- return NW;
- return C;
-}
-
-/* get row or column next to division the pointer is on */
-static void
-getdivisions(struct Container *c, struct Column **cdiv, struct Row **rdiv, int x, int y)
-{
- struct Column *col;
- struct Row *row;
-
- *cdiv = NULL;
- *rdiv = NULL;
- TAILQ_FOREACH(col, &c->colq, entry) {
- if (TAILQ_NEXT(col, entry) != NULL && x >= col->x + col->w && x < col->x + col->w + config.divwidth) {
- *cdiv = col;
- return;
- }
- if (x >= col->x && x < col->x + col->w) {
- TAILQ_FOREACH(row, &col->rowq, entry) {
- if (TAILQ_NEXT(row, entry) != NULL && y >= row->y + row->h && y < row->y + row->h + config.divwidth) {
- *rdiv = row;
- return;
- }
- }
- }
- }
-}
-
-/* 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, strlen(wmname));
- 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 Object *tab;
- struct Container *c;
- Window *wins = NULL;
- int i = 0;
-
- if (wm.nclients < 1) {
- XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST], XA_WINDOW, 32, PropModeReplace, NULL, 0);
- return;
- }
- wins = ecalloc(wm.nclients, sizeof *wins);
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- TAB_FOREACH_BEGIN(c, tab){
- wins[i++] = tab->win;
- }TAB_FOREACH_END
- }
- XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST], XA_WINDOW, 32, PropModeReplace, (unsigned char *)wins, i);
- free(wins);
-}
-
-#define LOOPSTACKING(array, list, index) { \
- struct Container *c; \
- struct Column *col; \
- struct Row *row; \
- struct Object *p; \
- struct Tab *t; \
- \
- TAILQ_FOREACH(c, &(list), raiseentry) { \
- TAILQ_FOREACH(col, &c->colq, entry) { \
- if (col->selrow->seltab != NULL) \
- (array)[--(index)] = col->selrow->seltab->obj.win; \
- TAILQ_FOREACH(p, &col->selrow->tabq, entry) { \
- t = (struct Tab *)p; \
- if (t != col->selrow->seltab) { \
- (array)[--(index)] = t->obj.win; \
- } \
- } \
- TAILQ_FOREACH(row, &col->rowq, entry) { \
- if (row == col->selrow) \
- continue; \
- if (row->seltab != NULL) \
- (array)[--(index)] = row->seltab->obj.win; \
- TAILQ_FOREACH(p, &row->tabq, entry) { \
- t = (struct Tab *)p; \
- if (t != row->seltab) { \
- (array)[--(index)] = t->obj.win; \
- } \
- } \
- } \
- } \
- } \
-}
-
-/* set stacking list of clients hint */
-static void
-ewmhsetclientsstacking(void)
-{
- Window *wins = NULL;
- int i = 0;
-
- if (wm.nclients < 1) {
- XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST_STACKING], XA_WINDOW, 32, PropModeReplace, NULL, 0);
- return;
- }
- wins = ecalloc(wm.nclients, sizeof *wins);
- i = wm.nclients;
- LOOPSTACKING(wins, wm.fullq, i)
- LOOPSTACKING(wins, wm.aboveq, i)
- LOOPSTACKING(wins, wm.centerq, i)
- LOOPSTACKING(wins, wm.belowq, i)
- XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST_STACKING], 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 Object *t;
- unsigned long n;
-
- n = (c->issticky || c->isminimized) ? 0xFFFFFFFF : (unsigned long)c->desk;
- TAB_FOREACH_BEGIN(c, t){
- ewmhsetdesktop(t->win, n);
- }TAB_FOREACH_END
-}
-
-/* set frames of window */
-static void
-ewmhsetframeextents(Window win, int b, int t)
-{
- unsigned long data[4];
-
- data[0] = data[1] = data[3] = b;
- data[2] = b + t;
- XChangeProperty(dpy, win, atoms[_NET_FRAME_EXTENTS], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 4);
-}
-
-/* set state of windows */
-static void
-ewmhsetstate(struct Container *c)
-{
- struct Object *t;
- Atom data[9];
- int n = 0;
-
- if (c == NULL)
- return;
- if (c == wm.focused)
- data[n++] = atoms[_NET_WM_STATE_FOCUSED];
- if (c->isfullscreen)
- data[n++] = atoms[_NET_WM_STATE_FULLSCREEN];
- if (c->issticky)
- data[n++] = atoms[_NET_WM_STATE_STICKY];
- if (c->isshaded)
- data[n++] = atoms[_NET_WM_STATE_SHADED];
- if (c->isminimized)
- data[n++] = atoms[_NET_WM_STATE_HIDDEN];
- if (c->ismaximized) {
- data[n++] = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
- data[n++] = atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
- }
- if (c->layer > 0)
- data[n++] = atoms[_NET_WM_STATE_ABOVE];
- else if (c->layer < 0)
- data[n++] = atoms[_NET_WM_STATE_BELOW];
- TAB_FOREACH_BEGIN(c, t){
- XChangeProperty(dpy, t->win, atoms[_NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char *)data, n);
- }TAB_FOREACH_END
-}
-
-/* set group of windows in client */
-static void
-shodgrouptab(struct Container *c)
-{
- struct Object *t;
-
- TAB_FOREACH_BEGIN(c, t){
- XChangeProperty(dpy, t->win, atoms[_SHOD_GROUP_TAB], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&row->seltab->obj.win, 1);
- }TAB_FOREACH_END
-}
-
-/* set group of windows in client */
-static void
-shodgroupcontainer(struct Container *c)
-{
- struct Object *t;
-
- TAB_FOREACH_BEGIN(c, t){
- XChangeProperty(dpy, t->win, atoms[_SHOD_GROUP_CONTAINER], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&c->selcol->selrow->seltab->obj.win, 1);
- }TAB_FOREACH_END
-}
-
-/* send a WM_DELETE message to client */
-static void
-winclose(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;
-}
-
-/* if window is bigger than monitor, resize it while maintaining proportion */
-static void
-fitmonitor(struct Monitor *mon, int *x, int *y, int *w, int *h, float factor)
-{
- int origw, origh;
- int minw, minh;
-
- origw = *w;
- origh = *h;
- minw = min(origw, mon->ww * factor);
- minh = min(origh, mon->wh * factor);
- if (origw * minh > origh * minw) {
- minh = (origh * minw) / origw;
- minw = (origw * minh) / origh;
- } else {
- minw = (origw * minh) / origh;
- minh = (origh * minw) / origw;
- }
- *w = max(wm.minsize, minw);
- *h = max(wm.minsize, minh);
- *x = max(mon->wx, min(mon->wx + mon->ww - *w, *x));
- *y = max(mon->wy, min(mon->wy + mon->wh - *h, *y));
-}
-
-/* get tab decoration style */
-static int
-tabgetstyle(struct Tab *t)
-{
- if (t == NULL)
- return UNFOCUSED;
- 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)
-{
- return (c == wm.focused) ? FOCUSED : UNFOCUSED;
-}
-
-/* check if container can be shaded */
-static int
-containerisshaded(struct Container *c)
-{
- return c->isshaded && !c->isfullscreen;
-}
-
-/* calculate size of dialogs of a tab */
-static void
-dialogcalcsize(struct Dialog *dial)
-{
- struct Tab *tab;
-
- tab = dial->tab;
- dial->w = max(1, min(dial->maxw, tab->winw - 2 * config.borderwidth));
- dial->h = max(1, min(dial->maxh, tab->winh - 2 * config.borderwidth));
- dial->x = tab->winw / 2 - dial->w / 2;
- dial->y = tab->winh / 2 - dial->h / 2;
-}
-
-/* calculate position and width of tabs of a row */
-static void
-rowcalctabs(struct Row *row)
-{
- struct Object *p, *q;
- struct Dialog *d;
- struct Tab *t;
- int i, x;
-
- x = config.titlewidth;
- i = 0;
- TAILQ_FOREACH(p, &row->tabq, entry) {
- t = (struct Tab *)p;
- if (row == row->col->maxrow)
- t->winh = max(1, row->col->c->h - 2 * row->col->c->b - row->col->nrows * config.titlewidth);
- else
- t->winh = max(1, row->h - config.titlewidth);
- t->winw = row->col->w;
- t->w = max(1, ((i + 1) * (t->winw - 2 * config.titlewidth) / row->ntabs) - (i * (t->winw - 2 * config.titlewidth) / row->ntabs));
- t->x = x;
- x += t->w;
- TAILQ_FOREACH(q, &t->dialq, entry) {
- d = (struct Dialog *)q;
- dialogcalcsize(d);
- }
- i++;
- }
-}
-
-/* calculate position and height of rows of a column */
-static void
-colcalcrows(struct Column *col, int recalcfact, int recursive)
-{
- struct Container *c;
- struct Row *row;
- int i, y, h, sumh;
- int content;
- int recalc;
-
- c = col->c;
-
- /* check if rows sum up the height of the container */
- content = c->h - (col->nrows - 1) * config.divwidth - 2 * c->b;
- sumh = 0;
- recalc = 0;
- TAILQ_FOREACH(row, &col->rowq, entry) {
- if (!recalcfact) {
- if (TAILQ_NEXT(row, entry) == NULL) {
- row->h = content - sumh;
- } else {
- row->h = row->fact * content;
- }
- if (row->h <= config.titlewidth) {
- recalc = 1;
- }
- }
- sumh += row->h;
- }
- if (sumh != content)
- recalc = 1;
-
- if (col->c->isfullscreen && col->c->ncols == 1 && col->nrows == 1) {
- h = col->c->h + config.titlewidth;
- y = -config.titlewidth;
- recalc = 1;
- } else {
- h = col->c->h - 2 * c->b - (col->nrows - 1) * config.divwidth;
- y = c->b;
- }
- i = 0;
- TAILQ_FOREACH(row, &col->rowq, entry) {
- if (recalc)
- row->h = max(1, ((i + 1) * h / col->nrows) - (i * h / col->nrows));
- if (recalc || recalcfact)
- row->fact = (double)row->h/(double)c->h;
- row->y = y;
- y += row->h + config.divwidth;
- if (recursive)
- rowcalctabs(row);
- i++;
- }
-}
-
-/* calculate position and width of columns of a container */
-static void
-containercalccols(struct Container *c, int recalcfact, int recursive)
-{
- struct Column *col;
- int i, x, w;
- int sumw;
- int content;
- int recalc;
-
- if (c->isfullscreen) {
- c->x = c->mon->mx;
- c->y = c->mon->my;
- c->w = c->mon->mw;
- c->h = c->mon->mh;
- c->b = 0;
- } else if (c->ismaximized) {
- c->x = c->mon->wx;
- c->y = c->mon->wy;
- c->w = c->mon->ww;
- c->h = c->mon->wh;
- c->b = config.borderwidth;
- } else {
- c->x = c->nx;
- c->y = c->ny;
- c->w = c->nw;
- c->h = c->nh;
- c->b = config.borderwidth;
- }
- if (containerisshaded(c)) {
- c->h = 0;
- }
-
- /* check if columns sum up the width of the container */
- content = c->w - (c->ncols - 1) * config.divwidth - 2 * c->b;
- sumw = 0;
- recalc = 0;
- TAILQ_FOREACH(col, &c->colq, entry) {
- if (!recalcfact) {
- if (TAILQ_NEXT(col, entry) == NULL) {
- col->w = content - sumw;
- } else {
- col->w = col->fact * content;
- }
- if (col->w == 0) {
- recalc = 1;
- }
- }
- sumw += col->w;
- }
- if (sumw != content)
- recalc = 1;
-
- w = c->w - 2 * c->b - (c->ncols - 1) * config.divwidth;
- x = c->b;
- i = 0;
- TAILQ_FOREACH(col, &c->colq, entry) {
- if (containerisshaded(c))
- c->h = max(c->h, col->nrows * config.titlewidth);
- if (recalc)
- col->w = max(1, ((i + 1) * w / c->ncols) - (i * w / c->ncols));
- if (recalc || recalcfact)
- col->fact = (double)col->w/(double)c->w;
- col->x = x;
- x += col->w + config.divwidth;
- if (recursive)
- colcalcrows(col, recalcfact, 1);
- i++;
- }
- if (containerisshaded(c)) {
- c->h += 2 * c->b;
- }
-}
-
-/* find best position to place a container on screen */
-static void
-containerplace(struct Container *c, struct Monitor *mon, int desk, int userplaced)
-{
- 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 */
-
- if (desk < 0 || desk >= config.ndesktops || c == NULL || c->isminimized)
- return;
-
- fitmonitor(mon, &c->nx, &c->ny, &c->nw, &c->nh, 1.0);
-
- /* if the user placed the window, we should not re-place it */
- if (userplaced)
- return;
-
- /*
- * The container area is the region of the screen where containers live,
- * that is, the area of the monitor not occupied by bars or the dock; it
- * corresponds to the region occupied by a maximized container.
- *
- * Shod tries to find an empty region on the container area or a region
- * with few containers in it to place a new container. To do that, shod
- * cuts the container area in DIV divisions horizontally and vertically,
- * creating DIV*DIV regions; shod then counts how many containers are on
- * each region; and places the new container on those regions with few
- * containers over them.
- *
- * After some trial and error, I found out that a DIV equals to 15 is
- * optimal. It is not too low to provide a incorrect placement, nor too
- * high to take so much computer time.
- */
-
- /* increment cells of grid a window is in */
- TAILQ_FOREACH(tmp, &wm.focusq, entry) {
- if (tmp != c && !tmp->isminimized && ((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;
- xa = tmp->nx;
- xb = tmp->nx + tmp->nw;
- 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, max(mon->wx, mon->wx + subx + subw / 2 - c->nw / 2));
- c->ny = min(mon->wy + mon->wh - c->nh, max(mon->wy, mon->wy + suby + subh / 2 - c->nh / 2));
- containercalccols(c, 0, 1);
-}
-
-/* draw rectangle shadows */
-static void
-drawrectangle(Pixmap pix, int x, int y, int w, int h, unsigned long top, unsigned long bot)
-{
- XGCValues val;
- XRectangle *recs;
- int i;
-
- if (w <= 0 || h <= 0)
- return;
-
- recs = ecalloc(config.shadowthickness * 2, sizeof(*recs));
-
- /* draw light shadow */
- for(i = 0; i < config.shadowthickness; i++) {
- recs[i * 2] = (XRectangle){.x = x + i, .y = y + i, .width = 1, .height = h - (i * 2 + 1)};
- recs[i * 2 + 1] = (XRectangle){.x = x + i, .y = y + i, .width = w - (i * 2 + 1), .height = 1};
- }
- val.foreground = top;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 2);
-
- /* draw dark shadow */
- for(i = 0; i < config.shadowthickness; i++) {
- recs[i * 2] = (XRectangle){.x = x + w - 1 - i, .y = y + i, .width = 1, .height = h - i * 2};
- recs[i * 2 + 1] = (XRectangle){.x = x + i, .y = y + h - 1 - i, .width = w - i * 2, .height = 1};
- }
- val.foreground = bot;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 2);
-
- free(recs);
-}
-
-/* draw borders with shadows */
-static void
-drawborders(Pixmap pix, int w, int h, unsigned long *decor)
-{
- XGCValues val;
- XRectangle *recs;
- int partw, parth;
- int i;
-
- if (w <= 0 || h <= 0)
- return;
-
- partw = w - 2 * config.borderwidth;
- parth = h - 2 * config.borderwidth;
-
- recs = ecalloc(config.shadowthickness * 4, sizeof(*recs));
-
- /* draw background */
- val.foreground = decor[COLOR_MID];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, pix, gc, 0, 0, w, h);
-
- /* draw light shadow */
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 4 + 0] = (XRectangle){.x = i, .y = i, .width = 1, .height = h - 1 - i};
- recs[i * 4 + 1] = (XRectangle){.x = i, .y = i, .width = w - 1 - i, .height = 1};
- recs[i * 4 + 2] = (XRectangle){.x = w - config.borderwidth + i, .y = config.borderwidth - 1 - i, .width = 1, .height = parth + 2 * (i + 1)};
- recs[i * 4 + 3] = (XRectangle){.x = config.borderwidth - 1 - i, .y = h - config.borderwidth + i, .width = partw + 2 * (i + 1), .height = 1};
- }
- val.foreground = decor[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 4);
-
- /* draw dark shadow */
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 4 + 0] = (XRectangle){.x = w - 1 - i, .y = i, .width = 1, .height = h - i * 2};
- recs[i * 4 + 1] = (XRectangle){.x = i, .y = h - 1 - i, .width = w - i * 2, .height = 1};
- recs[i * 4 + 2] = (XRectangle){.x = config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = 1, .height = parth + 1 + i * 2};
- recs[i * 4 + 3] = (XRectangle){.x = config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = partw + 1 + i * 2, .height = 1};
- }
- val.foreground = decor[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 4);
-
- free(recs);
-}
-
-/* decorate dialog window */
-static void
-dialogdecorate(struct Dialog *d)
-{
- unsigned long *decor;
- int fullw, fullh; /* size of dialog window + borders */
-
- decor = theme.border[tabgetstyle(d->tab)];
- fullw = d->w + 2 * config.borderwidth;
- fullh = d->h + 2 * config.borderwidth;
-
- /* (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;
-
- drawborders(d->pix, fullw, fullh, decor);
-
- 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;
- XGlyphInfo box;
- unsigned long mid, top, bot;
- size_t len;
- int style;
- int drawlines = 0;
- int x, y, i;
-
- style = tabgetstyle(t);
- mid = theme.border[style][COLOR_MID];
- if (t->row != NULL && t != t->row->col->c->selcol->selrow->seltab) {
- top = theme.border[style][COLOR_LIGHT];
- bot = theme.border[style][COLOR_DARK];
- drawlines = 0;
- } else if (t->row != NULL && pressed) {
- top = theme.border[style][COLOR_DARK];
- bot = theme.border[style][COLOR_LIGHT];
- drawlines = 1;
- } else {
- top = theme.border[style][COLOR_LIGHT];
- bot = theme.border[style][COLOR_DARK];
- drawlines = 1;
- }
-
- /* (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, config.titlewidth, 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 background */
- val.foreground = mid;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, t->pixtitle, gc, 0, 0, t->w, config.titlewidth);
-
- /* draw shadows */
- drawrectangle(t->pixtitle, 0, 0, t->w, config.titlewidth, top, bot);
-
- /* write tab title */
- if (t->name != NULL) {
- len = strlen(t->name);
- XftTextExtentsUtf8(dpy, theme.font, t->name, len, &box);
- x = max(0, (t->w - box.width) / 2 + box.x);
- y = (config.titlewidth - box.height) / 2 + box.y;
-
- for (i = 3; drawlines && i < config.titlewidth - 3; i += 3) {
- val.foreground = top;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, t->pixtitle, gc, 4, i, x - 8, 1);
- XFillRectangle(dpy, t->pixtitle, gc, t->w - x + 2, i, x - 6, 1);
- }
-
- for (i = 4; drawlines && i < config.titlewidth - 2; i += 3) {
- val.foreground = bot;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, t->pixtitle, gc, 4, i, x - 8, 1);
- XFillRectangle(dpy, t->pixtitle, gc, t->w - x + 2, i, x - 6, 1);
- }
-
- drawtext(t->pixtitle, &theme.fg[style][drawlines], theme.font, x, y, t->name, len);
- }
-
- /* draw frame background */
- val.foreground = mid;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, t->pix, gc, 0, 0, t->winw, t->winh);
-
- XCopyArea(dpy, t->pixtitle, t->title, gc, 0, 0, t->w, config.titlewidth, 0, 0);
- XCopyArea(dpy, t->pix, t->frame, gc, 0, 0, t->winw, t->winh, 0, 0);
-}
-
-/* draw title bar buttons */
-static void
-buttonleftdecorate(struct Row *row, int pressed)
-{
- XGCValues val;
- XRectangle recs[2];
- unsigned long mid, top, bot;
- int style;
- int x, y, w;
-
- w = config.titlewidth - 9;
- style = (row->seltab) ? tabgetstyle(row->seltab) : UNFOCUSED;
- mid = theme.border[style][COLOR_MID];
- if (pressed) {
- top = theme.border[style][COLOR_DARK];
- bot = theme.border[style][COLOR_LIGHT];
- } else {
- top = theme.border[style][COLOR_LIGHT];
- bot = theme.border[style][COLOR_DARK];
- }
-
- /* draw background */
- val.foreground = mid;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, row->pixbl, gc, 0, 0, config.titlewidth, config.titlewidth);
- drawrectangle(row->pixbl, 0, 0, config.titlewidth, config.titlewidth, top, bot);
-
- if (w > 0) {
- x = 4;
- y = config.titlewidth / 2 - 1;
- recs[0] = (XRectangle){.x = x, .y = y, .width = w, .height = 1};
- recs[1] = (XRectangle){.x = x, .y = y, .width = 1, .height = 3};
- val.foreground = (pressed) ? bot : top;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, row->pixbl, gc, recs, 2);
- recs[0] = (XRectangle){.x = x + 1, .y = y + 2, .width = w, .height = 1};
- recs[1] = (XRectangle){.x = x + w, .y = y, .width = 1, .height = 3};
- val.foreground = (pressed) ? top : bot;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, row->pixbl, gc, recs, 2);
- }
-
- XCopyArea(dpy, row->pixbl, row->bl, gc, 0, 0, config.titlewidth, config.titlewidth, 0, 0);
-}
-
-/* draw title bar buttons */
-static void
-buttonrightdecorate(Window button, Pixmap pix, int style, int pressed)
-{
- XGCValues val;
- XPoint pts[9];
- unsigned long mid, top, bot;
- int w;
-
- w = (config.titlewidth - 11) / 2;
- mid = theme.border[style][COLOR_MID];
- if (pressed) {
- top = theme.border[style][COLOR_DARK];
- bot = theme.border[style][COLOR_LIGHT];
- } else {
- top = theme.border[style][COLOR_LIGHT];
- bot = theme.border[style][COLOR_DARK];
- }
-
- /* draw background */
- val.foreground = mid;
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, pix, gc, 0, 0, config.titlewidth, config.titlewidth);
-
- drawrectangle(pix, 0, 0, config.titlewidth, config.titlewidth, top, bot);
-
- if (w > 0) {
- pts[0] = (XPoint){.x = 3, .y = config.titlewidth - 5};
- pts[1] = (XPoint){.x = 0, .y = - 1};
- pts[2] = (XPoint){.x = w, .y = -w};
- pts[3] = (XPoint){.x = -w, .y = -w};
- pts[4] = (XPoint){.x = 0, .y = -2};
- pts[5] = (XPoint){.x = 2, .y = 0};
- pts[6] = (XPoint){.x = w, .y = w};
- pts[7] = (XPoint){.x = w, .y = -w};
- pts[8] = (XPoint){.x = 1, .y = 0};
- val.foreground = (pressed) ? bot : top;
- XChangeGC(dpy, gc, GCForeground, &val);
- XDrawLines(dpy, pix, gc, pts, 9, CoordModePrevious);
-
- pts[0] = (XPoint){.x = 3, .y = config.titlewidth - 4};
- pts[1] = (XPoint){.x = 2, .y = 0};
- pts[2] = (XPoint){.x = w, .y = -w};
- pts[3] = (XPoint){.x = w, .y = w};
- pts[4] = (XPoint){.x = 2, .y = 0};
- pts[5] = (XPoint){.x = 0, .y = -2};
- pts[6] = (XPoint){.x = -w, .y = -w};
- pts[7] = (XPoint){.x = w, .y = -w};
- pts[8] = (XPoint){.x = 0, .y = -2};
- val.foreground = (pressed) ? top : bot;
- XChangeGC(dpy, gc, GCForeground, &val);
- XDrawLines(dpy, pix, gc, pts, 9, CoordModePrevious);
- }
-
- XCopyArea(dpy, pix, button, gc, 0, 0, config.titlewidth, config.titlewidth, 0, 0);
-}
-
-/* draw decoration on container frame */
-static void
-containerdecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, int recursive, enum Octant o)
-{
- struct Column *col;
- struct Row *row;
- struct Object *t, *d;
- XRectangle *recs;
- XGCValues val;
- unsigned long *decor;
- int x, y, w, h;
- int isshaded;
- int i;
-
- if (c == NULL)
- return;
- decor = theme.border[containergetstyle(c)];
- w = c->w - config.corner * 2;
- h = c->h - config.corner * 2;
- isshaded = containerisshaded(c);
-
- recs = ecalloc(config.shadowthickness * 5, sizeof(*recs));
-
- /* (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;
-
- /* draw background */
- val.foreground = decor[COLOR_MID];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, c->pix, gc, 0, 0, c->w, c->h);
-
- if (c->b > 0) {
- /* top edge */
- drawrectangle(c->pix, config.corner, 0, w, config.borderwidth,
- (o == N ? decor[COLOR_DARK] : decor[COLOR_LIGHT]),
- (o == N ? decor[COLOR_LIGHT] : decor[COLOR_DARK]));
-
- /* bottom edge */
- drawrectangle(c->pix, config.corner, c->h - config.borderwidth, w, config.borderwidth,
- (o == S ? decor[COLOR_DARK] : decor[COLOR_LIGHT]),
- (o == S ? decor[COLOR_LIGHT] : decor[COLOR_DARK]));
-
- /* left edge */
- drawrectangle(c->pix, 0, config.corner, config.borderwidth, h,
- (o == W ? decor[COLOR_DARK] : decor[COLOR_LIGHT]),
- (o == W ? decor[COLOR_LIGHT] : decor[COLOR_DARK]));
-
- /* left edge */
- drawrectangle(c->pix, c->w - config.borderwidth, config.corner, config.borderwidth, h,
- (o == E ? decor[COLOR_DARK] : decor[COLOR_LIGHT]),
- (o == E ? decor[COLOR_LIGHT] : decor[COLOR_DARK]));
-
- if (isshaded) {
- /* left corner */
- x = 0;
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = x + i, .y = 0, .width = 1, .height = c->h - 1 - i};
- recs[i * 3 + 1] = (XRectangle){.x = x + 0, .y = i, .width = config.corner - 1 - i, .height = 1};
- recs[i * 3 + 2] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = c->h - config.borderwidth + i, .width = config.titlewidth, .height = 1};
- }
- val.foreground = (o & W) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 3);
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 5 + 0] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = 1, .height = c->h - config.borderwidth * 2 + 1 + i * 2};
- recs[i * 5 + 1] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = config.titlewidth + 1 + i, .height = 1};
- recs[i * 5 + 2] = (XRectangle){.x = x + config.corner - 1 - i, .y = i, .width = 1, .height = config.borderwidth - i};
- recs[i * 5 + 3] = (XRectangle){.x = x + config.corner - 1 - i, .y = c->h - config.borderwidth + i, .width = 1, .height = config.borderwidth - i};
- recs[i * 5 + 4] = (XRectangle){.x = x + i, .y = c->h - 1 - i, .width = config.corner - i, .height = 1};
- }
- val.foreground = (o & W) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 5);
-
- /* right corner */
- x = c->w - config.corner;
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 5 + 0] = (XRectangle){.x = x + i, .y = 0, .width = 1, .height = config.borderwidth - 1 - i};
- recs[i * 5 + 1] = (XRectangle){.x = x + 0, .y = i, .width = config.corner - 1 - i, .height = 1};
- recs[i * 5 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = config.borderwidth - 1 - i, .width = 1, .height = c->h - config.borderwidth * 2 + 1 + i * 2};
- recs[i * 5 + 3] = (XRectangle){.x = x + i, .y = c->h - config.borderwidth + i, .width = config.titlewidth + 1, .height = 1};
- recs[i * 5 + 4] = (XRectangle){.x = x + i, .y = c->h - config.borderwidth + i, .width = 1, .height = config.borderwidth - 1 - i * 2};
- }
- val.foreground = (o == E) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 5);
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = x + config.corner - 1 - i, .y = i, .width = 1, .height = c->h - i};
- recs[i * 3 + 1] = (XRectangle){.x = x + i, .y = config.borderwidth - 1 - i, .width = config.titlewidth, .height = 1};
- recs[i * 3 + 2] = (XRectangle){.x = x + i, .y = c->h - 1 - i, .width = config.corner - i, .height = 1};
- }
- val.foreground = (o == E) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 3);
- } else {
- /* top left corner */
- x = y = 0;
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 2 + 0] = (XRectangle){.x = x + i, .y = y + 0, .width = 1, .height = config.corner - 1 - i};
- recs[i * 2 + 1] = (XRectangle){.x = x + 0, .y = y + i, .width = config.corner - 1 - i, .height = 1};
- }
- val.foreground = (o == NW) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 2);
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 4 + 0] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + config.borderwidth - 1 - i, .width = 1, .height = config.titlewidth + 1 + i};
- recs[i * 4 + 1] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + config.borderwidth - 1 - i, .width = config.titlewidth + 1 + i, .height = 1};
- recs[i * 4 + 2] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + i, .width = 1, .height = config.borderwidth - i};
- recs[i * 4 + 3] = (XRectangle){.x = x + i, .y = y + config.corner - 1 - i, .width = config.borderwidth - i, .height = 1};
- }
- val.foreground = (o == NW) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 4);
-
- /* bottom left corner */
- x = 0;
- y = c->h - config.corner;
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = x + i, .y = y + 0, .width = 1, .height = config.corner - 1 - i};
- recs[i * 3 + 1] = (XRectangle){.x = x + 0, .y = y + i, .width = config.borderwidth - 1 - i, .height = 1};
- recs[i * 3 + 2] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + config.titlewidth + i, .width = config.titlewidth, .height = 1};
- }
- val.foreground = (o == SW) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 3);
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + i, .width = 1, .height = config.titlewidth};
- recs[i * 3 + 1] = (XRectangle){.x = x + i, .y = y + config.corner - 1 - i, .width = config.corner - i, .height = 1};
- recs[i * 3 + 2] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + config.titlewidth + i, .width = 1, .height = config.borderwidth - i};
- }
- val.foreground = (o == SW) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 3);
-
- /* top right corner */
- x = c->w - config.corner;
- y = 0;
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = x + i, .y = y + 0, .width = 1, .height = config.borderwidth - 1 - i};
- recs[i * 3 + 1] = (XRectangle){.x = x + 0, .y = y + i, .width = config.corner - 1 - i, .height = 1};
- recs[i * 3 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = y + config.borderwidth - 1 - i, .width = 1, .height = config.titlewidth};
- }
- val.foreground = (o == NE) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 3);
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + i, .width = 1, .height = config.corner};
- recs[i * 3 + 1] = (XRectangle){.x = x + i, .y = y + config.borderwidth - 1 - i, .width = config.titlewidth, .height = 1};
- recs[i * 3 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = y + config.corner - 1 - i, .width = config.borderwidth - i, .height = 1};
- }
- val.foreground = (o == NE) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 3);
-
- /* bottom right corner */
- x = c->w - config.corner;
- y = c->h - config.corner;
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 4 + 0] = (XRectangle){.x = x + i, .y = y + config.titlewidth + i, .width = 1, .height = config.borderwidth - 1 - i * 2};
- recs[i * 4 + 1] = (XRectangle){.x = x + config.titlewidth + i, .y = y + i, .width = config.borderwidth - 1 - i * 2, .height = 1};
- recs[i * 4 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = y + i, .width = 1, .height = config.titlewidth + 1};
- recs[i * 4 + 3] = (XRectangle){.x = x + i, .y = y + config.titlewidth + i, .width = config.titlewidth + 1, .height = 1};
- }
- val.foreground = (o == SE) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 4);
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 2 + 0] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + i, .width = 1, .height = config.corner - i};
- recs[i * 2 + 1] = (XRectangle){.x = x + i, .y = y + config.corner - 1 - i, .width = config.corner - i, .height = 1};
- }
- val.foreground = (o == SE) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, c->pix, gc, recs, config.shadowthickness * 2);
- }
- }
-
- TAILQ_FOREACH(col, &c->colq, entry) {
- /* draw column division */
- if (TAILQ_NEXT(col, entry) != NULL) {
- drawrectangle(c->pix, col->x + col->w, c->b, config.divwidth, c->h - 2 * c->b,
- (col == cdiv ? decor[COLOR_DARK] : decor[COLOR_LIGHT]),
- (col == cdiv ? decor[COLOR_LIGHT] : decor[COLOR_DARK]));
- }
-
- TAILQ_FOREACH(row, &col->rowq, entry) {
- /* draw row division */
- if (TAILQ_NEXT(row, entry) != NULL) {
- drawrectangle(c->pix, col->x, row->y + row->h, col->w, config.divwidth,
- (row == rdiv ? decor[COLOR_DARK] : decor[COLOR_LIGHT]),
- (row == rdiv ? decor[COLOR_LIGHT] : decor[COLOR_DARK]));
- }
-
- /* (re)create titlebar pixmap */
- if (row->pw != col->w || row->pixbar == None) {
- if (row->pixbar != None)
- XFreePixmap(dpy, row->pixbar);
- row->pixbar = XCreatePixmap(dpy, row->bar, col->w, config.titlewidth, depth);
- }
- row->pw = col->w;
-
-
- /* draw background of titlebar pixmap */
- val.foreground = decor[COLOR_MID];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, row->pixbar, gc, 0, 0, col->w, config.titlewidth);
- XCopyArea(dpy, row->pixbar, row->bar, gc, 0, 0, col->w, config.titlewidth, 0, 0);
-
- /* draw buttons */
- buttonleftdecorate(row, 0);
- buttonrightdecorate(row->br, row->pixbr, tabgetstyle(row->seltab), 0);
-
- /* decorate tabs, if necessary */
- if (recursive) {
- TAILQ_FOREACH(t, &row->tabq, entry) {
- tabdecorate((struct Tab *)t, 0);
- TAILQ_FOREACH(d, &((struct Tab *)t)->dialq, entry) {
- dialogdecorate((struct Dialog *)d);
- }
- }
- }
- }
- }
-
- XCopyArea(dpy, c->pix, c->frame, gc, 0, 0, c->w, c->h, 0, 0);
- free(recs);
-}
-
-/* decorate prompt frame */
-static void
-promptdecorate(struct Prompt *prompt, int w, int h)
-{
- XGCValues val;
- XRectangle *recs;
- int partw, parth;
- int i;
-
- if (prompt->pw == w && prompt->ph == h && prompt->pix != None)
- goto done;
-
- /* (re)create pixmap */
- if (prompt->pix != None)
- XFreePixmap(dpy, prompt->pix);
- prompt->pix = XCreatePixmap(dpy, prompt->frame, w, h, depth);
- prompt->pw = w;
- prompt->ph = h;
- recs = ecalloc(config.shadowthickness * 3, sizeof(*recs));
- partw = w - 2 * config.borderwidth;
- parth = h - 2 * config.borderwidth;
-
- /* draw background */
- val.foreground = theme.prompt[COLOR_MID];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, prompt->pix, gc, 0, 0, w, h);
-
- /* draw light shadow */
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = i, .y = i, .width = 1, .height = h - 1 - i};
- recs[i * 3 + 1] = (XRectangle){.x = w - config.borderwidth + i, .y = 0, .width = 1, .height = parth + config.borderwidth + i};
- recs[i * 3 + 2] = (XRectangle){.x = config.borderwidth - 1 - i, .y = h - config.borderwidth + i, .width = partw + 2 + i * 2, .height = 1};
- }
- val.foreground = theme.prompt[COLOR_LIGHT];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, prompt->pix, gc, recs, config.shadowthickness * 3);
-
- /* draw dark shadow */
- for (i = 0; i < config.shadowthickness; i++) {
- recs[i * 3 + 0] = (XRectangle){.x = w - 1 - i, .y = i, .width = 1, .height = h - i * 2};
- recs[i * 3 + 1] = (XRectangle){.x = i, .y = h - 1 - i, .width = w - i * 2, .height = 1};
- recs[i * 3 + 2] = (XRectangle){.x = config.borderwidth - 1 - i, .y = i, .width = 1, .height = parth + config.borderwidth};
- }
- val.foreground = theme.prompt[COLOR_DARK];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangles(dpy, prompt->pix, gc, recs, config.shadowthickness * 3);
-
- free(recs);
-done:
- XCopyArea(dpy, prompt->pix, prompt->frame, gc, 0, 0, w, h, 0, 0);
-}
-
-/* decorate notification */
-static void
-notifdecorate(struct Notification *n)
-{
- /* (re)create pixmap */
- 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;
-
- drawborders(n->pix, n->w, n->h, theme.notif);
-
- XCopyArea(dpy, n->pix, n->frame, gc, 0, 0, n->w, n->h, 0, 0);
-}
-
-/* decorate menu */
-static void
-menudecorate(struct Menu *menu, int titlepressed)
-{
- XGlyphInfo box;
- XGCValues val;
- size_t len;
- unsigned long top, bot;
- int tw, th;
- int x, y;
-
- if (menu->pw != menu->w || menu->ph != menu->h || menu->pix == None) {
- if (menu->pix != None)
- XFreePixmap(dpy, menu->pix);
- menu->pix = XCreatePixmap(dpy, menu->frame, menu->w, menu->h, depth);
- }
- menu->pw = menu->w;
- menu->ph = menu->h;
- tw = max(1, menu->w - 2 * config.borderwidth - config.titlewidth);
- th = config.titlewidth;
- if (menu->tw != tw || menu->th != th || menu->pixtitlebar == None) {
- if (menu->pixtitlebar != None)
- XFreePixmap(dpy, menu->pixtitlebar);
- menu->pixtitlebar = XCreatePixmap(dpy, menu->titlebar, tw, th, depth);
- }
- menu->tw = tw;
- menu->th = th;
-
- if (titlepressed) {
- top = theme.border[FOCUSED][COLOR_DARK];
- bot = theme.border[FOCUSED][COLOR_LIGHT];
- } else {
- top = theme.border[FOCUSED][COLOR_LIGHT];
- bot = theme.border[FOCUSED][COLOR_DARK];
- }
- val.fill_style = FillSolid;
- val.foreground = theme.border[FOCUSED][COLOR_MID];
- XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val);
- XFillRectangle(dpy, menu->pixtitlebar, gc, 0, 0, menu->tw, menu->th);
- drawborders(menu->pix, menu->w, menu->h, theme.border[FOCUSED]);
- drawrectangle(menu->pixtitlebar, 0, 0, menu->tw, config.titlewidth, top, bot);
- /* write menu title */
- if (menu->name != NULL) {
- len = strlen(menu->name);
- XftTextExtentsUtf8(dpy, theme.font, menu->name, len, &box);
- x = max(0, (menu->tw - box.width) / 2 + box.x);
- y = (config.titlewidth - box.height) / 2 + box.y;
- drawtext(menu->pixtitlebar, &theme.fg[FOCUSED][1], theme.font, x, y, menu->name, len);
- }
- buttonrightdecorate(menu->button, menu->pixbutton, FOCUSED, 0);
- XCopyArea(dpy, menu->pix, menu->frame, gc, 0, 0, menu->pw, menu->ph, 0, 0);
- XCopyArea(dpy, menu->pixtitlebar, menu->titlebar, gc, 0, 0, menu->tw, menu->th, 0, 0);
-}
-
-/* map menus */
-static void
-menumap(struct Tab *tab)
-{
- struct Object *menu;
-
- if (tab == NULL)
- return;
- TAILQ_FOREACH(menu, &tab->menuq, entry) {
- XMapWindow(dpy, ((struct Menu *)menu)->frame);
- icccmwmstate(menu->win, NormalState);
- }
-}
-
-/* unmap menus */
-static void
-menuunmap(struct Tab *tab)
-{
- struct Object *menu;
-
- if (tab == NULL)
- return;
- TAILQ_FOREACH(menu, &tab->menuq, entry) {
- XUnmapWindow(dpy, ((struct Menu *)menu)->frame);
- icccmwmstate(menu->win, IconicState);
- }
-}
-
-/* raise menus */
-static void
-menuraise(struct Tab *tab)
-{
- struct Container *c;
- struct Object *p;
- struct Menu *menu;
- Window wins[2], layer;
-
- c = tab->row->col->c;
- if (c == NULL || c->isminimized)
- return;
- if (c->isfullscreen)
- layer = wm.layerwins[LAYER_FULLSCREEN];
- else if (c->layer > 0)
- layer = wm.layerwins[LAYER_ABOVE];
- else if (c->layer < 0)
- layer = wm.layerwins[LAYER_BELOW];
- else
- layer = wm.layerwins[LAYER_NORMAL];
- wins[0] = layer;
- TAILQ_FOREACH(p, &tab->menuq, entry) {
- menu = (struct Menu *)p;
- wins[1] = menu->frame;
- XRestackWindows(dpy, wins, 2);
- wins[0] = menu->frame;
- }
-}
-
-/* remove menu from the menu list */
-static void
-menudelraise(struct Tab *tab, struct Menu *menu)
-{
- if (TAILQ_EMPTY(&tab->menuq))
- return;
- TAILQ_REMOVE(&tab->menuq, (struct Object *)menu, entry);
-}
-
-/* put menu on beginning of menu list */
-static void
-menuaddraise(struct Tab *tab, struct Menu *menu)
-{
- menudelraise(tab, menu);
- TAILQ_INSERT_HEAD(&tab->menuq, (struct Object *)menu, entry);
- XSetInputFocus(dpy, menu->obj.win, RevertToParent, CurrentTime);
-}
-
-/* delete menu; return whether menu was deleted */
-static int
-menudel(struct Object *obj, int ignoreunmap)
-{
- struct Menu *menu;
-
- menu = (struct Menu *)obj;
- if (ignoreunmap && menu->ignoreunmap) {
- menu->ignoreunmap--;
- return 0;
- }
- menudelraise(menu->tab, menu);
- if (menu->pix != None)
- XFreePixmap(dpy, menu->pix);
- if (menu->pixbutton != None)
- XFreePixmap(dpy, menu->pixbutton);
- if (menu->pixtitlebar != None)
- XFreePixmap(dpy, menu->pixtitlebar);
- icccmdeletestate(menu->obj.win);
- XReparentWindow(dpy, menu->obj.win, root, 0, 0);
- XDestroyWindow(dpy, menu->frame);
- XDestroyWindow(dpy, menu->titlebar);
- XDestroyWindow(dpy, menu->button);
- free(menu->name);
- free(menu);
- return 1;
-}
-
-/* commit menu geometry */
-static void
-menumoveresize(struct Menu *menu)
-{
- XMoveResizeWindow(dpy, menu->frame, menu->x, menu->y, menu->w, menu->h);
- XMoveWindow(dpy, menu->button, menu->w - config.borderwidth - config.titlewidth, config.borderwidth);
- XResizeWindow(dpy, menu->titlebar, max(1, menu->w - 2 * config.borderwidth - config.titlewidth), config.titlewidth);
- XResizeWindow(dpy, menu->obj.win, menu->w - 2 * config.borderwidth, menu->h - 2 * config.borderwidth - config.titlewidth);
-}
-
-/* place menu next to its container */
-static void
-menuplace(struct Menu *menu)
-{
- struct Container *c;
-
- c = menu->tab->row->col->c;
- fitmonitor(c->mon, &menu->x, &menu->y, &menu->w, &menu->h, 1.0);
- menumoveresize(menu);
-}
-
-/* configure menu window */
-static void
-menuconfigure(struct Menu *menu, unsigned int valuemask, XWindowChanges *wc)
-{
- if (menu == NULL)
- return;
- if (valuemask & CWX)
- menu->x = wc->x;
- if (valuemask & CWY)
- menu->y = wc->y;
- if (valuemask & CWWidth)
- menu->w = wc->width;
- if (valuemask & CWHeight)
- menu->h = wc->height;
- menumoveresize(menu);
- menudecorate(menu, 0);
-}
-
-/* remove container from the focus list */
-static void
-containerdelfocus(struct Container *c)
-{
- TAILQ_REMOVE(&wm.focusq, c, entry);
-}
-
-/* add container into head of focus queue */
-static void
-containerinsertfocus(struct Container *c)
-{
- TAILQ_INSERT_HEAD(&wm.focusq, c, entry);
-}
-
-/* put container on beginning of focus list */
-static void
-containeraddfocus(struct Container *c)
-{
- if (c == NULL || c->isminimized)
- return;
- containerdelfocus(c);
- containerinsertfocus(c);
-}
-
-/* hide container */
-static void
-containerhide(struct Container *c, int hide)
-{
- struct Object *t, *d;
-
- if (c == NULL)
- return;
- c->ishidden = hide;
- if (hide) {
- XUnmapWindow(dpy, c->frame);
- menuunmap(c->selcol->selrow->seltab);
- } else {
- XMapWindow(dpy, c->frame);
- }
- TAB_FOREACH_BEGIN(c, t) {
- icccmwmstate(t->win, (hide ? IconicState : NormalState));
- TAILQ_FOREACH(d, &((struct Tab *)t)->dialq, entry) {
- icccmwmstate(d->win, (hide ? IconicState : NormalState));
- }
- }TAB_FOREACH_END
-}
-
-/* commit dialog size and position */
-static void
-dialogmoveresize(struct Dialog *dial)
-{
- struct Container *c;
- int dx, dy, dw, dh;
-
- dialogcalcsize(dial);
- c = dial->tab->row->col->c;
- dx = dial->x - config.borderwidth;
- dy = dial->y - config.borderwidth;
- dw = dial->w + 2 * config.borderwidth;
- dh = dial->h + 2 * config.borderwidth;
- XMoveResizeWindow(dpy, dial->frame, dx, dy, dw, dh);
- XMoveResizeWindow(dpy, dial->obj.win, config.borderwidth, config.borderwidth, dial->w, dial->h);
- winnotify(dial->obj.win, c->x + dial->tab->row->col->x + dial->x, c->y + dial->tab->row->y + dial->y, dial->w, dial->h);
- if (dial->pw != dw || dial->ph != dh) {
- dialogdecorate(dial);
- }
-}
-
-/* configure dialog 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)
-{
- XMoveResizeWindow(dpy, t->title, t->x, 0, t->w, config.titlewidth);
- if (t->ptw != t->w) {
- tabdecorate(t, 0);
- }
- winnotify(t->obj.win, t->row->col->c->x + t->row->col->x, t->row->col->c->y + t->row->y + config.titlewidth, t->winw, t->winh);
-}
-
-/* commit titlebar size and position */
-static void
-titlebarmoveresize(struct Row *row, int x, int y, int w)
-{
- XMoveResizeWindow(dpy, row->bar, x, y, w, config.titlewidth);
- XMoveWindow(dpy, row->bl, 0, 0);
- XMoveWindow(dpy, row->br, w - config.titlewidth, 0);
-}
-
-/* commit container size and position */
-static void
-containermoveresize(struct Container *c)
-{
- struct Object *t, *d;
- struct Column *col;
- struct Row *row;
- struct Tab *tab;
- struct Dialog *dial;
- int rowy, rowh;
- int isshaded;
-
- if (c == NULL)
- return;
- XMoveResizeWindow(dpy, c->frame, c->x, c->y, c->w, c->h);
- XMoveResizeWindow(dpy, c->curswin[BORDER_N], config.corner, 0, c->w - 2 * config.corner, c->b);
- XMoveResizeWindow(dpy, c->curswin[BORDER_S], config.corner, c->h - c->b, c->w - 2 * config.corner, c->b);
- XMoveResizeWindow(dpy, c->curswin[BORDER_W], 0, config.corner, c->b, c->h - 2 * config.corner);
- XMoveResizeWindow(dpy, c->curswin[BORDER_E], c->w - c->b, config.corner, c->b, c->h - 2 * config.corner);
- XMoveResizeWindow(dpy, c->curswin[BORDER_NW], 0, 0, config.corner, config.corner);
- XMoveResizeWindow(dpy, c->curswin[BORDER_NE], c->w - config.corner, 0, config.corner, config.corner);
- XMoveResizeWindow(dpy, c->curswin[BORDER_SW], 0, c->h - config.corner, config.corner, config.corner);
- XMoveResizeWindow(dpy, c->curswin[BORDER_SE], c->w - config.corner, c->h - config.corner, config.corner, config.corner);
- isshaded = containerisshaded(c);
- TAILQ_FOREACH(col, &c->colq, entry) {
- rowy = c->b;
- rowh = max(1, c->h - 2 * c->b - col->nrows * config.titlewidth);
- if (TAILQ_NEXT(col, entry) != NULL) {
- XMoveResizeWindow(dpy, col->div, col->x + col->w, c->b, config.divwidth, c->h - 2 * c->b);
- XMapWindow(dpy, col->div);
- } else {
- XUnmapWindow(dpy, col->div);
- }
- TAILQ_FOREACH(row, &col->rowq, entry) {
- if (!isshaded && TAILQ_NEXT(row, entry) != NULL && col->maxrow == NULL) {
- XMoveResizeWindow(dpy, row->div, col->x, row->y + row->h, col->w, config.divwidth);
- XMapWindow(dpy, row->div);
- } else {
- XUnmapWindow(dpy, row->div);
- }
- if (!isshaded && col->maxrow == NULL) { /* regular row */
- titlebarmoveresize(row, col->x, row->y, col->w);
- XMoveResizeWindow(dpy, row->frame, col->x, row->y + config.titlewidth, col->w, row->h - config.titlewidth);
- XMapWindow(dpy, row->frame);
- } else if (!isshaded && row == col->maxrow) { /* maximized row */
- titlebarmoveresize(row, col->x, rowy, col->w);
- XMoveResizeWindow(dpy, row->frame, col->x, rowy + config.titlewidth, col->w, rowh);
- XMapWindow(dpy, row->frame);
- rowy += rowh;
- } else { /* minimized row */
- titlebarmoveresize(row, col->x, rowy, col->w);
- XUnmapWindow(dpy, row->frame);
- }
- rowy += config.titlewidth;
- TAILQ_FOREACH(t, &row->tabq, entry) {
- tab = (struct Tab *)t;
- XMoveResizeWindow(dpy, tab->frame, 0, 0, tab->winw, tab->winh);
- TAILQ_FOREACH(d, &tab->dialq, entry) {
- dial = (struct Dialog *)d;
- dialogmoveresize(dial);
- ewmhsetframeextents(dial->obj.win, c->b, 0);
- }
- XResizeWindow(dpy, tab->obj.win, tab->winw, tab->winh);
- ewmhsetframeextents(tab->obj.win, c->b, TITLEWIDTH(c));
- tabmoveresize(tab);
- }
- }
- }
-}
-
-/* check if container needs to be redecorated and redecorate it */
-static void
-containerredecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, enum Octant o)
-{
- if (c->pw != c->w || c->ph != c->h) {
- containerdecorate(c, cdiv, rdiv, 0, o);
- }
-}
-
-/* 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) && wc->width >= wm.minsize)
- c->nw = wc->width;
- if ((valuemask & CWHeight) && wc->height >= wm.minsize)
- c->nh = wc->height;
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, 0);
-}
-
-/* remove container from the raise list */
-static void
-containerdelraise(struct Container *c)
-{
- if (c->isfullscreen) {
- if (!TAILQ_EMPTY(&wm.fullq)) {
- TAILQ_REMOVE(&wm.fullq, c, raiseentry);
- }
- } else if (c->layer > 0) {
- if (!TAILQ_EMPTY(&wm.aboveq)) {
- TAILQ_REMOVE(&wm.aboveq, c, raiseentry);
- }
- } else if (c->layer < 0) {
- if (!TAILQ_EMPTY(&wm.belowq)) {
- TAILQ_REMOVE(&wm.belowq, c, raiseentry);
- }
- } else {
- if (!TAILQ_EMPTY(&wm.centerq)) {
- TAILQ_REMOVE(&wm.centerq, c, raiseentry);
- }
- }
-}
-
-/* add container into head of focus queue */
-static void
-containerinsertraise(struct Container *c)
-{
- if (c->isfullscreen) {
- TAILQ_INSERT_HEAD(&wm.fullq, c, raiseentry);
- } else if (c->layer > 0) {
- TAILQ_INSERT_HEAD(&wm.aboveq, c, raiseentry);
- } else if (c->layer < 0) {
- TAILQ_INSERT_HEAD(&wm.belowq, c, raiseentry);
- } else {
- TAILQ_INSERT_HEAD(&wm.centerq, c, raiseentry);
- }
-}
-
-/* raise container */
-static void
-containerraise(struct Container *c, int isfullscreen, int layer)
-{
- Window wins[2];
-
- if (c == NULL || c->isminimized)
- return;
- containerdelraise(c);
- wins[1] = c->frame;
- if (isfullscreen) {
- TAILQ_INSERT_HEAD(&wm.fullq, c, raiseentry);
- wins[0] = wm.layerwins[LAYER_FULLSCREEN];
- } else if (layer > 0) {
- TAILQ_INSERT_HEAD(&wm.aboveq, c, raiseentry);
- wins[0] = wm.layerwins[LAYER_ABOVE];
- } else if (layer < 0) {
- TAILQ_INSERT_HEAD(&wm.belowq, c, raiseentry);
- wins[0] = wm.layerwins[LAYER_BELOW];
- } else {
- TAILQ_INSERT_HEAD(&wm.centerq, c, raiseentry);
- wins[0] = wm.layerwins[LAYER_NORMAL];
- }
- c->isfullscreen = isfullscreen;
- c->layer = layer;
- XRestackWindows(dpy, wins, 2);
- menuraise(c->selcol->selrow->seltab);
- ewmhsetclientsstacking();
-}
-
-/* send container to desktop, raise it and optionally place it */
-static void
-containersendtodesk(struct Container *c, struct Monitor *mon, int desk, int place, int userplaced)
-{
- if (c == NULL || desk < 0 || desk >= config.ndesktops || c->isminimized)
- return;
- c->desk = desk;
- c->mon = mon;
- if (c->issticky) {
- c->issticky = 0;
- ewmhsetstate(c);
- }
- if (place)
- containerplace(c, mon, desk, userplaced);
- if (desk != mon->seldesk) /* container was sent to invisible desktop */
- containerhide(c, 1);
- containerraise(c, c->isfullscreen, c->layer);
- 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);
- if (focus) {
- if ((tofocus = getnextfocused(c->mon, 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, wm.selmon->seldesk, 1, 0);
- containermoveresize(c);
- containerhide(c, 0);
- tabfocus(c->selcol->selrow->seltab, 0);
- } else {
- return;
- }
- ewmhsetstate(c);
-}
-
-/* make a container occupy the whole monitor */
-static void
-containerfullscreen(struct Container *c, int fullscreen)
-{
- if (fullscreen != REMOVE && !c->isfullscreen)
- containerraise(c, 1, c->layer);
- else if (fullscreen != ADD && c->isfullscreen)
- containerraise(c, 0, c->layer);
- else
- return;
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, 0);
- ewmhsetstate(c);
-}
-
-/* maximize a container on the monitor */
-static void
-containermaximize(struct Container *c, int maximize)
-{
- if (maximize != REMOVE && !c->ismaximized)
- c->ismaximized = 1;
- else if (maximize != ADD && c->ismaximized)
- c->ismaximized = 0;
- else
- return;
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, 0);
- ewmhsetstate(c);
-}
-
-/* shade container title bar */
-static void
-containershade(struct Container *c, int shade)
-{
- void tabfocus(struct Tab *t, int gotodesk);
-
- if (shade != REMOVE && !c->isshaded) {
- c->isshaded = 1;
- XDefineCursor(dpy, c->curswin[BORDER_NW], theme.cursors[CURSOR_W]);
- XDefineCursor(dpy, c->curswin[BORDER_SW], theme.cursors[CURSOR_W]);
- XDefineCursor(dpy, c->curswin[BORDER_NE], theme.cursors[CURSOR_E]);
- XDefineCursor(dpy, c->curswin[BORDER_SE], theme.cursors[CURSOR_E]);
- } else if (shade != ADD && c->isshaded) {
- c->isshaded = 0;
- XDefineCursor(dpy, c->curswin[BORDER_NW], theme.cursors[CURSOR_NW]);
- XDefineCursor(dpy, c->curswin[BORDER_SW], theme.cursors[CURSOR_SW]);
- XDefineCursor(dpy, c->curswin[BORDER_NE], theme.cursors[CURSOR_NE]);
- XDefineCursor(dpy, c->curswin[BORDER_SE], theme.cursors[CURSOR_SE]);
- } else {
- return;
- }
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, 0);
- ewmhsetstate(c);
- if (c == wm.focused) {
- tabfocus(c->selcol->selrow->seltab, 0);
- }
-}
-
-/* stick a container on the monitor */
-static void
-containerstick(struct Container *c, int stick)
-{
- if (stick != REMOVE && !c->issticky) {
- c->issticky = 1;
- ewmhsetwmdesktop(c);
- } else if (stick != ADD && c->issticky) {
- c->issticky = 0;
- containersendtodesk(c, c->mon, c->mon->seldesk, 0, 0);
- } else {
- return;
- }
-}
-
-/* raise container above others */
-static void
-containerabove(struct Container *c, int above)
-{
- if (above != REMOVE && c->layer != 1)
- containerraise(c, c->isfullscreen, 1);
- else if (above != ADD && c->layer != 0)
- containerraise(c, c->isfullscreen, 0);
- else
- return;
- ewmhsetstate(c);
-}
-
-/* lower container below others */
-static void
-containerbelow(struct Container *c, int below)
-{
- if (below != REMOVE && c->layer != -1)
- containerraise(c, c->isfullscreen, -1);
- else if (below != ADD && c->layer != 0)
- containerraise(c, c->isfullscreen, 0);
- else
- return;
- ewmhsetstate(c);
-}
-
-/* create new container */
-static struct Container *
-containernew(int x, int y, int w, int h, int state)
-{
- struct Container *c;
- int i;
-
- x -= config.borderwidth,
- y -= config.borderwidth,
- w += 2 * config.borderwidth,
- h += 2 * config.borderwidth + config.titlewidth,
- c = emalloc(sizeof *c);
- *c = (struct Container) {
- .x = x, .y = y, .w = w, .h = h,
- .nx = x, .ny = y, .nw = w, .nh = h,
- .b = config.borderwidth,
- .pix = None,
- .isfullscreen = (state & FULLSCREEN),
- .ismaximized = (state & MAXIMIZED),
- .isminimized = (state & MINIMIZED),
- .issticky = (state & STICKY),
- .isshaded = (state & SHADED),
- .layer = (state & ABOVE) ? +1 : (state & BELOW) ? -1 : 0,
- };
- TAILQ_INIT(&c->colq);
- c->frame = XCreateWindow(dpy, root, c->x, c->y, c->w, c->h, 0, depth, CopyFromParent, visual, clientmask, &clientswa);
- c->curswin[BORDER_N] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = theme.cursors[CURSOR_N],
- }
- );
- c->curswin[BORDER_S] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = theme.cursors[CURSOR_S],
- }
- );
- c->curswin[BORDER_W] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = theme.cursors[CURSOR_W],
- }
- );
- c->curswin[BORDER_E] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = theme.cursors[CURSOR_E],
- }
- );
- c->curswin[BORDER_NW] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = c->isshaded ? theme.cursors[CURSOR_W] : theme.cursors[CURSOR_NW],
- }
- );
- c->curswin[BORDER_SW] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = c->isshaded ? theme.cursors[CURSOR_W] : theme.cursors[CURSOR_SW],
- }
- );
- c->curswin[BORDER_NE] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = c->isshaded ? theme.cursors[CURSOR_E] : theme.cursors[CURSOR_NE],
- }
- );
- c->curswin[BORDER_SE] = XCreateWindow(
- dpy, c->frame, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent,
- CWCursor,
- &(XSetWindowAttributes){
- .cursor = c->isshaded ? theme.cursors[CURSOR_E] : theme.cursors[CURSOR_SE],
- }
- );
- for (i = 0; i < BORDER_LAST; i++)
- XMapWindow(dpy, c->curswin[i]);
- containerinsertfocus(c);
- containerinsertraise(c);
- return c;
-}
-
-/* delete dialog; return whether dialog was deleted */
-static int
-dialogdel(struct Object *obj, int ignoreunmap)
-{
- struct Dialog *dial;
-
- dial = (struct Dialog *)obj;
- if (ignoreunmap && dial->ignoreunmap) {
- dial->ignoreunmap--;
- return 0;
- }
- TAILQ_REMOVE(&dial->tab->dialq, (struct Object *)dial, entry);
- if (dial->pix != None)
- XFreePixmap(dpy, dial->pix);
- icccmdeletestate(dial->obj.win);
- XReparentWindow(dpy, dial->obj.win, root, 0, 0);
- XDestroyWindow(dpy, dial->frame);
- free(dial);
- return 1;
-}
-
-/* detach tab from row */
-static void
-tabdetach(struct Tab *tab, int x, int y)
-{
- struct Row *row;
-
- row = tab->row;
- if (row->seltab == tab) {
- row->seltab = (struct Tab *)TAILQ_PREV((struct Object *)tab, Queue, entry);
- if (row->seltab == NULL) {
- row->seltab = (struct Tab *)TAILQ_NEXT((struct Object *)tab, entry);
- }
- }
- row->ntabs--;
- tab->ignoreunmap = IGNOREUNMAP;
- XReparentWindow(dpy, tab->title, root, x, y);
- TAILQ_REMOVE(&row->tabq, (struct Object *)tab, entry);
- tab->row = NULL;
- rowcalctabs(row);
-}
-
-/* delete tab */
-static void
-tabdel(struct Tab *tab)
-{
- struct Dialog *dial;
- struct Menu *menu;
-
- while ((dial = (struct Dialog *)TAILQ_FIRST(&tab->dialq)) != NULL) {
- XDestroyWindow(dpy, dial->obj.win);
- dialogdel((struct Object *)dial, 0);
- }
- while ((menu = (struct Menu *)TAILQ_FIRST(&tab->menuq)) != NULL) {
- XDestroyWindow(dpy, menu->obj.win);
- menudel((struct Object *)menu, 0);
- }
- tabdetach(tab, 0, 0);
- if (tab->pixtitle != None)
- XFreePixmap(dpy, tab->pixtitle);
- if (tab->pix != None)
- XFreePixmap(dpy, tab->pix);
- icccmdeletestate(tab->obj.win);
- XReparentWindow(dpy, tab->obj.win, root, 0, 0);
- XDestroyWindow(dpy, tab->title);
- XDestroyWindow(dpy, tab->frame);
- clientsdecr();
- free(tab->name);
- free(tab);
-}
-
-/* stack rows */
-static void
-rowstack(struct Column *col, struct Row *row)
-{
- if (row == NULL) {
- col->maxrow = NULL;
- } else if (col->maxrow != row) {
- col->maxrow = row;
- rowcalctabs(row);
- } else {
- return;
- }
- colcalcrows(col, 0, 1);
- containermoveresize(col->c);
- containerdecorate(col->c, NULL, NULL, 0, 0);
-}
-
-/* detach row from column */
-static void
-rowdetach(struct Row *row, int recalc)
-{
- struct Column *col;
-
- col = row->col;
- if (col->selrow == row) {
- col->selrow = TAILQ_PREV(row, RowQueue, entry);
- if (col->selrow == NULL) {
- col->selrow = TAILQ_NEXT(row, entry);
- }
- }
- col->nrows--;
- TAILQ_REMOVE(&col->rowq, row, entry);
- if (row == row->col->maxrow)
- row->col->maxrow = NULL;
- if (recalc) {
- colcalcrows(row->col, 1, 0);
- }
-}
-
-/* delete row */
-static void
-rowdel(struct Row *row)
-{
- struct Tab *tab;
-
- while ((tab = (struct Tab *)TAILQ_FIRST(&row->tabq)) != NULL)
- tabdel(tab);
- rowdetach(row, 1);
- XDestroyWindow(dpy, row->frame);
- XDestroyWindow(dpy, row->bar);
- XDestroyWindow(dpy, row->bl);
- XDestroyWindow(dpy, row->br);
- XDestroyWindow(dpy, row->div);
- if (row->pixbar != None)
- XFreePixmap(dpy, row->pixbar);
- XFreePixmap(dpy, row->pixbl);
- XFreePixmap(dpy, row->pixbr);
- free(row);
-}
-
-/* detach column from container */
-static void
-coldetach(struct Column *col)
-{
- struct Container *c;
-
- c = col->c;
- if (c->selcol == col) {
- c->selcol = TAILQ_PREV(col, ColumnQueue, entry);
- if (c->selcol == NULL) {
- c->selcol = TAILQ_NEXT(col, entry);
- }
- }
- c->ncols--;
- TAILQ_REMOVE(&c->colq, col, entry);
- containercalccols(col->c, 1, 0);
-}
-
-/* delete column */
-static void
-coldel(struct Column *col)
-{
- struct Row *row;
-
- while ((row = TAILQ_FIRST(&col->rowq)) != NULL)
- rowdel(row);
- coldetach(col);
- XDestroyWindow(dpy, col->div);
- free(col);
-}
-
-/* delete container */
-static void
-containerdel(struct Container *c)
-{
- struct Column *col;
- int i;
-
- containerdelfocus(c);
- containerdelraise(c);
- if (wm.focused == c)
- wm.focused = NULL;
- TAILQ_REMOVE(&wm.focusq, c, entry);
- while ((col = TAILQ_FIRST(&c->colq)) != NULL)
- coldel(col);
- if (c->pix != None)
- XFreePixmap(dpy, c->pix);
- XDestroyWindow(dpy, c->frame);
- for (i = 0; i < BORDER_LAST; i++)
- XDestroyWindow(dpy, c->curswin[i]);
- free(c);
-}
-
-/* add column to container */
-static void
-containeraddcol(struct Container *c, struct Column *col, struct Column *prev)
-{
- struct Container *oldc;
-
- oldc = col->c;
- col->c = c;
- c->selcol = col;
- c->ncols++;
- if (prev == NULL || TAILQ_EMPTY(&c->colq))
- TAILQ_INSERT_HEAD(&c->colq, col, entry);
- else
- TAILQ_INSERT_AFTER(&c->colq, prev, col, entry);
- XReparentWindow(dpy, col->div, c->frame, 0, 0);
- containercalccols(c, 1, 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 = (struct Column){ };
- TAILQ_INIT(&col->rowq);
- col->div = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent, CWCursor,
- &(XSetWindowAttributes){.cursor = theme.cursors[CURSOR_H]});
- return col;
-}
-
-/* add row to column */
-static void
-coladdrow(struct Column *col, struct Row *row, struct Row *prev)
-{
- struct Container *c;
- struct Column *oldcol;
-
- c = col->c;
- oldcol = row->col;
- row->col = col;
- col->selrow = row;
- col->nrows++;
- if (prev == NULL || TAILQ_EMPTY(&col->rowq))
- TAILQ_INSERT_HEAD(&col->rowq, row, entry);
- else
- TAILQ_INSERT_AFTER(&col->rowq, prev, row, entry);
- colcalcrows(col, 1, 0); /* set row->y, row->h, etc */
- XReparentWindow(dpy, row->div, c->frame, col->x + col->w, c->b);
- XReparentWindow(dpy, row->bar, c->frame, col->x, row->y);
- XReparentWindow(dpy, row->frame, c->frame, col->x, row->y);
- XMapWindow(dpy, row->bar);
- XMapWindow(dpy, row->frame);
- if (oldcol != NULL && oldcol->nrows == 0) {
- coldel(oldcol);
- }
-}
-
-/* create new row */
-static struct Row *
-rownew(void)
-{
- struct Row *row;
-
- row = emalloc(sizeof(*row));
- *row = (struct Row){
- .pixbar = None,
- };
- TAILQ_INIT(&row->tabq);
- row->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- row->bar = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- row->bl = XCreateWindow(dpy, row->bar, 0, 0, config.titlewidth, config.titlewidth, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- row->pixbl = XCreatePixmap(dpy, row->bl, config.titlewidth, config.titlewidth, depth);
- row->br = XCreateWindow(dpy, row->bar, 0, 0, config.titlewidth, config.titlewidth, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- row->div = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
- CopyFromParent, InputOnly, CopyFromParent, CWCursor,
- &(XSetWindowAttributes){.cursor = theme.cursors[CURSOR_V]});
- row->pixbr = XCreatePixmap(dpy, row->bl, config.titlewidth, config.titlewidth, depth);
- XMapWindow(dpy, row->bl);
- XMapWindow(dpy, row->br);
- XDefineCursor(dpy, row->bl, theme.cursors[CURSOR_HAND]);
- XDefineCursor(dpy, row->br, theme.cursors[CURSOR_PIRATE]);
- return row;
-}
-
-/* add tab to row */
-static void
-rowaddtab(struct Row *row, struct Tab *tab, struct Tab *prev)
-{
- struct Row *oldrow;
-
- oldrow = tab->row;
- tab->row = row;
- row->seltab = tab;
- row->ntabs++;
- if (prev == NULL || TAILQ_EMPTY(&row->tabq))
- TAILQ_INSERT_HEAD(&row->tabq, (struct Object *)tab, entry);
- else
- TAILQ_INSERT_AFTER(&row->tabq, (struct Object *)prev, (struct Object *)tab, entry);
- rowcalctabs(row); /* set tab->x, tab->w, etc */
- if (tab->title == None) {
- tab->title = XCreateWindow(dpy, row->bar, tab->x, 0, tab->w, config.titlewidth, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- } else {
- XReparentWindow(dpy, tab->title, row->bar, tab->x, 0);
- }
- XReparentWindow(dpy, tab->frame, row->frame, 0, 0);
- XMapWindow(dpy, tab->frame);
- XMapWindow(dpy, tab->title);
- 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 Monitor *mon, int desk)
-{
- return mon->seldesk == desk;
-}
-
-/* (un)show desktop */
-static void
-deskshow(int show)
-{
- struct Container *c;
-
- TAILQ_FOREACH(c, &wm.focusq, entry)
- if (!c->isminimized)
- containerhide(c, show);
- wm.showingdesk = show;
- ewmhsetshowingdesktop(show);
-}
-
-/* change desktop */
-static void
-deskfocus(struct Monitor *mon, int desk, int focus)
-{
- void tabfocus(struct Tab *t, int gotodesk);
- struct Container *c;
-
- if (desk < 0 || desk >= config.ndesktops || desk == wm.selmon->seldesk)
- return;
- if (!deskisvisible(mon, desk)) {
- /* unhide cointainers of new current desktop
- * hide containers of previous current desktop */
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- if (!c->isminimized && c->desk == desk) {
- containerhide(c, 0);
- } else if (!c->issticky && c->desk == mon->seldesk) {
- containerhide(c, 1);
- }
- }
- }
-
- /* update current desktop */
- wm.selmon = mon;
- wm.selmon->seldesk = desk;
- if (wm.showingdesk)
- deskshow(0);
- ewmhsetcurrentdesktop(desk);
-
- /* focus client on the new current desktop */
- if (focus) {
- c = getnextfocused(mon, desk);
- if (c != NULL) {
- tabfocus(c->selcol->selrow->seltab, 0);
- } else {
- tabfocus(NULL, 0);
- }
- }
-}
-
-/* snap to edge */
-static void
-snaptoedge(int *x, int *y, int w, int h)
-{
- struct Container *c;
-
- if (config.snap <= 0)
- return;
- if (abs(*y - wm.selmon->wy) < config.snap)
- *y = wm.selmon->wy;
- if (abs(*y + h - wm.selmon->wy - wm.selmon->wh) < config.snap)
- *y = wm.selmon->wy + wm.selmon->wh - h;
- if (abs(*x - wm.selmon->wx) < config.snap)
- *x = wm.selmon->wx;
- if (abs(*x + w - wm.selmon->wx - wm.selmon->ww) < config.snap)
- *x = wm.selmon->wx + wm.selmon->ww - w;
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- if (!c->isminimized && c->mon == wm.selmon &&
- (c->issticky || c->desk == wm.selmon->seldesk)) {
- if (*x + w >= c->x && *x <= c->x + c->w) {
- if (abs(*y + h - c->y) < config.snap) {
- *y = c->y - h;
- }
- if (abs(*y - c->y) < config.snap) {
- *y = c->y;
- }
- if (abs(*y + h - c->y - c->h) < config.snap) {
- *y = c->y + c->h - h;
- }
- if (abs(*y - c->y - c->h) < config.snap) {
- *y = c->y + c->h;
- }
- }
- if (*y + h >= c->y && *y <= c->y + c->h) {
- if (abs(*x + w - c->x) < config.snap) {
- *x = c->x - w;
- }
- if (abs(*x - c->x) < config.snap) {
- *x = c->x;
- }
- if (abs(*x + w - c->x - c->w) < config.snap) {
- *x = c->x + c->w - w;
- }
- if (abs(*x - c->x - c->w) < config.snap) {
- *x = c->x + c->w;
- }
- }
- }
- }
-}
-
-/* move container x pixels to the right and y pixels down */
-static void
-containerincrmove(struct Container *c, int x, int y, int done)
-{
- struct Monitor *monto;
- struct Object *t;
- struct Tab *tab;
-
- if (c == NULL || c->isminimized || c->ismaximized || c->isfullscreen)
- return;
- c->nx += x;
- c->ny += y;
- c->x = c->nx;
- c->y = c->ny;
- snaptoedge(&c->x, &c->y, c->w, c->h);
- if (done) {
- containermoveresize(c);
- } else {
- XMoveWindow(dpy, c->frame, c->x, c->y);
- TAB_FOREACH_BEGIN(c, t){
- tab = (struct Tab *)t;
- winnotify(tab->obj.win, c->x + col->x, c->y + row->y + config.titlewidth, tab->winw, tab->winh);
- }TAB_FOREACH_END
- }
- if (!c->issticky) {
- monto = getmon(c->nx + c->nw / 2, c->ny + c->nh / 2);
- if (monto != NULL && monto != c->mon) {
- containersendtodesk(c, monto, monto->seldesk, 0, 0);
- if (wm.focused == c) {
- deskfocus(monto, monto->seldesk, 0);
- }
- }
- }
-}
-
-/* create new tab */
-static struct Tab *
-tabnew(Window win, Window leader, int ignoreunmap)
-{
- struct Tab *tab;
-
- tab = emalloc(sizeof(*tab));
- *tab = (struct Tab){
- .ignoreunmap = ignoreunmap,
- .pix = None,
- .pixtitle = None,
- .title = None,
- .leader = leader,
- .obj.win = win,
- .obj.type = TYPE_NORMAL,
- };
- TAILQ_INIT(&tab->dialq);
- TAILQ_INIT(&tab->menuq);
- ((struct Object *)tab)->type = TYPE_NORMAL;
- tab->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, CopyFromParent, visual, clientmask, &clientswa),
- XReparentWindow(dpy, tab->obj.win, tab->frame, 0, 0);
- XMapWindow(dpy, tab->obj.win);
- icccmwmstate(win, NormalState);
- clientsincr();
- return tab;
-}
-
-/* clear window urgency */
-static void
-tabclearurgency(struct Tab *tab)
-{
- XWMHints wmh = {0};
-
- XSetWMHints(dpy, tab->obj.win, &wmh);
- tab->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 *tab, int gotodesk)
-{
- struct Container *c;
- struct Dialog *dial;
-
- wm.prevfocused = wm.focused;
- if (tab == NULL) {
- wm.focused = NULL;
- XSetInputFocus(dpy, wm.focuswin, RevertToParent, CurrentTime);
- ewmhsetactivewindow(None);
- } else {
- c = tab->row->col->c;
- if (!c->isfullscreen && getfullscreen(c->mon, c->desk) != NULL)
- return; /* we should not focus a client below a fullscreen client */
- wm.focused = c;
- tab->row->seltab = tab;
- tab->row->col->selrow = tab->row;
- tab->row->col->c->selcol = tab->row->col;
- if (gotodesk)
- deskfocus(c->mon, c->issticky ? c->mon->seldesk : c->desk, 0);
- if (tab->row->col->maxrow != NULL && tab->row->col->maxrow != tab->row)
- rowstack(tab->row->col, tab->row);
- XRaiseWindow(dpy, tab->frame);
- if (c->isshaded) {
- XSetInputFocus(dpy, c->frame, RevertToParent, CurrentTime);
- } else if (!TAILQ_EMPTY(&tab->dialq)) {
- dial = (struct Dialog *)TAILQ_FIRST(&tab->dialq);
- XRaiseWindow(dpy, dial->frame);
- XSetInputFocus(dpy, dial->obj.win, RevertToParent, CurrentTime);
- } else {
- XSetInputFocus(dpy, tab->obj.win, RevertToParent, CurrentTime);
- }
- ewmhsetactivewindow(tab->obj.win);
- if (tab->isurgent)
- tabclearurgency(tab);
- menumap(tab);
- containeraddfocus(c);
- containerdecorate(c, NULL, NULL, 1, 0);
- containerminimize(c, 0, 0);
- containerraise(c, c->isfullscreen, c->layer);
- shodgrouptab(c);
- shodgroupcontainer(c);
- ewmhsetstate(c);
- }
- if (wm.prevfocused != NULL && wm.prevfocused != wm.focused) {
- TAILQ_REMOVE(&wm.focusq, wm.prevfocused, entry);
- TAILQ_INSERT_TAIL(&wm.focusq, wm.prevfocused, entry);
- if (tab != wm.prevfocused->selcol->selrow->seltab)
- menuunmap(wm.prevfocused->selcol->selrow->seltab);
- containerdecorate(wm.prevfocused, NULL, NULL, 1, 0);
- ewmhsetstate(wm.prevfocused);
- }
-}
-
-/* update tab title */
-static void
-winupdatetitle(Window win, char **name)
-{
- free(*name);
- *name = getwinname(win);
-}
-
-/* try to attach tab in a client of specified client list */
-static int
-tryattach(struct ContainerQueue *queue, struct Tab *det, int xroot, int yroot)
-{
- enum { CREATNOTHING = 0x0, CREATROW = 0x1, CREATCOL = 0x2 };
- struct Container *c;
- struct Column *col, *ncol;
- struct Row *row, *nrow;
- struct Tab *tab;
- struct Object *t;
- int flag, rowy, rowh;
-
- if (det == NULL)
- return 0;
- flag = 0;
- nrow = NULL;
- ncol = NULL;
- for (c = TAILQ_FIRST(queue); c != NULL; c = TAILQ_NEXT(c, raiseentry)) {
- if (c->ishidden || xroot < c->x || xroot >= c->x + c->w || yroot < c->y || yroot >= c->y + c->h)
- continue;
- tab = NULL;
- TAILQ_FOREACH(col, &c->colq, entry) {
- row = NULL;
- if (xroot - c->x >= col->x - DROPPIXELS &&
- xroot - c->x < col->x + col->w + DROPPIXELS) {
- if (yroot - c->y < c->b) {
- flag = CREATROW;
- goto done;
- }
- rowy = c->b;
- TAILQ_FOREACH(row, &col->rowq, entry) {
- if (col->maxrow != NULL) {
- if (row == col->maxrow) {
- rowh = c->h - 2 * c->b - (col->nrows - 1) * config.titlewidth;
- } else {
- rowh = config.titlewidth;
- }
- } else {
- rowh = row->h;
- }
- if (yroot - c->y >= rowy &&
- yroot - c->y < rowy + config.titlewidth) {
- TAILQ_FOREACH(t, &row->tabq, entry) {
- tab = (struct Tab *)t;
- if (xroot - c->x + col->x < col->x + tab->x + tab->w / 2) {
- tab = (struct Tab *)TAILQ_PREV(t, Queue, entry);
- goto done;
- }
- }
- goto done;
- }
- if (yroot - c->y >= rowy + rowh - DROPPIXELS &&
- yroot - c->y < rowy + rowh + config.divwidth) {
- flag = CREATROW;
- goto done;
- }
- rowy += rowh + config.divwidth;
- }
- }
- row = NULL;
- if (xroot - c->x >= col->x + col->w - DROPPIXELS &&
- xroot - c->x < col->x + col->w + config.divwidth + DROPPIXELS) {
- flag = CREATCOL | CREATROW;
- goto done;
- }
- }
- if (xroot - c->x < c->b + DROPPIXELS) {
- flag = CREATCOL | CREATROW;
- goto done;
- }
- break;
- }
- return 0;
-done:
- if (flag & CREATCOL) {
- ncol = colnew();
- containeraddcol(c, ncol, col);
- col = ncol;
- }
- if (flag & CREATROW) {
- nrow = rownew();
- coladdrow(col, nrow, row);
- row = nrow;
- }
- rowaddtab(row, det, tab);
- if (ncol != NULL)
- containercalccols(c, 1, 1);
- else if (nrow != NULL)
- colcalcrows(col, 1, 1);
- else
- rowcalctabs(row);
- tabfocus(det, 0);
- XMapSubwindows(dpy, c->frame);
- /* no need to call shodgrouptab() and shodgroupcontainer(); tabfocus() already calls them */
- ewmhsetclientsstacking();
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, 0);
- return 1;
-}
-
-/* create new dialog */
-static struct Dialog *
-dialognew(Window win, int maxw, int maxh, int ignoreunmap)
-{
- struct Dialog *dial;
-
- dial = emalloc(sizeof(*dial));
- *dial = (struct Dialog){
- .pix = None,
- .maxw = maxw,
- .maxh = maxh,
- .ignoreunmap = ignoreunmap,
- .obj.win = win,
- .obj.type = TYPE_DIALOG,
- };
- dial->frame = XCreateWindow(dpy, root, 0, 0, maxw, maxh, 0, depth, CopyFromParent, visual, clientmask, &clientswa),
- XReparentWindow(dpy, dial->obj.win, dial->frame, 0, 0);
- XMapWindow(dpy, dial->obj.win);
- return dial;
-}
-
-/* create new splash screen */
-static struct Splash *
-splashnew(Window win, int w, int h)
-{
- struct Splash *splash;
-
- splash = emalloc(sizeof(*splash));
- *splash = (struct Splash){
- .obj.win = win,
- .obj.type = TYPE_SPLASH,
- .w = w,
- .h = h,
- };
- ((struct Object *)splash)->type = TYPE_SPLASH;
- XReparentWindow(dpy, win, root, 0, 0);
- return splash;
-}
-
-/* center splash screen on monitor and raise it above other windows */
-static void
-splashplace(struct Splash *splash)
-{
- Window wins[2];
- fitmonitor(wm.selmon, &splash->x, &splash->y, &splash->w, &splash->h, 0.5);
- splash->x = wm.selmon->wx + (wm.selmon->ww - splash->w) / 2;
- splash->y = wm.selmon->wy + (wm.selmon->wh - splash->h) / 2;
- wins[1] = splash->obj.win;
- wins[0] = wm.layerwins[LAYER_SPLASH];
- XMoveWindow(dpy, splash->obj.win, splash->x, splash->y);
- XRestackWindows(dpy, wins, 2);
-}
-
-/* delete splash screen window */
-static int
-splashdel(struct Object *obj, int dummy)
-{
- struct Splash *splash;
-
- splash = (struct Splash *)obj;
- (void)dummy;
- TAILQ_REMOVE(&wm.splashq, (struct Object *)splash, entry);
- icccmdeletestate(splash->obj.win);
- free(splash);
- return 0;
-}
-
-/* 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;
-
- mon = emalloc(sizeof *mon);
- *mon = (struct Monitor){
- .mx = info->x_org,
- .my = info->y_org,
- .mw = info->width,
- .mh = info->height,
- .wx = info->x_org,
- .wy = info->y_org,
- .ww = info->width,
- .wh = info->height,
- };
- mon->seldesk = 0;
- TAILQ_INSERT_TAIL(&wm.monq, mon, entry);
-}
-
-/* delete monitor and set monitor of clients on it to NULL */
-static void
-mondel(struct Monitor *mon)
-{
- struct Container *c;
-
- TAILQ_REMOVE(&wm.monq, mon, entry);
- TAILQ_FOREACH(c, &wm.focusq, entry)
- if (c->mon == mon)
- c->mon = NULL;
- free(mon);
-}
-
-/* update the list of monitors */
-static void
-monupdate(void)
-{
- XineramaScreenInfo *info = NULL;
- XineramaScreenInfo *unique = NULL;
- struct Monitor *mon, *tmp;
- struct Container *c, *focus;
- struct Object *t, *m, *s;
- 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 = TAILQ_FIRST(&wm.monq);
- while (mon != NULL) {
- 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 = TAILQ_NEXT(mon, entry);
- 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;
- TAILQ_FOREACH(mon, &wm.monq, entry) {
- 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 = TAILQ_FIRST(&wm.monq);
-
- /* send containers which do not belong to a window to selected desktop */
- focus = NULL;
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- if (!c->isminimized && c->mon == NULL) {
- focus = c;
- containersendtodesk(c, wm.selmon, wm.selmon->seldesk, 1, 0);
- containermoveresize(c);
-
- /* move menus to new monitor */
- TAB_FOREACH_BEGIN(c, t) {
- TAILQ_FOREACH(m, &((struct Tab *)t)->menuq, entry) {
- menuplace((struct Menu *)m);
- }
- } TAB_FOREACH_END
- }
- }
- TAILQ_FOREACH(s, &wm.splashq, entry)
- splashplace((struct Splash *)s);
- if (focus != NULL) /* if a contained changed desktop, focus it */
- tabfocus(focus->selcol->selrow->seltab, 1);
-
- free(unique);
-}
-
-/* update window area and dock area of monitor */
-static void
-monupdatearea(void)
-{
- struct Monitor *mon;
- struct Bar *bar;
- struct Object *p;
- struct Container *c;
- int t, b, l, r;
-
- TAILQ_FOREACH(mon, &wm.monq, entry) {
- mon->wx = mon->mx;
- mon->wy = mon->my;
- mon->ww = mon->mw;
- mon->wh = mon->mh;
- t = b = l = r = 0;
- if (mon == TAILQ_FIRST(&wm.monq) && dock.mapped) {
- switch (config.dockgravity[0]) {
- case 'N':
- t = config.dockwidth;
- break;
- case 'S':
- b = config.dockwidth;
- break;
- case 'W':
- l = config.dockwidth;
- break;
- case 'E':
- default:
- r = config.dockwidth;
- break;
- }
- }
- TAILQ_FOREACH(p, &wm.barq, entry) {
- bar = (struct Bar *)p;
- if (bar->strut[STRUT_TOP] != 0) {
- if (bar->strut[STRUT_TOP] >= mon->my &&
- bar->strut[STRUT_TOP] < mon->my + mon->mh &&
- (!bar->partial ||
- (bar->strut[STRUT_TOP_START_X] >= mon->mx &&
- bar->strut[STRUT_TOP_END_X] <= mon->mx + mon->mw))) {
- t = max(t, bar->strut[STRUT_TOP] - mon->my);
- }
- } else if (bar->strut[STRUT_BOTTOM] != 0) {
- if (screenh - bar->strut[STRUT_BOTTOM] <= mon->my + mon->mh &&
- screenh - bar->strut[STRUT_BOTTOM] > mon->my &&
- (!bar->partial ||
- (bar->strut[STRUT_BOTTOM_START_X] >= mon->mx &&
- bar->strut[STRUT_BOTTOM_END_X] <= mon->mx + mon->mw))) {
- b = max(b, bar->strut[STRUT_BOTTOM] - (screenh - (mon->my + mon->mh)));
- }
- } else if (bar->strut[STRUT_LEFT] != 0) {
- if (bar->strut[STRUT_LEFT] >= mon->mx &&
- bar->strut[STRUT_LEFT] < mon->mx + mon->mw &&
- (!bar->partial ||
- (bar->strut[STRUT_LEFT_START_Y] >= mon->my &&
- bar->strut[STRUT_LEFT_END_Y] <= mon->my + mon->mh))) {
- l = max(l, bar->strut[STRUT_LEFT] - mon->mx);
- }
- } else if (bar->strut[STRUT_RIGHT] != 0) {
- if (screenw - bar->strut[STRUT_RIGHT] <= mon->mx + mon->mw &&
- screenw - bar->strut[STRUT_RIGHT] > mon->mx &&
- (!bar->partial ||
- (bar->strut[STRUT_RIGHT_START_Y] >= mon->my &&
- bar->strut[STRUT_RIGHT_END_Y] <= mon->my + mon->mh))) {
- r = max(r, bar->strut[STRUT_RIGHT] - (screenw - (mon->mx + mon->mw)));
- }
- }
- }
- mon->wy += t;
- mon->wh -= t + b;
- mon->wx += l;
- mon->ww -= l + r;
- }
- TAILQ_FOREACH(c, &wm.focusq, entry) {
- if (c->ismaximized) {
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, 0);
- }
- }
-}
-
-/* select window input events, grab mouse button presses, and clear its border */
-static void
-preparewin(Window win)
-{
- XSelectInput(dpy, win, 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 - config.borderwidth * 2);
- *h = min(*h, wm.selmon->wh - config.borderwidth);
- *x = wm.selmon->wx + (wm.selmon->ww - *w) / 2 - config.borderwidth;
- *y = 0;
- *fw = *w + config.borderwidth * 2;
- *fh = *h + config.borderwidth;
-}
-
-/* create notification window */
-static void
-notifnew(Window win, int w, int h)
-{
- struct Notification *notif;
-
- notif = emalloc(sizeof(*notif));
- *notif = (struct Notification){
- .w = w + 2 * config.borderwidth,
- .h = h + 2 * config.borderwidth,
- .pix = None,
- .obj.type = TYPE_NOTIFICATION,
- .obj.win = win,
- };
- TAILQ_INSERT_TAIL(&wm.notifq, (struct Object *)notif, entry);
- notif->frame = XCreateWindow(
- dpy, root, 0, 0, 1, 1, 0,
- depth, CopyFromParent, visual,
- clientmask,
- &(XSetWindowAttributes){
- .event_mask = SubstructureNotifyMask | SubstructureRedirectMask,
- .colormap = colormap
- }
- );
- XReparentWindow(dpy, notif->obj.win, notif->frame, 0, 0);
- XMapWindow(dpy, notif->obj.win);
-}
-
-/* place notifications */
-static void
-notifplace(void)
-{
- struct Object *n;
- struct Notification *notif;
- int x, y, h;
-
- h = 0;
- TAILQ_FOREACH(n, &wm.notifq, entry) {
- notif = (struct Notification *)n;
- x = TAILQ_FIRST(&wm.monq)->wx;
- y = TAILQ_FIRST(&wm.monq)->wy;
- switch (config.notifgravity[0]) {
- case 'N':
- switch (config.notifgravity[1]) {
- case 'W':
- break;
- case 'E':
- x += TAILQ_FIRST(&wm.monq)->ww - notif->w;
- break;
- default:
- x += (TAILQ_FIRST(&wm.monq)->ww - notif->w) / 2;
- break;
- }
- break;
- case 'S':
- switch(config.notifgravity[1]) {
- case 'W':
- y += TAILQ_FIRST(&wm.monq)->wh - notif->h;
- break;
- case 'E':
- x += TAILQ_FIRST(&wm.monq)->ww - notif->w;
- y += TAILQ_FIRST(&wm.monq)->wh - notif->h;
- break;
- default:
- x += (TAILQ_FIRST(&wm.monq)->ww - notif->w) / 2;
- y += TAILQ_FIRST(&wm.monq)->wh - notif->h;
- break;
- }
- break;
- case 'W':
- y += (TAILQ_FIRST(&wm.monq)->wh - notif->h) / 2;
- break;
- case 'C':
- x += (TAILQ_FIRST(&wm.monq)->ww - notif->w) / 2;
- y += (TAILQ_FIRST(&wm.monq)->wh - notif->h) / 2;
- break;
- case 'E':
- x += TAILQ_FIRST(&wm.monq)->ww - notif->w;
- y += (TAILQ_FIRST(&wm.monq)->wh - notif->h) / 2;
- break;
- default:
- x += TAILQ_FIRST(&wm.monq)->ww - notif->w;
- break;
- }
-
- if (config.notifgravity[0] == 'S')
- y -= h;
- else
- y += h;
- h += notif->h + config.notifgap + config.borderwidth * 2;
-
- XMoveResizeWindow(dpy, notif->frame, x, y, notif->w, notif->h);
- XMoveResizeWindow(dpy, notif->obj.win, config.borderwidth, config.borderwidth, notif->w - 2 * config.borderwidth, notif->h - 2 * config.borderwidth);
- XMapWindow(dpy, notif->frame);
- if (notif->pw != notif->w || notif->ph != notif->h) {
- notifdecorate(notif);
- }
- winnotify(notif->obj.win, x + config.borderwidth, y + config.borderwidth, notif->w - 2 * config.borderwidth, notif->h - 2 * config.borderwidth);
- }
-}
-
-/* delete notification */
-static int
-notifdel(struct Object *obj, int dummy)
-{
- struct Notification *notif;
-
- (void)dummy;
- notif = (struct Notification *)obj;
- TAILQ_REMOVE(&wm.notifq, (struct Object *)notif, entry);
- if (notif->pix != None)
- XFreePixmap(dpy, notif->pix);
- XReparentWindow(dpy, notif->obj.win, root, 0, 0);
- XDestroyWindow(dpy, notif->frame);
- free(notif);
- notifplace();
- return 0;
-}
-
-/* fill strut array of bar */
-static void
-barstrut(struct Bar *bar)
-{
- unsigned long *arr;
- unsigned long l, i;
-
- for (i = 0; i < STRUT_LAST; i++)
- bar->strut[i] = 0;
- bar->partial = 1;
- l = getcardprop(bar->obj.win, atoms[_NET_WM_STRUT_PARTIAL], &arr);
- if (arr == NULL) {
- bar->partial = 0;
- l = getcardprop(bar->obj.win, atoms[_NET_WM_STRUT], &arr);
- if (arr == NULL) {
- return;
- }
- }
- for (i = 0; i < STRUT_LAST && i < l; i++)
- bar->strut[i] = arr[i];
- XFree(arr);
-}
-
-/* delete bar */
-static int
-bardel(struct Object *obj, int dummy)
-{
- struct Bar *bar;
-
- (void)dummy;
- bar = (struct Bar *)obj;
- TAILQ_REMOVE(&wm.barq, (struct Object *)bar, entry);
- free(bar);
- monupdatearea();
- return 0;
-}
-
-/* decorate dock */
-static void
-dockdecorate(void)
-{
- XGCValues val;
-
- if (dock.pw != dock.w || dock.ph != dock.h || dock.pix == None) {
- if (dock.pix != None)
- XFreePixmap(dpy, dock.pix);
- dock.pix = XCreatePixmap(dpy, dock.win, dock.w, dock.h, depth);
- }
- dock.pw = dock.w;
- dock.ph = dock.h;
- val.fill_style = FillSolid;
- val.foreground = theme.dock[COLOR_MID];
- XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val);
- XFillRectangle(dpy, dock.pix, gc, 0, 0, dock.w, dock.h);
-
- drawrectangle(dock.pix, 0, 0, dock.w, dock.h, theme.dock[COLOR_LIGHT], theme.dock[COLOR_DARK]);
-
- XCopyArea(dpy, dock.pix, dock.win, gc, 0, 0, dock.w, dock.h, 0, 0);
-}
-
-/* update dock position; create it, if necessary */
-static void
-dockupdate(void)
-{
- struct Object *p;
- struct Dockapp *dapp;
- Window wins[2];
- int size;
- int n;
-
- size = 0;
- TAILQ_FOREACH(p, &dock.dappq, entry) {
- dapp = (struct Dockapp *)p;
- switch (config.dockgravity[0]) {
- case 'N':
- dapp->x = DOCKBORDER + size;
- dapp->y = DOCKBORDER;
- n = dapp->w / config.dockspace + (dapp->w % config.dockspace ? 1 : 0);
- n *= config.dockspace;
- dapp->x += max(0, (n - dapp->w) / 2);
- dapp->y += max(0, (config.dockwidth - dapp->h) / 2);
- break;
- case 'S':
- dapp->x = DOCKBORDER + size;
- dapp->y = DOCKBORDER;
- n = dapp->w / config.dockspace + (dapp->w % config.dockspace ? 1 : 0);
- n *= config.dockspace;
- dapp->x += max(0, (n - dapp->w) / 2);
- dapp->y += max(0, (config.dockwidth - dapp->h) / 2);
- break;
- case 'W':
- dapp->x = DOCKBORDER;
- dapp->y = DOCKBORDER + size;
- n = dapp->h / config.dockspace + (dapp->h % config.dockspace ? 1 : 0);
- n *= config.dockspace;
- dapp->x += max(0, (config.dockwidth - dapp->w) / 2);
- dapp->y += max(0, (n - dapp->h) / 2);
- break;
- case 'E':
- default:
- dapp->y = DOCKBORDER + size;
- dapp->x = DOCKBORDER;
- n = dapp->h / config.dockspace + (dapp->h % config.dockspace ? 1 : 0);
- n *= config.dockspace;
- dapp->x += max(0, (config.dockwidth - dapp->w) / 2);
- dapp->y += max(0, (n - dapp->h) / 2);
- break;
- }
- size += n;
- }
- if (size == 0) {
- XUnmapWindow(dpy, dock.win);
- dock.mapped = 0;
- return;
- }
- dock.mapped = 1;
- size += DOCKBORDER * 2;
- switch (config.dockgravity[0]) {
- case 'N':
- dock.h = config.dockwidth;
- dock.y = 0;
- break;
- case 'S':
- dock.h = config.dockwidth;
- dock.y = TAILQ_FIRST(&wm.monq)->mh - config.dockwidth;
- break;
- case 'W':
- dock.w = config.dockwidth;
- dock.x = 0;
- break;
- case 'E':
- default:
- dock.w = config.dockwidth;
- dock.x = TAILQ_FIRST(&wm.monq)->mw - config.dockwidth;
- dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
- dock.y = TAILQ_FIRST(&wm.monq)->mh / 2 - size / 2;
- break;
- }
- if (config.dockgravity[0] == 'N' || config.dockgravity[0] == 'S') {
- switch (config.dockgravity[1]) {
- case 'W':
- dock.w = min(size, TAILQ_FIRST(&wm.monq)->mw);
- dock.x = 0;
- break;
- case 'E':
- dock.w = min(size, TAILQ_FIRST(&wm.monq)->mw);
- dock.x = TAILQ_FIRST(&wm.monq)->mw - size;
- break;
- default:
- dock.w = min(size, TAILQ_FIRST(&wm.monq)->mw);
- dock.x = TAILQ_FIRST(&wm.monq)->mw / 2 - size / 2;
- break;
- }
- } else if (config.dockgravity[0] != '\0') {
- switch (config.dockgravity[1]) {
- case 'N':
- dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
- dock.y = 0;
- break;
- case 'S':
- dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
- dock.y = TAILQ_FIRST(&wm.monq)->mh - size;
- break;
- default:
- dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
- dock.y = TAILQ_FIRST(&wm.monq)->mh / 2 - size / 2;
- break;
- }
- }
- TAILQ_FOREACH(p, &dock.dappq, entry) {
- dapp = (struct Dockapp *)p;
- XMoveWindow(dpy, dapp->obj.win, dapp->x, dapp->y);
- winnotify(dapp->obj.win, dock.x + dapp->x, dock.y + dapp->y, dapp->w, dapp->h);
- }
- dockdecorate();
- wins[0] = wm.layerwins[LAYER_DOCK];
- wins[1] = dock.win;
- XMoveResizeWindow(dpy, dock.win, dock.x, dock.y, dock.w, dock.h);
- XRestackWindows(dpy, wins, 2);
- XMapWindow(dpy, dock.win);
- XMapSubwindows(dpy, dock.win);
-}
-
-/* create dockapp */
-static void
-dockappnew(Window win, int w, int h, int dockpos, int ignoreunmap)
-{
- struct Dockapp *dapp;
- struct Object *prev;
-
- dapp = emalloc(sizeof(*dapp));
- *dapp = (struct Dockapp){
- .obj.type = TYPE_DOCKAPP,
- .obj.win = win,
- .w = w,
- .h = h,
- .ignoreunmap = ignoreunmap,
- .dockpos = dockpos,
- };
- TAILQ_FOREACH_REVERSE(prev, &dock.dappq, Queue, entry)
- if (((struct Dockapp *)prev)->dockpos <= dockpos)
- break;
- if (prev != NULL) {
- TAILQ_INSERT_AFTER(&dock.dappq, prev, (struct Object *)dapp, entry);
- } else {
- TAILQ_INSERT_HEAD(&dock.dappq, (struct Object *)dapp, entry);
- }
-}
-
-/* delete dockapp */
-static int
-dockappdel(struct Object *obj, int ignoreunmap)
-{
- struct Dockapp *dapp;
-
- dapp = (struct Dockapp *)obj;
- if (ignoreunmap && dapp->ignoreunmap) {
- dapp->ignoreunmap--;
- return 0;
- }
- TAILQ_REMOVE(&dock.dappq, (struct Object *)dapp, entry);
- XReparentWindow(dpy, dapp->obj.win, root, 0, 0);
- free(dapp);
- dockupdate();
- monupdatearea();
- return 0;
-}
-
-/* create new menu */
-static struct Menu *
-menunew(Window win, int x, int y, int w, int h, int ignoreunmap)
-{
- struct Menu *menu;
-
- menu = emalloc(sizeof(*menu));
- *menu = (struct Menu){
- .titlebar = None,
- .button = None,
- .obj.win = win,
- .obj.type = TYPE_MENU,
- .pix = None,
- .pixbutton = None,
- .pixtitlebar = None,
- .x = x - config.borderwidth,
- .y = y - config.borderwidth,
- .w = w + config.borderwidth * 2,
- .h = h + config.borderwidth * 2 + config.titlewidth,
- .ignoreunmap = ignoreunmap,
- };
- menu->frame = XCreateWindow(dpy, root, 0, 0,
- w + config.borderwidth * 2,
- h + config.borderwidth * 2 + config.titlewidth, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa),
- menu->titlebar = XCreateWindow(dpy, menu->frame, config.borderwidth, config.borderwidth,
- max(1, menu->w - 2 * config.borderwidth - config.titlewidth),
- config.titlewidth, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- menu->button = XCreateWindow(dpy, menu->frame, menu->w - config.borderwidth - config.titlewidth, config.borderwidth,
- config.titlewidth, config.titlewidth, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- menu->pixbutton = XCreatePixmap(dpy, menu->button, config.titlewidth, config.titlewidth, depth);
- XDefineCursor(dpy, menu->button, theme.cursors[CURSOR_PIRATE]);
- XReparentWindow(dpy, menu->obj.win, menu->frame, config.borderwidth, config.borderwidth + config.titlewidth);
- XMapWindow(dpy, menu->obj.win);
- XMapWindow(dpy, menu->button);
- XMapWindow(dpy, menu->titlebar);
- return menu;
-}
-
-/* copy pixmar into exposed window */
-static void
-copypixmap(Window win)
-{
- Pixmap pix;
- int pw, ph;
-
- if (getexposed(win, &pix, &pw, &ph)) {
- XCopyArea(dpy, pix, win, gc, 0, 0, pw, ph, 0, 0);
- }
-}
-
-/* add splash screen and center it on the screen */
-static void
-managesplash(struct Splash *splash)
-{
- TAILQ_INSERT_HEAD(&wm.splashq, (struct Object *)splash, entry);
- icccmwmstate(splash->obj.win, NormalState);
- splashplace(splash);
- XMapWindow(dpy, splash->obj.win);
-}
-
-/* add dialog window into tab */
-static void
-managedialog(struct Tab *tab, struct Dialog *dial)
-{
- dial->tab = tab;
- TAILQ_INSERT_HEAD(&tab->dialq, (struct Object *)dial, entry);
- XReparentWindow(dpy, dial->frame, tab->frame, 0, 0);
- icccmwmstate(dial->obj.win, NormalState);
- dialogmoveresize(dial);
- XMapRaised(dpy, dial->frame);
- if (wm.focused != NULL && wm.focused->selcol->selrow->seltab == tab)
- tabfocus(tab, 0);
- ewmhsetclients();
- ewmhsetclientsstacking();
-}
-
-/* assign menu to tab */
-static void
-managemenu(struct Tab *tab, struct Menu *menu)
-{
- menu->tab = tab;
- TAILQ_INSERT_HEAD(&tab->menuq, (struct Object *)menu, entry);
- icccmwmstate(menu->obj.win, NormalState);
- menuplace(menu);
- menudecorate(menu, 0);
- if (wm.focused != NULL && wm.focused->selcol->selrow->seltab == tab)
- tabfocus(tab, 0);
- ewmhsetclients();
- ewmhsetclientsstacking();
-}
-
-/* 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;
- XEvent ev;
- int x, y, fw, fh;
-
- promptcalcgeom(&x, &y, &w, &h, &fw, &fh);
- prompt.frame = XCreateWindow(dpy, root, x, y, fw, fh, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
- prompt.pix = None;
- prompt.ph = prompt.pw = 0;
- XReparentWindow(dpy, win, prompt.frame, config.borderwidth, 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, fw, fh);
- } else {
- copypixmap(ev.xexpose.window);
- }
- }
- 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, config.borderwidth, 0, w, h);
- break;
- case ButtonPress:
- if (ev.xbutton.window != win && ev.xbutton.window != prompt.frame)
- winclose(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] = {wm.layerwins[LAYER_DESKTOP], win};
-
- XRestackWindows(dpy, wins, 2);
- XMapWindow(dpy, win);
-}
-
-/* map dockapp window */
-static void
-managedockapp(Window win, int w, int h, int pos, int ignoreunmap)
-{
- XReparentWindow(dpy, win, dock.win, 0, 0);
- dockappnew(win, w, h, pos, ignoreunmap);
- dockupdate();
- monupdatearea();
-}
-
-/* add notification window into notification queue; and update notification placement */
-static void
-managenotif(Window win, int w, int h)
-{
- notifnew(win, w, h);
- notifplace();
-}
-
-/* create container for tab */
-static void
-managecontainer(struct Container *c, struct Tab *tab, struct Monitor *mon, int desk, int userplaced)
-{
- struct Column *col;
- struct Row *row;
-
- c->mon = mon;
- c->desk = desk;
- row = rownew();
- col = colnew();
- containeraddcol(c, col, NULL);
- coladdrow(col, row, NULL);
- rowaddtab(row, tab, NULL);
- containerredecorate(c, NULL, NULL, 0);
- XMapSubwindows(dpy, c->frame);
- if (!c->isminimized) {
- containerplace(c, mon, desk, userplaced);
- containermoveresize(c);
- containerhide(c, 0);
- tabfocus(tab, 0);
- } else {
- containermoveresize(c);
- }
- /* no need to call shodgrouptab() and shodgroupcontainer(); tabfocus() already calls them */
- ewmhsetwmdesktop(c);
- ewmhsetclients();
- ewmhsetclientsstacking();
-}
-
-/* map bar window */
-static void
-managebar(Window win)
-{
- struct Bar *bar;
- Window wins[2] = {wm.layerwins[LAYER_DOCK], win};
-
- bar = emalloc(sizeof(*bar));
- *bar = (struct Bar){
- .obj.win = win,
- .obj.type = TYPE_DOCK,
- };
- TAILQ_INSERT_HEAD(&wm.barq, (struct Object *)bar, entry);
- XRestackWindows(dpy, wins, 2);
- XMapWindow(dpy, win);
- barstrut(bar);
- monupdatearea();
-}
-
-/* call one of the manage- functions */
-static void
-manage(Window win, int x, int y, int w, int h, int ignoreunmap)
-{
- struct Tab *t;
- struct Container *c;
- struct Dialog *d;
- struct Splash *splash;
- struct Menu *menu;
- struct Wintype wintype;
- int userplaced;
- if (getmanaged(win) != NULL)
- return;
- getwintype(win, &wintype);
- switch (wintype.type) {
- case TYPE_DESKTOP:
- managedesktop(win);
- break;
- case TYPE_DOCK:
- managebar(win);
- break;
- case TYPE_DOCKAPP:
- preparewin(win);
- managedockapp(win, w, h, wintype.dockpos, ignoreunmap);
- break;
- case TYPE_NOTIFICATION:
- preparewin(win);
- managenotif(win, w, h);
- break;
- case TYPE_PROMPT:
- preparewin(win);
- manageprompt(win, w, h);
- break;
- case TYPE_SPLASH:
- preparewin(win);
- splash = splashnew(win, w, h);
- managesplash(splash);
- break;
- case TYPE_DIALOG:
- preparewin(win);
- d = dialognew(win, w, h, ignoreunmap);
- managedialog(wintype.parent, d);
- break;
- case TYPE_MENU:
- preparewin(win);
- menu = menunew(win, x, y, w, h, ignoreunmap);
- winupdatetitle(menu->obj.win, &menu->name);
- managemenu(wintype.parent, menu);
- break;
- default:
- preparewin(win);
- userplaced = isuserplaced(win);
- t = tabnew(win, wintype.leader, ignoreunmap);
- winupdatetitle(t->obj.win, &t->name);
- c = containernew(x, y, w, h, wintype.state);
- managecontainer(c, t, wm.selmon, wm.selmon->seldesk, userplaced);
- break;
- }
-}
-
-/* unmanage tab (and delete its row if it is the only tab); return whether deletion occurred */
-static int
-unmanage(struct Object *obj, int ignoreunmap)
-{
- struct Container *c, *next;
- struct Column *col;
- struct Row *row;
- struct Tab *t;
- struct Monitor *mon;
- int desk;
- int moveresize;
- int focus;
-
- t = (struct Tab *)obj;
- if (ignoreunmap && t->ignoreunmap) {
- t->ignoreunmap--;
- return 0;
- }
- row = t->row;
- col = row->col;
- c = col->c;
- desk = c->desk;
- mon = c->mon;
- moveresize = 1;
- next = c;
- tabdel(t);
- focus = (c == wm.focused);
- if (row->ntabs == 0) {
- rowdel(row);
- if (col->nrows == 0) {
- coldel(col);
- if (c->ncols == 0) {
- containerdel(c);
- next = getnextfocused(mon, desk);
- moveresize = 0;
- }
- }
- }
- if (moveresize) {
- containercalccols(c, 1, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, 0);
- shodgrouptab(c);
- shodgroupcontainer(c);
- }
- if (focus) {
- tabfocus((next != NULL) ? next->selcol->selrow->seltab : NULL, 0);
- }
- return 1;
-}
-
-/* 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.x, wa.y, wa.width, wa.height, 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.x, wa.y, wa.width, wa.height, IGNOREUNMAP);
- }
- }
- if (wins != NULL) {
- XFree(wins);
- }
- }
-}
-
-/* map and hide dummy windows */
-static void
-mapdummywins(void)
-{
- XMoveWindow(dpy, wm.focuswin, -1, 0);
- XMapWindow(dpy, wm.focuswin);
- XMapWindow(dpy, wm.retabwin);
-}
-
-/* attach tab into row; return 1 if succeeded, zero otherwise */
-static void
-tabattach(struct Row *row, struct Tab *t, int xroot, int yroot, int x, int y)
-{
- struct Monitor *mon;
- struct Container *c, *newc;
- int recalc, redraw;
- struct Column *col;
-
- col = row->col;
- c = col->c;
- if (!tryattach(&wm.fullq, t, xroot, yroot)
- && !tryattach(&wm.aboveq, t, xroot, yroot)
- && !tryattach(&wm.centerq, t, xroot, yroot)
- && !tryattach(&wm.belowq, t, xroot, yroot)) {
- mon = getmon(xroot - x, yroot - y);
- if (mon == NULL)
- mon = wm.selmon;
- newc = containernew(xroot - x - config.titlewidth, yroot - y, t->winw, t->winh, 0);
- managecontainer(newc, t, mon, mon->seldesk, 1);
- }
- recalc = 1;
- redraw = 0;
- if (row->ntabs == 0) {
- rowdel(row);
- redraw = 1;
- }
- if (col->nrows == 0) {
- coldel(col);
- redraw = 1;
- }
- if (c->ncols == 0) {
- containerdel(c);
- recalc = 0;
- }
- if (recalc) {
- containercalccols(c, 1, 1);
- containermoveresize(c);
- shodgrouptab(c);
- shodgroupcontainer(c);
- if (redraw) {
- containerdecorate(c, NULL, NULL, 0, 0);
- }
- }
-}
-
-/* detach tab from window with mouse */
-static void
-mouseretab(struct Tab *t, int xroot, int yroot, int x, int y)
-{
- struct Row *row; /* row to be deleted, if emptied */
- struct Container *c;
- XEvent ev;
-
- row = t->row;
- c = row->col->c;
- if (XGrabPointer(dpy, root, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime) != GrabSuccess)
- goto done;
- tabdetach(t, xroot - x, yroot - y);
- containermoveresize(c);
- XUnmapWindow(dpy, t->title);
- XRaiseWindow(dpy, wm.retabwin);
- XMoveWindow(
- dpy, wm.retabwin,
- ev.xmotion.x_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth),
- ev.xmotion.y_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth)
- );
- while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
- switch (ev.type) {
- case Expose:
- if (ev.xexpose.count == 0)
- copypixmap(ev.xexpose.window);
- break;
- case MotionNotify:
- XMoveWindow(
- dpy, wm.retabwin,
- ev.xmotion.x_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth),
- ev.xmotion.y_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth)
- );
- break;
- case ButtonRelease:
- goto done;
- }
- }
-done:
- XMoveWindow(
- dpy, wm.retabwin,
- - (2 * config.borderwidth + config.titlewidth),
- - (2 * config.borderwidth + config.titlewidth)
- );
- tabattach(row, t, ev.xbutton.x_root, ev.xbutton.y_root, x, y);
- XUngrabPointer(dpy, CurrentTime);
-}
-
-/* resize container with mouse */
-static void
-mouseresize(int type, void *obj, int xroot, int yroot, enum Octant o)
-{
- struct Container *c;
- struct Menu *menu;
- Window frame;
- Cursor curs;
- XEvent ev;
- Time lasttime;
- int *nx, *ny, *nw, *nh;
- int x, y, dx, dy;
-
- if (type == FLOAT_MENU) {
- menu = (struct Menu *)obj;
- nx = &menu->x;
- ny = &menu->y;
- nw = &menu->w;
- nh = &menu->h;
- frame = menu->frame;
- menudecorate(menu, o != C);
- } else {
- c = (struct Container *)obj;
- if (c->isfullscreen || c->b == 0)
- return;
- if (containerisshaded(c)) {
- if (o & W) {
- o = W;
- } else if (o & E) {
- o = E;
- } else {
- return;
- }
- }
- nx = &c->nx;
- ny = &c->ny;
- nw = &c->nw;
- nh = &c->nh;
- frame = c->frame;
- containerdecorate(c, NULL, NULL, 0, o);
- }
- switch (o) {
- case NW:
- curs = theme.cursors[CURSOR_NW];
- break;
- case NE:
- curs = theme.cursors[CURSOR_NE];
- break;
- case SW:
- curs = theme.cursors[CURSOR_SW];
- break;
- case SE:
- curs = theme.cursors[CURSOR_SE];
- break;
- case N:
- curs = theme.cursors[CURSOR_N];
- break;
- case S:
- curs = theme.cursors[CURSOR_S];
- break;
- case W:
- curs = theme.cursors[CURSOR_W];
- break;
- case E:
- curs = theme.cursors[CURSOR_E];
- break;
- default:
- curs = None;
- break;
- }
- if (o & W)
- x = xroot - *nx - config.borderwidth;
- else if (o & E)
- x = *nx + *nw - config.borderwidth - xroot;
- else
- x = 0;
- if (o & N)
- y = yroot - *ny - config.borderwidth;
- else if (o & S)
- y = *ny + *nh - config.borderwidth - yroot;
- else
- y = 0;
- if (XGrabPointer(dpy, frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, curs, CurrentTime) != GrabSuccess)
- goto done;
- lasttime = 0;
- while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
- switch (ev.type) {
- case Expose:
- if (ev.xexpose.count == 0)
- copypixmap(ev.xexpose.window);
- break;
- case ButtonRelease:
- goto done;
- break;
- case MotionNotify:
- if (x > *nw)
- x = 0;
- if (y > *nh)
- y = 0;
- if (o & W &&
- ((ev.xmotion.x_root < xroot && x > ev.xmotion.x_root - *nx) ||
- (ev.xmotion.x_root > xroot && x < ev.xmotion.x_root - *nx))) {
- dx = xroot - ev.xmotion.x_root;
- if (*nw + dx >= wm.minsize) {
- *nx -= dx;
- *nw += dx;
- }
- } else if (o & E &&
- ((ev.xmotion.x_root > xroot && x > *nx + *nw - ev.xmotion.x_root) ||
- (ev.xmotion.x_root < xroot && x < *nx + *nw - ev.xmotion.x_root))) {
- dx = ev.xmotion.x_root - xroot;
- if (*nw + dx >= wm.minsize) {
- *nw += dx;
- }
- }
- if (o & N &&
- ((ev.xmotion.y_root < yroot && y > ev.xmotion.y_root - *ny) ||
- (ev.xmotion.y_root > yroot && y < ev.xmotion.y_root - *ny))) {
- dy = yroot - ev.xmotion.y_root;
- if (*nh + dy >= wm.minsize) {
- *ny -= dy;
- *nh += dy;
- }
- } else if (o & S &&
- ((ev.xmotion.y_root > yroot && *ny + *nh - ev.xmotion.y_root < y) ||
- (ev.xmotion.y_root < yroot && *ny + *nh - ev.xmotion.y_root > y))) {
- dy = ev.xmotion.y_root - yroot;
- if (*nh + dy >= wm.minsize) {
- *nh += dy;
- }
- }
- if (ev.xmotion.time - lasttime > RESIZETIME) {
- if (type == FLOAT_MENU) {
- menumoveresize(menu);
- menudecorate(menu, 0);
- } else {
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, o);
- }
- lasttime = ev.xmotion.time;
- }
- xroot = ev.xmotion.x_root;
- yroot = ev.xmotion.y_root;
- break;
- }
- }
-done:
- if (type == FLOAT_MENU) {
- menumoveresize(menu);
- menudecorate(menu, 0);
- } else {
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerdecorate(c, NULL, NULL, 0, 0);
- }
- XUngrabPointer(dpy, CurrentTime);
-}
-
-/* move floating entity (container or menu) with mouse */
-static void
-mousemove(int type, void *p, int xroot, int yroot, enum Octant o)
-{
- struct Container *c;
- struct Menu *menu;
- Window frame;
- XEvent ev;
- Time lasttime;
- int x, y;
- int floatx, floaty;
-
- if (type == FLOAT_MENU) {
- menu = (struct Menu *)p;
- menudecorate(menu, o);
- frame = menu->frame;
- } else {
- c = (struct Container *)p;
- containerdecorate(c, NULL, NULL, 0, o);
- frame = c->frame;
- }
- x = y = 0;
- if (XGrabPointer(dpy, frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, theme.cursors[CURSOR_MOVE], CurrentTime) != GrabSuccess)
- goto done;
- lasttime = 0;
- while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
- switch (ev.type) {
- case Expose:
- if (ev.xexpose.count == 0)
- copypixmap(ev.xexpose.window);
- break;
- case ButtonRelease:
- goto done;
- break;
- case MotionNotify:
- if (ev.xmotion.time - lasttime > MOVETIME) {
- x = ev.xmotion.x_root - xroot;
- y = ev.xmotion.y_root - yroot;
- if (type == FLOAT_MENU) {
- menu->x += x;
- menu->y += y;
- floatx = menu->x;
- floaty = menu->y;
- snaptoedge(&floatx, &floaty, menu->w, menu->h);
- XMoveWindow(dpy, menu->frame, floatx, floaty);
- } else {
- containerincrmove(c, x, y, 0);
- }
- lasttime = ev.xmotion.time;
- xroot = ev.xmotion.x_root;
- yroot = ev.xmotion.y_root;
- }
- break;
- }
- }
-done:
- if (type == FLOAT_MENU)
- menudecorate(menu, 0);
- else
- containerdecorate(c, NULL, NULL, 0, 0);
- XUngrabPointer(dpy, CurrentTime);
-}
-
-/* close tab with mouse */
-static void
-mouseclose(int type, void *obj)
-{
- struct Row *row;
- struct Menu *menu;
- Window win, button;
- XEvent ev;
-
- if (type == FLOAT_MENU) {
- menu = (struct Menu *)obj;
- button = menu->button;
- win = menu->obj.win;
- buttonrightdecorate(button, menu->pixbutton, FOCUSED, 1);
- } else {
- row = (struct Row *)obj;
- button = row->br;
- win = TAILQ_EMPTY(&row->seltab->dialq) ? row->seltab->obj.win : TAILQ_FIRST(&row->seltab->dialq)->win;
- buttonrightdecorate(button, row->pixbr, tabgetstyle(row->seltab), 1);
- }
- if (XGrabPointer(dpy, button, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime) != GrabSuccess)
- goto done;
- while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
- switch(ev.type) {
- case Expose:
- if (ev.xexpose.count == 0)
- copypixmap(ev.xexpose.window);
- break;
- case ButtonRelease:
- if (ev.xbutton.window == button &&
- ev.xbutton.x >= 0 && ev.xbutton.x >= 0 &&
- ev.xbutton.x < config.titlewidth && ev.xbutton.x < config.titlewidth)
- winclose(win);
- goto done;
- break;
- }
- }
-done:
- if (type == FLOAT_MENU) {
- buttonrightdecorate(menu->button, menu->pixbutton, FOCUSED, 0);
- } else {
- buttonrightdecorate(row->br, row->pixbr, tabgetstyle(row->seltab), 0);
- }
- XUngrabPointer(dpy, CurrentTime);
-}
-
-/* resize tiles by dragging division with mouse */
-static void
-mouseretile(struct Container *c, struct Column *cdiv, struct Row *rdiv, int xroot, int yroot)
-{
- XEvent ev;
- Cursor curs;
- Time lasttime;
- int x, y;
- int update;
-
- if (cdiv != NULL && TAILQ_NEXT(cdiv, entry) != NULL)
- curs = theme.cursors[CURSOR_H];
- else if (rdiv != NULL && TAILQ_NEXT(rdiv, entry) != NULL && rdiv->col->maxrow == NULL)
- curs = theme.cursors[CURSOR_V];
- else
- return;
- x = y = 0;
- update = 0;
- lasttime = 0;
- containerdecorate(c, cdiv, rdiv, 0, 0);
- if (XGrabPointer(dpy, c->frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, curs, CurrentTime) != GrabSuccess)
- goto done;
- while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
- switch (ev.type) {
- case Expose:
- if (ev.xexpose.count == 0)
- copypixmap(ev.xexpose.window);
- break;
- case ButtonRelease:
- goto done;
- break;
- case MotionNotify:
- x = ev.xmotion.x_root - xroot;
- y = ev.xmotion.y_root - yroot;
- if (cdiv != NULL) {
- if (x < 0 && cdiv->w + x > wm.minsize) {
- cdiv->w += x;
- TAILQ_NEXT(cdiv, entry)->w -= x;
- if (ev.xmotion.time - lasttime > RESIZETIME) {
- update = 1;
- }
- } else if (x > 0 && TAILQ_NEXT(cdiv, entry)->w - x > wm.minsize) {
- TAILQ_NEXT(cdiv, entry)->w -= x;
- cdiv->w += x;
- if (ev.xmotion.time - lasttime > RESIZETIME) {
- update = 1;
- }
- }
- } else if (rdiv != NULL) {
- if (y < 0 && rdiv->h + y > wm.minsize) {
- rdiv->h += y;
- TAILQ_NEXT(rdiv, entry)->h -= y;
- if (ev.xmotion.time - lasttime > RESIZETIME) {
- update = 1;
- }
- } else if (y > 0 && TAILQ_NEXT(rdiv, entry)->h - y > wm.minsize) {
- TAILQ_NEXT(rdiv, entry)->h -= y;
- rdiv->h += y;
- if (ev.xmotion.time - lasttime > RESIZETIME) {
- update = 1;
- }
- }
- }
- if (update) {
- containercalccols(c, 1, 1);
- containermoveresize(c);
- containerdecorate(c, cdiv, rdiv, 0, 0);
- lasttime = ev.xmotion.time;
- update = 0;
- }
- xroot = ev.xmotion.x_root;
- yroot = ev.xmotion.y_root;
- break;
- }
- }
-done:
- containercalccols(c, 1, 1);
- containermoveresize(c);
- containerdecorate(c, NULL, NULL, 0, 0);
- XUngrabPointer(dpy, CurrentTime);
-}
-
-/* perform container switching (aka alt-tab) */
-static void
-alttab(int forward)
-{
- struct Container *prev, *tohead;
-
- if (TAILQ_EMPTY(&wm.focusq))
- return;
- prev = TAILQ_FIRST(&wm.focusq);
- if (forward) {
- TAILQ_FOREACH(tohead, &wm.focusq, entry) {
- if (tohead != prev &&
- !tohead->isminimized &&
- tohead->mon == prev->mon &&
- (tohead->issticky || tohead->desk == prev->desk)) {
- break;
- }
- }
- } else {
- TAILQ_FOREACH_REVERSE(tohead, &wm.focusq, ContainerQueue, entry) {
- if (tohead != prev &&
- !tohead->isminimized &&
- tohead->mon == prev->mon &&
- (tohead->issticky || tohead->desk == prev->desk)) {
- break;
- }
- }
- }
- if (tohead == NULL || tohead == prev)
- return;
- tabfocus(tohead->selcol->selrow->seltab, 1);
-}
-
-/* stack rows in column with mouse */
-static void
-mousestack(struct Row *row)
-{
- XEvent ev;
-
- buttonleftdecorate(row, 1);
- if (XGrabPointer(dpy, row->bl, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime) != GrabSuccess)
- goto done;
- while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
- switch(ev.type) {
- case Expose:
- if (ev.xexpose.count == 0)
- copypixmap(ev.xexpose.window);
- break;
- case ButtonRelease:
- if (row->col->nrows > 1 &&
- ev.xbutton.window == row->bl &&
- ev.xbutton.x >= 0 && ev.xbutton.x >= 0 &&
- ev.xbutton.x < config.titlewidth && ev.xbutton.x < config.titlewidth) {
- rowstack(row->col, (row->col->maxrow == row) ? NULL : row);
- tabfocus(row->seltab, 0);
- }
- goto done;
- break;
- }
- }
-done:
- buttonleftdecorate(row, 0);
- XUngrabPointer(dpy, CurrentTime);
-}
-
-/* handle mouse operation, focusing and raising */
-static void
-xeventbuttonpress(XEvent *e)
-{
- struct Object *obj;
- struct Monitor *mon;
- struct Container *c;
- struct Column *cdiv;
- struct Row *rdiv;
- struct Tab *tab;
- struct Menu *menu;
- enum Octant o;
- XButtonPressedEvent *ev;
- Window dw;
- int x, y;
-
- ev = &e->xbutton;
-
- if ((obj = getmanaged(ev->window)) == NULL) {
- /* if user clicked in no window, focus the monitor below cursor */
- if ((mon = getmon(ev->x_root, ev->y_root)) != NULL)
- deskfocus(mon, mon->seldesk, 1);
- goto done;
- }
-
- menu = NULL;
- tab = NULL;
- switch (obj->type) {
- case TYPE_NORMAL:
- tab = (struct Tab *)obj;
- c = tab->row->col->c;
- break;
- case TYPE_DIALOG:
- tab = ((struct Dialog *)obj)->tab;
- c = tab->row->col->c;
- break;
- case TYPE_MENU:
- menu = (struct Menu *)obj;
- tab = menu->tab;
- c = tab->row->col->c;
- break;
- default:
- if ((mon = getmon(ev->x_root, ev->y_root)) != NULL)
- deskfocus(mon, mon->seldesk, 1);
- goto done;
- }
-
- /* raise menu above others or focus tab */
- if (menu != NULL)
- menuaddraise(tab, menu);
- else if ((wm.focused == NULL || tab != wm.focused->selcol->selrow->seltab) && ev->button == Button1)
- tabfocus(tab, 1);
-
- /* raise client */
- if (ev->button == Button1)
- containerraise(c, c->isfullscreen, c->layer);
-
- /* get pointer position */
- if (!XTranslateCoordinates(dpy, ev->window, c->frame, ev->x, ev->y, &x, &y, &dw))
- goto done;
-
- /* do action performed by mouse */
- if (menu != NULL) {
- if (ev->window == menu->titlebar && ev->button == Button1) {
- mousemove(FLOAT_MENU, menu, ev->x_root, ev->y_root, 1);
- } else if (ev->window == menu->button && ev->button == Button1) {
- mouseclose(FLOAT_MENU, menu);
- } else if ((ev->window == menu->frame && ev->button == Button3)
- || (ev->state == config.modifier && ev->button == Button1)) {
- mousemove(FLOAT_MENU, menu, ev->x_root, ev->y_root, 0);
- } else if (ev->state == config.modifier && ev->button == Button3) {
- o = getquadrant(menu->w, menu->h, x, y);
- mouseresize(FLOAT_MENU, menu, ev->x_root, ev->y_root, o);
- }
- } else {
- o = getframehandle(c->w, c->h, x, y);
- if (ev->window == tab->title && ev->button == Button3) {
- mouseretab(tab, ev->x_root, ev->y_root, ev->x, ev->y);
- } else if (ev->window == tab->row->bl && ev->button == Button1) {
- mousestack(tab->row);
- } else if (ev->window == tab->row->br && ev->button == Button1) {
- mouseclose(FLOAT_CONTAINER, tab->row);
- } else if (ev->window == c->frame && ev->button == Button1 && o == C) {
- getdivisions(c, &cdiv, &rdiv, x, y);
- if (cdiv != NULL || rdiv != NULL) {
- mouseretile(c, cdiv, rdiv, ev->x_root, ev->y_root);
- }
- } else if (!c->isfullscreen && !c->isminimized && !c->ismaximized) {
- if (ev->state == config.modifier && ev->button == Button1) {
- mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, 0);
- } else if (ev->window == c->frame && ev->button == Button3) {
- mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
- } else if ((ev->state == config.modifier && ev->button == Button3) ||
- (o != C && ev->window == c->frame && ev->button == Button1)) {
- if (o == C)
- o = getquadrant(c->w, c->h, x, y);
- mouseresize(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
- } else if (ev->window == tab->title && ev->button == Button1) {
- tabdecorate(tab, 1);
- mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, 0);
- tabdecorate(tab, 0);
- }
- }
- }
-
-done:
- XAllowEvents(dpy, ReplayPointer, CurrentTime);
-}
-
-/* handle client message event */
-static void
-xeventclientmessage(XEvent *e)
-{
- struct Monitor *mon;
- struct Container *c = NULL;
- struct Tab *tab = NULL;
- struct Menu *menu = NULL;
- struct Object *obj;
- XClientMessageEvent *ev;
- XWindowChanges wc;
- unsigned value_mask = 0;
- int prevdesk;
- int floattype;
- int i;
- void *p;
-
- ev = &e->xclient;
- if ((obj = getmanaged(ev->window)) != NULL) {
- switch (obj->type) {
- case TYPE_NORMAL:
- tab = (struct Tab *)obj;
- c = tab->row->col->c;
- break;
- case TYPE_DIALOG:
- tab = ((struct Dialog *)obj)->tab;
- c = tab->row->col->c;
- break;
- case TYPE_MENU:
- menu = (struct Menu *)obj;
- tab = menu->tab;
- c = tab->row->col->c;
- break;
- }
- }
- if (ev->message_type == atoms[_NET_CURRENT_DESKTOP]) {
- deskfocus(wm.selmon, ev->data.l[0], 1);
- } else if (ev->message_type == atoms[_NET_SHOWING_DESKTOP]) {
- if (ev->data.l[0]) {
- deskshow(1);
- } else {
- deskfocus(wm.selmon, wm.selmon->seldesk, 1);
- }
- } else if (ev->message_type == atoms[_NET_WM_STATE]) {
- if (obj == NULL || obj->type != TYPE_NORMAL)
- return;
- if (((Atom)ev->data.l[1] == atoms[_NET_WM_STATE_MAXIMIZED_VERT] ||
- (Atom)ev->data.l[1] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ]) &&
- ((Atom)ev->data.l[2] == atoms[_NET_WM_STATE_MAXIMIZED_VERT] ||
- (Atom)ev->data.l[2] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ])) {
- containermaximize(c, ev->data.l[0]);
- }
- for (i = 1; i < 3; i++) {
- if ((Atom)ev->data.l[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
- containerfullscreen(c, ev->data.l[0]);
- } else if ((Atom)ev->data.l[i] == atoms[_NET_WM_STATE_SHADED]) {
- containershade(c, ev->data.l[0]);
- } else if ((Atom)ev->data.l[i] == atoms[_NET_WM_STATE_STICKY]) {
- containerstick(c, ev->data.l[0]);
- } else if ((Atom)ev->data.l[i] == atoms[_NET_WM_STATE_HIDDEN]) {
- containerminimize(c, ev->data.l[0], (c == wm.focused));
- } else if ((Atom)ev->data.l[i] == atoms[_NET_WM_STATE_ABOVE]) {
- containerabove(c, ev->data.l[0]);
- } else if ((Atom)ev->data.l[i] == atoms[_NET_WM_STATE_BELOW]) {
- containerbelow(c, ev->data.l[0]);
- } else if ((Atom)ev->data.l[i] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION]) {
- tabupdateurgency(tab, ev->data.l[0] == ADD || (ev->data.l[0] == TOGGLE && !tab->isurgent));
- }
- }
- } else if (ev->message_type == atoms[_NET_ACTIVE_WINDOW]) {
-#define ACTIVATECOL(col) { if ((col) != NULL) tabfocus((col)->selrow->seltab, 1); }
-#define ACTIVATEROW(row) { if ((row) != NULL) tabfocus((row)->seltab, 1); }
- if (tab == NULL && wm.focused != NULL) {
- c = wm.focused;
- tab = wm.focused->selcol->selrow->seltab;
- }
- if (tab == NULL)
- return;
- switch (ev->data.l[3]) {
- case _SHOD_FOCUS_LEFT_CONTAINER:
- case _SHOD_FOCUS_RIGHT_CONTAINER:
- case _SHOD_FOCUS_TOP_CONTAINER:
- case _SHOD_FOCUS_BOTTOM_CONTAINER:
- // removed
- break;
- case _SHOD_FOCUS_PREVIOUS_CONTAINER:
- alttab(1);
- break;
- case _SHOD_FOCUS_NEXT_CONTAINER:
- alttab(0);
- break;
- case _SHOD_FOCUS_LEFT_WINDOW:
- ACTIVATECOL(TAILQ_PREV(tab->row->col, ColumnQueue, entry))
- break;
- case _SHOD_FOCUS_RIGHT_WINDOW:
- ACTIVATECOL(TAILQ_NEXT(tab->row->col, entry))
- break;
- case _SHOD_FOCUS_TOP_WINDOW:
- ACTIVATEROW(TAILQ_PREV(tab->row, RowQueue, entry))
- break;
- case _SHOD_FOCUS_BOTTOM_WINDOW:
- ACTIVATEROW(TAILQ_NEXT(tab->row, entry))
- break;
- case _SHOD_FOCUS_PREVIOUS_WINDOW:
- obj = (struct Object *)tab;
- if (TAILQ_PREV(obj, Queue, entry) != NULL)
- tabfocus((struct Tab *)TAILQ_PREV(obj, Queue, entry), 1);
- else
- tabfocus((struct Tab *)TAILQ_LAST(&tab->row->tabq, Queue), 1);
- break;
- case _SHOD_FOCUS_NEXT_WINDOW:
- obj = (struct Object *)tab;
- if (TAILQ_NEXT(obj, entry) != NULL)
- tabfocus((struct Tab *)TAILQ_NEXT(obj, entry), 1);
- else
- tabfocus((struct Tab *)TAILQ_FIRST(&tab->row->tabq), 1);
- break;
- default:
- tabfocus(tab, 1);
- break;
- }
- } else if (ev->message_type == atoms[_NET_CLOSE_WINDOW]) {
- winclose(ev->window);
- } else if (ev->message_type == atoms[_NET_MOVERESIZE_WINDOW]) {
- if (c == NULL)
- return;
- value_mask = CWX | CWY | CWWidth | CWHeight;
- wc.x = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->x + ev->data.l[1] : ev->data.l[1];
- wc.y = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->y + ev->data.l[2] : ev->data.l[2];
- wc.width = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->w + ev->data.l[3] : ev->data.l[3];
- wc.height = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->h + ev->data.l[4] : ev->data.l[4];
- if (obj->type == TYPE_DIALOG) {
- dialogconfigure((struct Dialog *)obj, value_mask, &wc);
- } else if (obj->type == TYPE_NORMAL) {
- containerconfigure(c, value_mask, &wc);
- }
- } else if (ev->message_type == atoms[_NET_WM_DESKTOP]) {
- if (obj == NULL || obj->type != TYPE_NORMAL)
- return;
- if (ev->data.l[0] == 0xFFFFFFFF) {
- containerstick(c, ADD);
- } else if (!c->isminimized) {
- if (ev->data.l[0] < 0 || ev->data.l[0] >= config.ndesktops || c->desk == ev->data.l[0])
- return;
- if (c->issticky)
- containerstick(c, REMOVE);
- mon = c->mon;
- prevdesk = c->desk;
- containersendtodesk(c, mon, ev->data.l[0], 0, 0);
- c = getnextfocused(mon, prevdesk);
- if (c != NULL) {
- tabfocus(c->selcol->selrow->seltab, 0);
- } else {
- tabfocus(NULL, 0);
- }
- }
- } else if (ev->message_type == atoms[_NET_REQUEST_FRAME_EXTENTS]) {
- if (c == NULL) {
- /*
- * A client can request an estimate for the frame size
- * which the window manager will put around it before
- * actually mapping its window. Java does this (as of
- * openjdk-7).
- */
- ewmhsetframeextents(ev->window, config.borderwidth, config.titlewidth);
- } else {
- ewmhsetframeextents(ev->window, c->b, (obj->type == TYPE_DIALOG ? 0 : TITLEWIDTH(c)));
- }
- } else if (ev->message_type == atoms[_NET_WM_MOVERESIZE]) {
- /*
- * Client-side decorated Gtk3 windows emit this signal when being
- * dragged by their GtkHeaderBar
- */
- if (obj == NULL || (obj->type != TYPE_NORMAL && obj->type != TYPE_MENU))
- return;
- if (menu != NULL) {
- p = menu;
- floattype = FLOAT_MENU;
- } else {
- p = c;
- floattype = FLOAT_CONTAINER;
- }
- switch (ev->data.l[2]) {
- case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], NW);
- break;
- case _NET_WM_MOVERESIZE_SIZE_TOP:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], N);
- break;
- case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], NE);
- break;
- case _NET_WM_MOVERESIZE_SIZE_RIGHT:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], E);
- break;
- case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], SE);
- break;
- case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], S);
- break;
- case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], SW);
- break;
- case _NET_WM_MOVERESIZE_SIZE_LEFT:
- mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], W);
- break;
- case _NET_WM_MOVERESIZE_MOVE:
- mousemove(floattype, p, ev->data.l[0], ev->data.l[1], C);
- break;
- default:
- XUngrabPointer(dpy, CurrentTime);
- break;
- }
- }
-}
-
-/* handle configure notify event */
-static void
-xeventconfigurenotify(XEvent *e)
-{
- XConfigureEvent *ev;
-
- 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 Object *obj;
-
- 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;
- obj = getmanaged(ev->window);
- if (obj == NULL) {
- XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
- } else if (obj->type == TYPE_DIALOG) {
- dialogconfigure((struct Dialog *)obj, ev->value_mask, &wc);
- } else if (obj->type == TYPE_MENU) {
- menuconfigure((struct Menu *)obj, ev->value_mask, &wc);
- } else if (obj->type == TYPE_NORMAL) {
- if (config.honorconfig) {
- containerconfigure(((struct Tab *)obj)->row->col->c, ev->value_mask, &wc);
- } else {
- containermoveresize(((struct Tab *)obj)->row->col->c);
- }
- }
-}
-
-static int (*unmanagetab[TYPE_LAST])(struct Object *, int) = {
- [TYPE_DOCKAPP] = dockappdel,
- [TYPE_SPLASH] = splashdel,
- [TYPE_NOTIFICATION] = notifdel,
- [TYPE_DOCK] = bardel,
- [TYPE_DIALOG] = dialogdel,
- [TYPE_MENU] = menudel,
- [TYPE_NORMAL] = unmanage,
-};
-
-/* forget about client */
-static void
-xeventdestroynotify(XEvent *e)
-{
- XDestroyWindowEvent *ev;
- struct Object *obj;
-
- ev = &e->xdestroywindow;
- if ((obj = getmanaged(ev->window)) != NULL) {
- if (obj->win == ev->window && (*unmanagetab[obj->type])(obj, 0)) {
- ewmhsetclients();
- ewmhsetclientsstacking();
- }
- }
-}
-
-/* focus window when cursor enter it (only if there is no focus button) */
-static void
-xevententernotify(XEvent *e)
-{
- struct Object *obj;
-
- if (!config.sloppyfocus)
- return;
- while (XCheckTypedEvent(dpy, EnterNotify, e))
- ;
- if ((obj = getmanaged(e->xcrossing.window)) == NULL)
- return;
- switch (obj->type) {
- case TYPE_MENU:
- tabfocus(((struct Menu *)obj)->tab, 1);
- break;
- case TYPE_DIALOG:
- tabfocus(((struct Dialog *)obj)->tab, 1);
- break;
- case TYPE_NORMAL:
- tabfocus((struct Tab *)obj, 1);
- break;
- }
-}
-
-/* redraw window decoration */
-static void
-xeventexpose(XEvent *e)
-{
- XExposeEvent *ev;
-
- ev = &e->xexpose;
- if (ev->count == 0) {
- copypixmap(ev->window);
- }
-}
-
-/* handle focusin event */
-static void
-xeventfocusin(XEvent *e)
-{
- XFocusChangeEvent *ev;
- struct Object *obj;
-
- ev = &e->xfocus;
- if (wm.focused == NULL) {
- tabfocus(NULL, 0);
- return;
- }
- obj = getmanaged(ev->window);
- if (obj == NULL)
- goto focus;
- switch (obj->type) {
- case TYPE_MENU:
- if (((struct Menu *)obj)->tab != wm.focused->selcol->selrow->seltab)
- goto focus;
- break;
- case TYPE_DIALOG:
- if (((struct Dialog *)obj)->tab != wm.focused->selcol->selrow->seltab)
- goto focus;
- break;
- case TYPE_NORMAL:
- if ((struct Tab *)obj != wm.focused->selcol->selrow->seltab)
- goto focus;
- break;
- }
- return;
-focus:
- 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.x, wa.y, wa.width, wa.height, 0);
-}
-
-/* update client properties */
-static void
-xeventpropertynotify(XEvent *e)
-{
- XPropertyEvent *ev;
- struct Object *obj;
- struct Tab *tab;
- struct Menu *menu;
-
- ev = &e->xproperty;
- if (ev->state == PropertyDelete)
- return;
- obj = getmanaged(ev->window);
- if (obj == NULL)
- return;
- if (obj->type == TYPE_NORMAL && ev->window == obj->win) {
- tab = (struct Tab *)obj;
- if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) {
- winupdatetitle(tab->obj.win, &tab->name);
- tabdecorate(tab, 0);
- } else if (ev->atom == XA_WM_HINTS) {
- tabupdateurgency(tab, winisurgent(tab->obj.win));
- }
- } else if (obj->type == TYPE_DOCK && (ev->atom == _NET_WM_STRUT_PARTIAL || ev->atom == _NET_WM_STRUT)) {
- barstrut((struct Bar *)obj);
- monupdatearea();
- } else if (obj->type == TYPE_MENU && ev->window == obj->win) {
- menu = (struct Menu *)obj;
- if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) {
- winupdatetitle(menu->obj.win, &menu->name);
- menudecorate(menu, 0);
- }
- }
-}
-
-/* forget about client */
-static void
-xeventunmapnotify(XEvent *e)
-{
- XUnmapEvent *ev;
- struct Object *obj;
-
- ev = &e->xunmap;
- if ((obj = getmanaged(ev->window)) != NULL) {
- if (obj->win == ev->window && (*unmanagetab[obj->type])(obj, 1)) {
- ewmhsetclients();
- ewmhsetclientsstacking();
- }
- }
-}
-
-/* run stdin on sh */
-static void
-autostart(char *filename)
-{
- pid_t pid;
-
- if (filename == NULL)
- return;
- if ((pid = efork()) == 0) {
- if (efork() == 0)
- execshell(filename);
- exit(0);
- }
- waitpid(pid, NULL, 0);
-}
-
-/* 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, theme.cursors[i]);
- }
-}
-
-/* clean window manager structures */
-static void
-cleanwm(void)
-{
- struct Monitor *mon;
- struct Object *obj;
- struct Container *c;
-
- while ((c = TAILQ_FIRST(&wm.focusq)) != NULL)
- containerdel(c);
- while ((obj = TAILQ_FIRST(&wm.notifq)) != NULL)
- (void)notifdel(obj, 0);
- while ((obj = TAILQ_FIRST(&wm.barq)) != NULL)
- (void)bardel(obj, 0);
- while ((obj = TAILQ_FIRST(&wm.splashq)) != NULL)
- (void)splashdel(obj, 0);
- while ((obj = TAILQ_FIRST(&dock.dappq)) != NULL)
- (void)dockappdel(obj, 0);
- while ((mon = TAILQ_FIRST(&wm.monq)) != NULL)
- mondel(mon);
- if (dock.pix != None)
- XFreePixmap(dpy, dock.pix);
- XDestroyWindow(dpy, dock.win);
-}
-
-/* free font */
-static void
-cleantheme(void)
-{
- XftFontClose(dpy, theme.font);
-}
-
-/* init retabbing drag-and-drop window */
-static void
-initdnd(void)
-{
- XGCValues val;
-
- wm.retabwin = XCreateWindow(
- dpy, root,
- - (2 * config.borderwidth + config.titlewidth),
- - (2 * config.borderwidth + config.titlewidth),
- 2 * config.borderwidth + config.titlewidth,
- 2 * config.borderwidth + config.titlewidth,
- 0, depth, CopyFromParent, visual,
- clientmask, &clientswa
- );
- wm.retabpix = XCreatePixmap(
- dpy, wm.retabwin,
- 2 * config.borderwidth + config.titlewidth,
- 2 * config.borderwidth + config.titlewidth,
- depth
- );
- val.foreground = theme.border[FOCUSED][COLOR_MID];
- XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(
- dpy, wm.retabpix, gc,
- 0, 0,
- 2 * config.borderwidth + config.titlewidth,
- 2 * config.borderwidth + config.titlewidth
- );
- drawborders(
- wm.retabpix,
- 2 * config.borderwidth + config.titlewidth,
- 2 * config.borderwidth + config.titlewidth,
- theme.border[FOCUSED]
- );
- drawrectangle(
- wm.retabpix,
- config.borderwidth,
- config.borderwidth,
- config.titlewidth,
- config.titlewidth,
- theme.border[FOCUSED][COLOR_LIGHT],
- theme.border[FOCUSED][COLOR_DARK]
- );
-}
-
-/* clean drag-and-drop window */
-static void
-cleandnd(void)
-{
- XFreePixmap(dpy, wm.retabpix);
- XDestroyWindow(dpy, wm.retabwin);
-}
-
-/* shod window manager */
-int
-main(int argc, char *argv[])
-{
- XEvent ev;
- char *filename;
- void (*xevents[LASTEvent])(XEvent *) = {
- [ButtonPress] = xeventbuttonpress,
- [ClientMessage] = xeventclientmessage,
- [ConfigureNotify] = xeventconfigurenotify,
- [ConfigureRequest] = xeventconfigurerequest,
- [DestroyNotify] = xeventdestroynotify,
- [EnterNotify] = xevententernotify,
- [Expose] = xeventexpose,
- [FocusIn] = xeventfocusin,
- [MapRequest] = xeventmaprequest,
- [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);
- root = RootWindow(dpy, screen);
- xerrorxlib = XSetErrorHandler(xerror);
+ xinit();
+ xinitvisual();
+ xiniterrfunc(xerror, &xerrorxlib);
XrmInitialize();
if ((xrm = XResourceManagerString(dpy)) != NULL)
xdb = XrmGetStringDatabase(xrm);
+ clientswa.colormap = colormap;
+ clientswa.border_pixel = BlackPixel(dpy, screen);
+ clientswa.background_pixel = BlackPixel(dpy, screen);
/* get configuration */
+ if ((wmname = strrchr(argv[0], '/')) != NULL)
+ wmname++;
+ else if (argv[0] != NULL && argv[0][0] != '\0')
+ wmname = argv[0];
+ else
+ wmname = WMNAME;
getresources();
filename = getoptions(argc, argv);
@@ -6612,15 +426,13 @@ main(int argc, char *argv[])
clientswa.event_mask |= EnterWindowMask;
/* initialize */
- xinitvisual();
initsignal();
- initdummywindows();
initcursors();
initatoms();
initroot();
+ initdummywindows();
inittheme();
initdock();
- initdnd();
/* initialize queues */
TAILQ_INIT(&wm.monq);
@@ -6638,7 +450,7 @@ main(int argc, char *argv[])
wm.selmon = TAILQ_FIRST(&wm.monq);
/* initialize ewmh hints */
- ewmhinit();
+ ewmhinit(wmname);
ewmhsetcurrentdesktop(0);
ewmhsetshowingdesktop(0);
ewmhsetclients();
@@ -6658,7 +470,6 @@ main(int argc, char *argv[])
(*xevents[ev.type])(&ev);
/* clean up */
- cleandnd();
cleandummywindows();
cleancursors();
cleantheme();
diff --git a/shod.h b/shod.h
@@ -0,0 +1,733 @@
+#include <sys/queue.h>
+
+#include "xutil.h"
+
+#define SHELL "SHELL"
+#define DEF_SHELL "sh"
+#define DNDDIFF 10 /* pixels from pointer to place dnd marker */
+#define IGNOREUNMAP 6 /* number of unmap notifies to ignore while scanning existing clients */
+#define NAMEMAXLEN 256 /* maximum length of window's name */
+#define DROPPIXELS 30 /* number of pixels from the border where a tab can be dropped in */
+#define RESIZETIME 64 /* time to redraw containers during resizing */
+#define MOVETIME 32 /* time to redraw containers during moving */
+#define DOCKBORDER 2
+#define LEN(x) (sizeof(x) / sizeof((x)[0]))
+#define _SHOD_MOVERESIZE_RELATIVE ((long)(1 << 16))
+
+#define TITLEWIDTH(c) (((c)->isfullscreen && (c)->ncols == 1 && TAILQ_FIRST(&(c)->colq)->nrows == 1) ? 0 : config.titlewidth)
+
+#define TAB_FOREACH_BEGIN(c, tab) { \
+ struct Column *col; \
+ struct Row *row; \
+ TAILQ_FOREACH(col, &(c)->colq, entry) { \
+ TAILQ_FOREACH(row, &col->rowq, entry) { \
+ TAILQ_FOREACH(tab, &row->tabq, entry)
+#define TAB_FOREACH_END } \
+ } \
+ }
+
+enum {
+ /* border array indices */
+ BORDER_N,
+ BORDER_S,
+ BORDER_W,
+ BORDER_E,
+ BORDER_NW,
+ BORDER_NE,
+ BORDER_SW,
+ BORDER_SE,
+ BORDER_LAST
+};
+
+enum {
+ /* cursor array indices */
+ CURSOR_NORMAL, /* regular cursor */
+ CURSOR_MOVE, /* arrow-cross cursor */
+ CURSOR_NW, /* north-west pointing cursor */
+ CURSOR_NE, /* north-east pointing cursor */
+ CURSOR_SW, /* south-west pointing cursor */
+ CURSOR_SE, /* south-east pointing cursor */
+ CURSOR_N, /* north pointing cursor */
+ CURSOR_S, /* south pointing cursor */
+ CURSOR_W, /* west pointing cursor */
+ CURSOR_E, /* east pointing cursor */
+ CURSOR_V, /* vertical arrow cursor */
+ CURSOR_H, /* horizontal arrow cursor */
+ CURSOR_HAND, /* hand cursor */
+ CURSOR_PIRATE, /* pirate-cross cursor */
+ CURSOR_LAST
+};
+
+enum {
+ /* application window array indices */
+ TYPE_UNKNOWN,
+ TYPE_NORMAL,
+ TYPE_DESKTOP,
+ TYPE_DOCK,
+ TYPE_MENU,
+ TYPE_DIALOG,
+ TYPE_NOTIFICATION,
+ TYPE_PROMPT,
+ TYPE_SPLASH,
+ TYPE_DOCKAPP,
+ TYPE_LAST
+};
+
+enum {
+ /* color array indices */
+ COLOR_DEF = 0,
+ COLOR_ALT = 1,
+
+ COLOR_MID = 0,
+ COLOR_LIGHT = 1,
+ COLOR_DARK = 2,
+ COLOR_LAST = 3
+};
+
+enum {
+ /* decoration style array indices */
+ FOCUSED,
+ UNFOCUSED,
+ URGENT,
+ STYLE_LAST
+};
+
+enum {
+ /* window layer array indices */
+ LAYER_DESKTOP,
+ LAYER_BELOW,
+ LAYER_NORMAL,
+ LAYER_ABOVE,
+ LAYER_DOCK,
+ LAYER_SPLASH,
+ LAYER_FULLSCREEN,
+ LAYER_LAST
+};
+
+enum {
+ /* strut elements array indices */
+ STRUT_LEFT = 0,
+ STRUT_RIGHT = 1,
+ STRUT_TOP = 2,
+ STRUT_BOTTOM = 3,
+ STRUT_LEFT_START_Y = 4,
+ STRUT_LEFT_END_Y = 5,
+ STRUT_RIGHT_START_Y = 6,
+ STRUT_RIGHT_END_Y = 7,
+ STRUT_TOP_START_X = 8,
+ STRUT_TOP_END_X = 9,
+ STRUT_BOTTOM_START_X = 10,
+ STRUT_BOTTOM_END_X = 11,
+ STRUT_LAST = 12,
+};
+
+enum {
+ /* container states bits*/
+ ABOVE = 0x01,
+ BELOW = 0x02,
+ FULLSCREEN = 0x04,
+ MAXIMIZED = 0x08,
+ MINIMIZED = 0x10,
+ SHADED = 0x20,
+ STICKY = 0x40,
+ USERPLACED = 0x80,
+};
+
+enum {
+ /* container state action */
+ REMOVE = 0,
+ ADD = 1,
+ TOGGLE = 2
+};
+
+enum Octant {
+ /* window eight sections (aka octants) */
+ 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),
+};
+
+TAILQ_HEAD(Queue, Object);
+struct Object {
+ /*
+ * An object is any structure that can directly contain a
+ * window of an application. For example, an open Gimp
+ * application can have two menu windows (on its multi-window
+ * mode), a dialog window, and the main application window.
+ * For each window of that application we have one object:
+ * - The Menu struct for the menu windows.
+ * - The Dialog struct for the dialog window.
+ * - The Tab struct for the main window.
+ *
+ * The application's main window is contained inside a tab,
+ * which is contained inside a row, which is contained inside a
+ * column, which is contained inside a container. but only the
+ * tab is an object, in the sense that it directly contains a
+ * window of the application. The row, the column, and the
+ * container are not objects in this sense, because they do not
+ * directly contain an application window (they contain it
+ * indirectly).
+ *
+ * Each object structure begins with an Object struct, so they
+ * can be polymorphically manipulated as its actual type or as
+ * an Object. For example, we can cast a Dialog into an Object
+ * and vice-versa (if we know that the object is a type).
+ *
+ * The structs that can directly contain an application window
+ * are the following:
+ * - struct Tab;
+ * - struct Dialog;
+ * - struct Menu;
+ * - struct Bar;
+ * - struct Dockapp;
+ * - struct Splash;
+ * - struct Notification;
+ */
+ TAILQ_ENTRY(Object) entry;
+ Window win;
+ int type;
+};
+
+TAILQ_HEAD(RowQueue, Row);
+struct Row {
+ TAILQ_ENTRY(Row) entry;
+ struct Queue tabq; /* list of tabs */
+
+ /*
+ * Each columnt is split vertically into rows; and each row
+ * contains tabs. We maintain in a row its list of tabs, and
+ * a pointer to its parent column.
+ */
+ struct Column *col; /* pointer to parent column */
+ struct Tab *seltab; /* pointer to selected tab */
+ int ntabs; /* number of tabs */
+
+ /* At the bottom of each column, except the bottomost one, ther
+ * is a divisor handle which can be dragged to resize the row.
+ * There are also other windows forming a row:
+ * - The divisor.
+ * - The frame below the tab windows.
+ * - The title bar where the tabs are drawn.
+ * - The left (row maximize) button.
+ * - The right (close) button.
+ */
+ Window div; /* horizontal division between rows */
+ Window frame; /* where tab frames are */
+ Window bar; /* title bar frame */
+ Window bl; /* left button */
+ Window br; /* right button */
+
+ /*
+ * We only keep the vertical geometry of a row (ie', its y
+ * position and its height), because, since a row horizontally
+ * spans its parent column width, its horizontal geometry is
+ * equal to the geometry of its parent column.
+ */
+ double fact; /* factor of height relative to container */
+ int y, h; /* row geometry */
+
+ /*
+ * Three of the windows of the row must be drawn. First we draw
+ * into their pixmap, and then copy the contents of the pixmap
+ * into the windows thenselves whenever they are damaged. It is
+ * necessary to redraw on the pixmap only when the row resizes;
+ * so we save the previous width of the row to compare with the
+ * row's current width.
+ */
+ Pixmap pixbar; /* pixmap for the title bar */
+ Pixmap pixbl; /* pixmap for left button */
+ Pixmap pixbr; /* pixmap for right button */
+ int pw;
+
+ /*
+ * Whether the frame is unmapped
+ */
+ int isunmapped;
+};
+
+TAILQ_HEAD(ColumnQueue, Column);
+struct Column {
+ TAILQ_ENTRY(Column) entry;
+ struct RowQueue rowq; /* list of rows */
+
+ /*
+ * Each container is split horizontally into columns; and each
+ * column is split vertically into rows. We maintain in a
+ * column its list of rows, and a pointer to its parent
+ * container.
+ */
+ struct Container *c; /* pointer to parent container */
+ struct Row *selrow; /* pointer to selected row */
+
+ /*
+ * At the right of each column, except the rightmost one, there
+ * is a divisor handle which can be dragged to resize the
+ * columns.
+ */
+ Window div; /* vertical division between columns */
+
+ /*
+ * We only keep the horizontal geometry of a column (ie', its x
+ * position and its width), because, since a column vertically
+ * spans its parent container height, its vertical geometry is
+ * equal to the geometry of its parent container.
+ */
+ double fact; /* factor of width relative to container */
+ int nrows; /* number of rows */
+ int x, w; /* column geometry */
+};
+
+TAILQ_HEAD(ContainerQueue, Container);
+struct Container {
+ /*
+ * The container is the main entity the user interact with, and
+ * the windows of most applications are mapped into a container.
+ *
+ * A container is an element of two queues:
+ * - The focus queue is a list of containers in the focus order.
+ * There is only one focus queue.
+ * - A raise queue is a list of containers in the Z-axis order.
+ * There are one raise queue for each layer of containers
+ * (fullscreen, above, middle and below).
+ */
+ TAILQ_ENTRY(Container) entry; /* entry for the focus queue */
+ TAILQ_ENTRY(Container) raiseentry; /* entry for a raise queue */
+
+ /*
+ * A container contains a list of columns.
+ * A column contains a list of rows.
+ * A row contains a list of tabs.
+ * A tab contains an application window and a list of menus and
+ * a list of dialogs.
+ */
+ struct ColumnQueue colq; /* list of columns in container */
+ struct Column *selcol; /* pointer to selected container */
+ int ncols; /* number of columns */
+
+ /*
+ * A container appears on a certain desktop of a certain monitor.
+ */
+ struct Monitor *mon; /* monitor container is on */
+ int desk; /* desktop container is on */
+
+ /*
+ * A container is composed of a frame window, mapped below all
+ * the columns/rows/tabs/etc. Inside the frame window, there
+ * are mapped the cursor windows, one for each border and corner
+ * of the container's frame. Each cursor window is associated
+ * to a pointer cursor (that's why hovering the pointer over the
+ * bottom right corner of the frame turns the cursor into an
+ * arrow.
+ */
+ Window frame; /* window to reparent the contents of the container */
+ Window curswin[BORDER_LAST]; /* dummy window used for change cursor while hovering borders */
+
+ /*
+ * The frame must be drawn, with all its borders and corner
+ * handles. First we draw into a pixmap, and then copy the
+ * contents of the pixmap into the frame window itself whenever
+ * the frame window is damaged. It is necessary to redraw on
+ * the pixmap only when the container resizes; so we save the
+ * width and height of the pixmap to compare with the size of
+ * the container.
+ */
+ Pixmap pix; /* pixmap to draw the frame */
+ int pw, ph; /* pixmap width and height */
+
+ /*
+ * A container has three geometries (position and size): one for
+ * when it is maximized, one for when it is fullscreen, and one
+ * for when it is floating. The maximized and fullscreen
+ * geometry of a container is obvious (they can be inferred from
+ * the monitor size). We then save the non-maximized geometry.
+ * We also save the current geometry (which can be one of those
+ * three).
+ */
+ int x, y, w, h, b; /* current geometry */
+ int nx, ny, nw, nh; /* non-maximized geometry */
+
+ /*
+ * A container can have several states. Except for the `layer`
+ * state (which has tree values), all states are boolean (can or
+ * cannot be valid at a given time) and begin with "is-". The
+ * possible values for boolean states are zero and nonzero. The
+ * possible values for the `layer` state is negative (below),
+ * zero (middle) or positive (above).
+ */
+ int ismaximized, issticky; /* window states */
+ int isminimized, isshaded; /* window states */
+ int isfullscreen; /* whether container is fullscreen */
+ int ishidden; /* whether container is hidden */
+ int layer; /* stacking order */
+};
+
+TAILQ_HEAD(MonitorQueue, Monitor);
+struct Monitor {
+ /*
+ * Each monitor has a focused desktop (a value between 0 and
+ * config.ndesktops - 1). A monitor also has two geometries:
+ * its full actual geometry (a rectangle spanning the entire
+ * monitor), and the window area (a rectangle spanning only
+ * the area without any dock, bar, panel, etc (that is, the
+ * area where containers can be maximized into).
+ */
+ TAILQ_ENTRY(Monitor) entry;
+ int seldesk; /* focused desktop on that monitor */
+ int mx, my, mw, mh; /* monitor size */
+ int wx, wy, ww, wh; /* window area */
+};
+
+struct Tab {
+ struct Object obj;
+
+ /*
+ * Additionally to the application window (in .obj), a tab
+ * contains a list of swallowed dialogs (unless -d is given) and
+ * a list of detached menus. A tab also contains a pointer to
+ * its parent row.
+ */
+ struct Queue dialq; /* queue of dialogs */
+ struct Queue menuq; /* queue of menus */
+ struct Row *row; /* pointer to parent row */
+
+ /*
+ * The application whose windows the tab maintains can be
+ * grouped under a leader window (which is not necessarily
+ * mapped on the screen).
+ */
+ Window leader; /* the group leader of the window */
+
+ /*
+ * Visually, a tab is composed of a title bar (aka tab); and a
+ * frame window which the application window and swallowed
+ * dialog windows are child of.
+ */
+ Window title; /* title bar (tab) */
+ Window frame; /* window to reparent the client window */
+
+ /*
+ * First we draw into pixmaps, and then copy their contents
+ * into the frame and title windows themselves whenever they
+ * are damaged. It is necessary to redraw on the pixmaps only
+ * when the titlebar or frame resizes; so we save the geometry
+ * of hte pixmaps to compare with the size of their windows.
+ */
+ Pixmap pix; /* pixmap to draw the background of the frame */
+ Pixmap pixtitle; /* pixmap to draw the background of the title window */
+ int ptw; /* pixmap width for the title window */
+ int pw, ph; /* pixmap size of the frame */
+
+ /*
+ * Geometry of the title bar (aka tab).
+ */
+ int x, w; /* title bar geometry */
+
+ /*
+ * Dirty hack to ignore unmap notifications.
+ */
+ int ignoreunmap; /* number of unmapnotifys to ignore */
+
+ /*
+ * Name of the tab's application window, its size and urgency.
+ */
+ int winw, winh; /* window geometry */
+ int isurgent; /* whether tab is urgent */
+ char *name; /* client name */
+};
+
+struct Dialog {
+ struct Object obj;
+ struct Tab *tab; /* pointer to parent tab */
+
+ /*
+ * Frames, pixmaps, saved pixmap geometry, etc
+ */
+ Window frame; /* window to reparent the client window */
+ Pixmap pix; /* pixmap to draw the frame */
+ int pw, ph; /* pixmap size */
+
+ /*
+ * Dialog geometry, which can be resized as the user resizes the
+ * container. The dialog can grow up to a maximum width and
+ * height.
+ */
+ int x, y, w, h; /* geometry of the dialog inside the tab frame */
+ int maxw, maxh; /* maximum size of the dialog */
+
+ int ignoreunmap; /* number of unmapnotifys to ignore */
+};
+
+struct Menu {
+ struct Object obj;
+ struct Tab *tab; /* pointer to parent tab */
+
+ /*
+ * Frames, pixmaps, saved pixmap geometry, etc
+ */
+ Window titlebar; /* close button */
+ Window button; /* close button */
+ Window frame; /* frame window */
+ Pixmap pix; /* pixmap to draw the frame */
+ Pixmap pixbutton; /* pixmap to draw the button */
+ Pixmap pixtitlebar; /* pixmap to draw the titlebar */
+ int pw, ph; /* pixmap size */
+ int tw, th; /* titlebar pixmap size */
+
+ int x, y, w, h; /* geometry of the menu window + the frame */
+ int ignoreunmap; /* number of unmapnotifys to ignore */
+ char *name; /* client name */
+};
+
+struct Bar {
+ struct Object obj;
+ int strut[STRUT_LAST]; /* strut values */
+ int ispartial; /* whether strut has 12 elements rather than 4 */
+};
+
+struct Dockapp {
+ struct Object obj;
+ int x, y, w, h; /* dockapp position and size */
+ int ignoreunmap; /* number of unmap requests to ignore */
+ int dockpos; /* position of the dockapp in the dock */
+};
+
+struct Splash {
+ struct Object obj;
+ int x, y, w, h; /* splash screen geometry */
+};
+
+struct Notification {
+ struct Object obj;
+ Window frame; /* window to reparent the 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 */
+};
+
+struct WM {
+ /*
+ * The window manager maintains a list of monitors and several
+ * window-holding entities such as containers and bars.
+ */
+ struct MonitorQueue monq; /* queue of monitors */
+ struct Queue barq; /* queue of bars */
+ struct Queue splashq; /* queue of splash screen windows */
+ struct Queue notifq; /* queue of notifications */
+ struct ContainerQueue focusq; /* queue of containers ordered by focus history */
+ int nclients; /* total number of container windows */
+
+ /*
+ * Containers are listed by the focusq queue. However, a
+ * container can also be listed under another list according to
+ * its layer on the Z-axis.
+ */
+ struct ContainerQueue fullq; /* queue of containers ordered from topmost to bottommost */
+ struct ContainerQueue aboveq; /* queue of containers ordered from topmost to bottommost */
+ struct ContainerQueue centerq; /* queue of containers ordered from topmost to bottommost */
+ struct ContainerQueue belowq; /* queue of containers ordered from topmost to bottommost */
+
+ /*
+ * We maintain a pointer to the focused container and the
+ * previously focused one. And also a pointer to the focused
+ * monitor which will receive new containers.
+ */
+ struct Container *focused; /* pointer to focused container */
+ struct Container *prevfocused; /* pointer to previously focused container */
+ struct Monitor *selmon; /* pointer to selected monitor */
+
+ /*
+ * Shod uses a dummy window called wmcheckwin for multiple
+ * purposes:
+ * - It is necessary to implement EWMH's _NET_SUPPORTING_WM_CHECK property.
+ * - It is hidden out of the screen and gets the focus when no
+ * window has the keyboard focus.
+ * - When the user reorder the tiles in a container by dragging a
+ * title bar with the right mouse button, this window follows
+ * the mouse pointer to indicate that dragging is in action.
+ *
+ * We first draw into the `wmcheckpix` pixmap and then copy its
+ * contents into the `wmcheckwin` when the window is damaged.
+ */
+ Window wmcheckwin; /* dummy window required by EWMH */
+ Pixmap wmcheckpix;
+
+ /*
+ * Shod uses an array of dummy windows to raise the containers
+ * into the layers of the Z-axis.
+ */
+ Window layerwins[LAYER_LAST]; /* dummy windows used to set stacking order */
+
+ Cursor cursors[CURSOR_LAST]; /* cursors for the mouse pointer */
+ int showingdesk; /* whether the desktop is being shown */
+ int minsize; /* minimum size of a container */
+};
+
+struct Dock {
+ /* the dock */
+ struct Queue dappq;
+ Window win; /* dock window */
+ Pixmap pix; /* dock pixmap */
+ int x, y, w, h; /* dock geometry */
+ int pw, ph; /* dock pixmap size */
+ int mapped; /* whether dock is mapped */
+};
+
+struct Config {
+ /* default configuration, set at `config.c` */
+
+ unsigned int modifier; /* modifier (default: Alt) */
+ int floatdialog; /* whether -d is passed */
+ int honorconfig; /* whether -c is passed */
+ int sloppyfocus; /* whether -s is passed */
+ int ndesktops; /* number of desktops */
+ int notifgap; /* gap between notifications */
+ int dockwidth, dockspace; /* dock geometry */
+ int snap; /* snap proximity */
+ int borderwidth; /* width of the border frame */
+ int titlewidth; /* height of the title bar */
+ int shadowthickness; /* thickness of the 3D shadows */
+
+ /* gravities (N for north, NE for northeast, etc) */
+ const char *notifgravity;
+ const char *dockgravity;
+
+ /* font and color names */
+ const char *font;
+ const char *foreground;
+ const char *dockcolors[2];
+ const char *bordercolors[STYLE_LAST][COLOR_LAST];
+
+ /* hardcoded rules */
+ struct Rule {
+ /* matching class, instance and role */
+ const char *class;
+ const char *instance;
+ const char *role;
+
+ /* type, state, etc to apply on matching windows */
+ int type;
+ int state;
+ } *rules;
+
+ /* the values below are computed from the values above */
+ int corner; /* = .borderwidth + .titlewidth */
+ int divwidth; /* = .borderwidth */
+};
+
+typedef void Managefunc(struct Tab *, struct Monitor *, int, Window, Window, XRectangle, int, int);
+typedef int Unmanagefunc(struct Object *obj, int ignoreunmap);
+
+/* container routines */
+void containerdel(struct Container *c);
+void containermoveresize(struct Container *c);
+void containerdecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, int recursive, enum Octant o);
+void containerredecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, enum Octant o);
+void containercalccols(struct Container *c, int recalcfact, int recursive);
+void containersetstate(struct Tab *, Atom *, unsigned long);
+void containerincrmove(struct Container *c, int x, int y);
+void containerraise(struct Container *c, int isfullscreen, int layer);
+void containerconfigure(struct Container *c, unsigned int valuemask, XWindowChanges *wc);
+void containersendtodeskandfocus(struct Container *c, struct Monitor *mon, unsigned long desk);
+void containerplace(struct Container *c, struct Monitor *mon, int desk, int userplaced);
+void containerdelrow(struct Row *row);
+void tabdetach(struct Tab *tab, int x, int y);
+void tabfocus(struct Tab *tab, int gotodesk);
+void tabdecorate(struct Tab *t, int pressed);
+void tabupdateurgency(struct Tab *t, int isurgent);
+void rowstack(struct Column *col, struct Row *row);
+void dialogconfigure(struct Dialog *d, unsigned int valuemask, XWindowChanges *wc);
+void dialogmoveresize(struct Dialog *dial);
+void menuincrmove(struct Menu *menu, int x, int y);
+void menuconfigure(struct Menu *menu, unsigned int valuemask, XWindowChanges *wc);
+void menumoveresize(struct Menu *menu);
+void menudecorate(struct Menu *menu, int titlepressed);
+void menuaddraise(struct Tab *tab, struct Menu *menu);
+void menuplace(struct Menu *menu);
+void deskfocus(struct Monitor *mon, int desk, int focus);
+void deskshow(int show);
+int tabattach(struct Container *c, struct Tab *t, int x, int y);
+int containerisshaded(struct Container *c);
+
+/* other object routines */
+void barstrut(struct Bar *bar);
+void notifplace(void);
+void splashplace(struct Splash *splash);
+void dockupdate(void);
+
+/* monitor routines */
+struct Monitor *getmon(int x, int y);
+void mondel(struct Monitor *mon);
+void monupdate(void);
+void monupdatearea(void);
+void fitmonitor(struct Monitor *mon, int *x, int *y, int *w, int *h, float factor);
+
+/* wm hints and messages routines */
+void icccmdeletestate(Window win);
+void icccmwmstate(Window win, int state);
+void ewmhinit(const char *wmname);
+void ewmhsetframeextents(Window win, int b, int t);
+void ewmhsetclients(void);
+void ewmhsetclientsstacking(void);
+void ewmhsetstate(struct Container *c);
+void ewmhsetwmdesktop(struct Container *c);
+void ewmhsetactivewindow(Window w);
+void ewmhsetcurrentdesktop(unsigned long n);
+void ewmhsetshowingdesktop(int n);
+void shodgrouptab(struct Container *c);
+void shodgroupcontainer(struct Container *c);
+void winupdatetitle(Window win, char **name);
+void winnotify(Window win, int x, int y, int w, int h);
+void winclose(Window win);
+
+/* decoration routines */
+void pixmapnew(Pixmap *pix, Window win, int w, int h);
+void drawcommit(Pixmap pix, Window win, int w, int h);
+void drawborders(Pixmap pix, int w, int h, int style);
+void drawbackground(Pixmap pix, int x, int y, int w, int h, int style);
+void drawframe(Pixmap pix, int isshaded, int w, int h, enum Octant o, int style);
+void drawshadow(Pixmap pix, int x, int y, int w, int h, int style, int pressed);
+void drawtitle(Drawable pix, const char *text, int w, int drawlines, int style, int pressed);
+void drawprompt(Pixmap pix, int w, int h);
+void drawdock(Pixmap pix, int w, int h);
+void buttonleftdecorate(Window button, Pixmap pix, int style, int pressed);
+void buttonrightdecorate(Window button, Pixmap pix, int style, int pressed);
+void copypixmap(Window win);
+void inittheme(void);
+void cleantheme(void);
+
+/* window management routines */
+Managefunc managedockapp;
+Managefunc managedialog;
+Managefunc managesplash;
+Managefunc manageprompt;
+Managefunc managenotif;
+Managefunc managemenu;
+Managefunc managetab;
+Managefunc managebar;
+Unmanagefunc unmanagedockapp;
+Unmanagefunc unmanagedialog;
+Unmanagefunc unmanagesplash;
+Unmanagefunc unmanageprompt;
+Unmanagefunc unmanagenotif;
+Unmanagefunc unmanagemenu;
+Unmanagefunc unmanagetab;
+Unmanagefunc unmanagebar;
+void scan(void);
+
+/* function tables */
+extern void (*managefuncs[])(struct Tab *, struct Monitor *, int, Window, Window, XRectangle, int, int);
+extern int (*unmanagefuncs[])(struct Object *, int);
+
+/* extern variables */
+extern XSetWindowAttributes clientswa;
+extern unsigned long clientmask;
+extern void (*xevents[LASTEvent])(XEvent *);
+extern struct Config config;
+extern struct WM wm;
+extern struct Dock dock;
diff --git a/shodc.c b/shodc.c
@@ -1,11 +1,8 @@
#include <err.h>
#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
#include <unistd.h>
-#include <X11/Xlib.h>
-#include <X11/Xatom.h>
-#include <X11/Xutil.h>
+
+#include "xutil.h"
#define NAMEMAXLEN 128
#define DIRECT_ACTION 2
@@ -49,42 +46,8 @@ enum Direction {
_SHOD_FOCUS_NEXT_WINDOW = 12,
};
-/* atoms for the list operation */
-enum {
- UTF8STRING,
- _NET_WM_NAME,
- _NET_WM_VISIBLE_NAME,
- _NET_WM_DESKTOP,
- _NET_NUMBER_OF_DESKTOPS,
- _NET_DESKTOP_NAMES,
- _NET_CLOSE_WINDOW,
- _NET_MOVERESIZE_WINDOW,
- _NET_CURRENT_DESKTOP,
- _NET_ACTIVE_WINDOW,
- _NET_CLIENT_LIST,
- _NET_CLIENT_LIST_STACKING,
- _NET_WM_STATE,
- _NET_WM_STATE_STICKY,
- _NET_WM_STATE_MAXIMIZED_VERT,
- _NET_WM_STATE_MAXIMIZED_HORZ,
- _NET_WM_STATE_HIDDEN,
- _NET_WM_STATE_SHADED,
- _NET_WM_STATE_FULLSCREEN,
- _NET_WM_STATE_ABOVE,
- _NET_WM_STATE_BELOW,
- _NET_WM_STATE_DEMANDS_ATTENTION,
- _NET_WM_STATE_FOCUSED,
- _SHOD_GROUP_TAB,
- _SHOD_GROUP_CONTAINER,
- ATOM_LAST
-};
-
/* global variables */
-static Display *dpy;
-static int screen;
-static Window root;
static Window active;
-static Atom atoms[ATOM_LAST];
/* show usage and exit */
static void
@@ -101,27 +64,6 @@ usage(void)
exit(1);
}
-/* call calloc checking for errors */
-static void *
-ecalloc(size_t nmemb, size_t size)
-{
- void *p;
- if ((p = calloc(nmemb, size)) == NULL)
- err(1, "calloc");
- 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;
-}
-
/* send client message to root window */
static void
clientmsg(Window win, Atom atom, unsigned long d0, unsigned long d1, unsigned long d2, unsigned long d3, unsigned long d4)
@@ -172,74 +114,14 @@ getwin(const char *s)
return (Window)strtol(s, NULL, 0);
}
-/* get window property from root window */
-static unsigned long
-getwinprop(Window win, Atom prop, Window **wins)
-{
- unsigned char *list;
- unsigned long len;
- unsigned long dl; /* dummy variable */
- int di; /* dummy variable */
- Atom da; /* dummy variable */
-
- list = NULL;
- if (XGetWindowProperty(dpy, win, prop, 0L, 1024, False, XA_WINDOW,
- &da, &di, &len, &dl, &list) != Success || list == NULL) {
- *wins = NULL;
- return 0;
- }
- *wins = (Window *)list;
- return len;
-}
-
/* get array of windows */
static unsigned long
getwins(Window **wins, int sflag)
{
if (sflag)
- return getwinprop(root, atoms[_NET_CLIENT_LIST_STACKING], wins);
+ return getwinsprop(root, atoms[_NET_CLIENT_LIST_STACKING], wins);
else
- return getwinprop(root, atoms[_NET_CLIENT_LIST], wins);
-}
-
-/* get atom property from given window */
-static unsigned long
-getatomprop(Window win, Atom prop, Atom **atoms)
-{
- unsigned char *list;
- unsigned long len;
- unsigned long dl; /* dummy variable */
- int di; /* dummy variable */
- Atom da; /* dummy variable */
-
- list = NULL;
- if (XGetWindowProperty(dpy, win, prop, 0L, 1024, False, XA_ATOM,
- &da, &di, &len, &dl, &list) != Success || list == NULL) {
- *atoms = NULL;
- return 0;
- }
- *atoms = (Atom *)list;
- return len;
-}
-
-/* get cardinal property from given window */
-static unsigned long
-getcardprop(Window win, Atom prop)
-{
- unsigned long ret;
- unsigned char *u;
- unsigned long dl; /* dummy variable */
- int di; /* dummy variable */
- Atom da; /* dummy variable */
-
- u = NULL;
- if (XGetWindowProperty(dpy, win, prop, 0L, 1L, False, XA_CARDINAL,
- &da, &di, &dl, &dl, &u) != Success || u == NULL) {
- return 0;
- }
- ret = *(unsigned long *)u;
- XFree(u);
- return ret;
+ return getwinsprop(root, atoms[_NET_CLIENT_LIST], wins);
}
/* get array of desktop names, return size of array */
@@ -254,7 +136,7 @@ getdesknames(char **desknames)
if (XGetWindowProperty(dpy, root, atoms[_NET_DESKTOP_NAMES], 0, ~0, False,
- UTF8STRING, &da, &di, &len, &dl, &str) ==
+ UTF8_STRING, &da, &di, &len, &dl, &str) ==
Success && str) {
*desknames = (char *)str;
} else {
@@ -277,7 +159,7 @@ getwinname(Window win)
int di;
Atom da;
- if (XGetWindowProperty(dpy, win, atoms[_NET_WM_NAME], 0L, 8L, False, UTF8STRING,
+ if (XGetWindowProperty(dpy, win, atoms[_NET_WM_NAME], 0L, 8L, False, UTF8_STRING,
&da, &di, &size, &dl, &p) == Success && p) {
name = estrndup((char *)p, NAMEMAXLEN);
XFree(p);
@@ -291,41 +173,6 @@ getwinname(Window win)
return name;
}
-/* intern atoms */
-static void
-initatoms(void)
-{
- char *atomnames[ATOM_LAST] = {
- [UTF8STRING] = "UTF8STRING",
- [_NET_WM_NAME] = "_NET_WM_NAME",
- [_NET_WM_VISIBLE_NAME] = "_NET_WM_VISIBLE_NAME",
- [_NET_WM_DESKTOP] = "_NET_WM_DESKTOP",
- [_NET_NUMBER_OF_DESKTOPS] = "_NET_NUMBER_OF_DESKTOPS",
- [_NET_DESKTOP_NAMES] = "_NET_DESKTOP_NAMES",
- [_NET_CLOSE_WINDOW] = "_NET_CLOSE_WINDOW",
- [_NET_MOVERESIZE_WINDOW] = "_NET_MOVERESIZE_WINDOW",
- [_NET_CURRENT_DESKTOP] = "_NET_CURRENT_DESKTOP",
- [_NET_ACTIVE_WINDOW] = "_NET_ACTIVE_WINDOW",
- [_NET_CLIENT_LIST] = "_NET_CLIENT_LIST",
- [_NET_CLIENT_LIST_STACKING] = "_NET_CLIENT_LIST_STACKING",
- [_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_SHADED] = "_NET_WM_STATE_SHADED",
- [_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_DEMANDS_ATTENTION] = "_NET_WM_STATE_DEMANDS_ATTENTION",
- [_NET_WM_STATE_FOCUSED] = "_NET_WM_STATE_FOCUSED",
- [_SHOD_GROUP_TAB] = "_SHOD_GROUP_TAB",
- [_SHOD_GROUP_CONTAINER] = "_SHOD_GROUP_CONTAINER",
- };
-
- XInternAtoms(dpy, atomnames, ATOM_LAST, False, atoms);
-}
-
/* close window */
static void
closewin(int argc, char *argv[])
@@ -497,19 +344,19 @@ longlist(Window win)
state[LIST_URGENCY] = 'u';
XFree(wmhints);
}
- if (getwinprop(win, XA_WM_TRANSIENT_FOR, &list) > 0) {
+ if (getwinsprop(win, XA_WM_TRANSIENT_FOR, &list) > 0) {
if (*list != None) {
state[LIST_DIALOG] = 'd';
}
XFree(list);
}
- if (getwinprop(win, atoms[_SHOD_GROUP_CONTAINER], &list) > 0) {
+ if (getwinsprop(win, atoms[_SHOD_GROUP_CONTAINER], &list) > 0) {
if (*list != None) {
container = *list;
}
XFree(list);
}
- if (getwinprop(win, atoms[_SHOD_GROUP_TAB], &list) > 0) {
+ if (getwinsprop(win, atoms[_SHOD_GROUP_TAB], &list) > 0) {
if (*list != None) {
tab = *list;
}
@@ -517,7 +364,7 @@ longlist(Window win)
}
if (win == active)
state[LIST_ACTIVE] = 'a';
- if ((natoms = getatomprop(win, atoms[_NET_WM_STATE], &as)) > 0) {
+ if ((natoms = getatomsprop(win, atoms[_NET_WM_STATE], &as)) > 0) {
for (i = 0; i < natoms; i++) {
if (as[i] == atoms[_NET_WM_STATE_STICKY]) {
state[LIST_STICKY] = 'y';
@@ -591,7 +438,7 @@ listdesks(int argc, char *argv[])
nameslen = getdesknames(&desknames);
wdesk = ecalloc(ndesks, sizeof *wdesk);
urgdesks = ecalloc(ndesks, sizeof *urgdesks);
- nwins = getwinprop(root, atoms[_NET_CLIENT_LIST], &wins);
+ nwins = getwinsprop(root, atoms[_NET_CLIENT_LIST], &wins);
for (i = 0; i < nwins; i++) {
desk = getcardprop(wins[i], atoms[_NET_WM_DESKTOP]);
hints = XGetWMHints(dpy, wins[i]);
@@ -756,11 +603,7 @@ main(int argc, char *argv[])
if (argc < 2)
usage();
- if ((dpy = XOpenDisplay(NULL)) == NULL)
- errx(1, "could not open display");
- screen = DefaultScreen(dpy);
- root = RootWindow(dpy, screen);
-
+ xinit();
initatoms();
active = getactivewin();
if (strcmp(argv[1], "close") == 0)
diff --git a/xapp.c b/xapp.c
@@ -0,0 +1,2106 @@
+#include "shod.h"
+
+#define DIV 15 /* see containerplace() for details */
+
+/* get next focused container after old on given monitor and desktop */
+static struct Container *
+getnextfocused(struct Monitor *mon, int desk)
+{
+ struct Container *c;
+
+ TAILQ_FOREACH_REVERSE(c, &wm.focusq, ContainerQueue, entry)
+ if (!c->isminimized && c->mon == mon && (c->issticky || c->desk == desk))
+ return c;
+ return NULL;
+}
+
+/* snap to edge */
+static void
+snaptoedge(int *x, int *y, int w, int h)
+{
+ struct Container *c;
+
+ if (config.snap <= 0)
+ return;
+ if (abs(*y - wm.selmon->wy) < config.snap)
+ *y = wm.selmon->wy;
+ if (abs(*y + h - wm.selmon->wy - wm.selmon->wh) < config.snap)
+ *y = wm.selmon->wy + wm.selmon->wh - h;
+ if (abs(*x - wm.selmon->wx) < config.snap)
+ *x = wm.selmon->wx;
+ if (abs(*x + w - wm.selmon->wx - wm.selmon->ww) < config.snap)
+ *x = wm.selmon->wx + wm.selmon->ww - w;
+ TAILQ_FOREACH(c, &wm.focusq, entry) {
+ if (!c->isminimized && c->mon == wm.selmon &&
+ (c->issticky || c->desk == wm.selmon->seldesk)) {
+ if (*x + w >= c->x && *x <= c->x + c->w) {
+ if (abs(*y + h - c->y) < config.snap) {
+ *y = c->y - h;
+ }
+ if (abs(*y - c->y) < config.snap) {
+ *y = c->y;
+ }
+ if (abs(*y + h - c->y - c->h) < config.snap) {
+ *y = c->y + c->h - h;
+ }
+ if (abs(*y - c->y - c->h) < config.snap) {
+ *y = c->y + c->h;
+ }
+ }
+ if (*y + h >= c->y && *y <= c->y + c->h) {
+ if (abs(*x + w - c->x) < config.snap) {
+ *x = c->x - w;
+ }
+ if (abs(*x - c->x) < config.snap) {
+ *x = c->x;
+ }
+ if (abs(*x + w - c->x - c->w) < config.snap) {
+ *x = c->x + c->w - w;
+ }
+ if (abs(*x - c->x - c->w) < config.snap) {
+ *x = c->x + c->w;
+ }
+ }
+ }
+ }
+}
+
+/* increment number of clients */
+static void
+clientsincr(void)
+{
+ wm.nclients++;
+}
+
+/* decrement number of clients */
+static void
+clientsdecr(void)
+{
+ wm.nclients--;
+}
+
+/* check if desktop is visible */
+static int
+deskisvisible(struct Monitor *mon, int desk)
+{
+ return mon->seldesk == desk;
+}
+
+/* get decoration style (and state) of container */
+static int
+containergetstyle(struct Container *c)
+{
+ return (c == wm.focused) ? FOCUSED : UNFOCUSED;
+}
+
+/* get tab decoration style */
+static int
+tabgetstyle(struct Tab *t)
+{
+ if (t == NULL)
+ return UNFOCUSED;
+ if (t->isurgent)
+ return URGENT;
+ if (t->row->col->c == wm.focused)
+ return FOCUSED;
+ return UNFOCUSED;
+}
+
+/* clear window urgency */
+static void
+tabclearurgency(struct Tab *tab)
+{
+ XWMHints wmh = {0};
+
+ XSetWMHints(dpy, tab->obj.win, &wmh);
+ tab->isurgent = 0;
+}
+
+/* commit tab size and position */
+static void
+tabmoveresize(struct Tab *t)
+{
+ XMoveResizeWindow(dpy, t->title, t->x, 0, t->w, config.titlewidth);
+ if (t->ptw != t->w) {
+ tabdecorate(t, 0);
+ }
+ winnotify(t->obj.win, t->row->col->c->x + t->row->col->x, t->row->col->c->y + t->row->y + config.titlewidth, t->winw, t->winh);
+}
+
+/* commit titlebar size and position */
+static void
+titlebarmoveresize(struct Row *row, int x, int y, int w)
+{
+ XMoveResizeWindow(dpy, row->bar, x, y, w, config.titlewidth);
+ XMoveWindow(dpy, row->bl, 0, 0);
+ XMoveWindow(dpy, row->br, w - config.titlewidth, 0);
+}
+
+/* calculate size of dialogs of a tab */
+static void
+dialogcalcsize(struct Dialog *dial)
+{
+ struct Tab *tab;
+
+ tab = dial->tab;
+ dial->w = max(1, min(dial->maxw, tab->winw - 2 * config.borderwidth));
+ dial->h = max(1, min(dial->maxh, tab->winh - 2 * config.borderwidth));
+ dial->x = tab->winw / 2 - dial->w / 2;
+ dial->y = tab->winh / 2 - dial->h / 2;
+}
+
+/* create new dialog */
+static struct Dialog *
+dialognew(Window win, int maxw, int maxh, int ignoreunmap)
+{
+ struct Dialog *dial;
+
+ dial = emalloc(sizeof(*dial));
+ *dial = (struct Dialog){
+ .pix = None,
+ .maxw = maxw,
+ .maxh = maxh,
+ .ignoreunmap = ignoreunmap,
+ .obj.win = win,
+ .obj.type = TYPE_DIALOG,
+ };
+ dial->frame = XCreateWindow(dpy, root, 0, 0, maxw, maxh, 0, depth, CopyFromParent, visual, clientmask, &clientswa),
+ XReparentWindow(dpy, dial->obj.win, dial->frame, 0, 0);
+ XMapWindow(dpy, dial->obj.win);
+ return dial;
+}
+
+/* map menus */
+static void
+menumap(struct Tab *tab)
+{
+ struct Object *menu;
+
+ if (tab == NULL)
+ return;
+ TAILQ_FOREACH(menu, &tab->menuq, entry) {
+ XMapWindow(dpy, ((struct Menu *)menu)->frame);
+ icccmwmstate(menu->win, NormalState);
+ }
+}
+
+/* create new menu */
+static struct Menu *
+menunew(Window win, int x, int y, int w, int h, int ignoreunmap)
+{
+ struct Menu *menu;
+
+ menu = emalloc(sizeof(*menu));
+ *menu = (struct Menu){
+ .titlebar = None,
+ .button = None,
+ .obj.win = win,
+ .obj.type = TYPE_MENU,
+ .pix = None,
+ .pixbutton = None,
+ .pixtitlebar = None,
+ .x = x - config.borderwidth,
+ .y = y - config.borderwidth,
+ .w = w + config.borderwidth * 2,
+ .h = h + config.borderwidth * 2 + config.titlewidth,
+ .ignoreunmap = ignoreunmap,
+ };
+ menu->frame = XCreateWindow(dpy, root, 0, 0,
+ w + config.borderwidth * 2,
+ h + config.borderwidth * 2 + config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa),
+ menu->titlebar = XCreateWindow(dpy, menu->frame, config.borderwidth, config.borderwidth,
+ max(1, menu->w - 2 * config.borderwidth - config.titlewidth),
+ config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ menu->button = XCreateWindow(dpy, menu->frame, menu->w - config.borderwidth - config.titlewidth, config.borderwidth,
+ config.titlewidth, config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ menu->pixbutton = XCreatePixmap(dpy, menu->button, config.titlewidth, config.titlewidth, depth);
+ XDefineCursor(dpy, menu->button, wm.cursors[CURSOR_PIRATE]);
+ XReparentWindow(dpy, menu->obj.win, menu->frame, config.borderwidth, config.borderwidth + config.titlewidth);
+ XMapWindow(dpy, menu->obj.win);
+ XMapWindow(dpy, menu->button);
+ XMapWindow(dpy, menu->titlebar);
+ return menu;
+}
+
+/* remove menu from the menu list */
+static void
+menudelraise(struct Tab *tab, struct Menu *menu)
+{
+ if (TAILQ_EMPTY(&tab->menuq))
+ return;
+ TAILQ_REMOVE(&tab->menuq, (struct Object *)menu, entry);
+}
+
+/* calculate position and width of tabs of a row */
+static void
+rowcalctabs(struct Row *row)
+{
+ struct Object *p, *q;
+ struct Dialog *d;
+ struct Tab *t;
+ int i, x;
+
+ x = config.titlewidth;
+ i = 0;
+ TAILQ_FOREACH(p, &row->tabq, entry) {
+ t = (struct Tab *)p;
+ t->winh = max(1, row->h - config.titlewidth);
+ t->winw = row->col->w;
+ t->w = max(1, ((i + 1) * (t->winw - 2 * config.titlewidth) / row->ntabs) - (i * (t->winw - 2 * config.titlewidth) / row->ntabs));
+ t->x = x;
+ x += t->w;
+ TAILQ_FOREACH(q, &t->dialq, entry) {
+ d = (struct Dialog *)q;
+ dialogcalcsize(d);
+ }
+ i++;
+ }
+}
+
+/* calculate position and height of rows of a column */
+static void
+colcalcrows(struct Column *col, int recalcfact, int recursive)
+{
+ struct Container *c;
+ struct Row *row;
+ int i, y, h, sumh;
+ int content;
+ int recalc;
+
+ c = col->c;
+
+ /* check if rows sum up the height of the container */
+ content = c->h - col->nrows * config.titlewidth - (col->nrows - 1) * config.divwidth - 2 * c->b;
+ sumh = 0;
+ recalc = 0;
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ if (!recalcfact) {
+ if (TAILQ_NEXT(row, entry) == NULL) {
+ row->h = content - sumh + config.titlewidth;
+ } else {
+ row->h = row->fact * content + config.titlewidth;
+ }
+ if (row->h < config.titlewidth) {
+ recalc = 1;
+ }
+ }
+ sumh += row->h - config.titlewidth;
+ }
+ if (sumh != content)
+ recalc = 1;
+
+ if (col->c->isfullscreen && col->c->ncols == 1 && col->nrows == 1) {
+ h = col->c->h + config.titlewidth;
+ y = -config.titlewidth;
+ recalc = 1;
+ } else {
+ h = col->c->h - 2 * c->b - (col->nrows - 1) * config.divwidth;
+ y = c->b;
+ }
+ i = 0;
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ if (recalc)
+ row->h = max(config.titlewidth, ((i + 1) * h / col->nrows) - (i * h / col->nrows));
+ row->fact = (double)(row->h - config.titlewidth) / (double)(content);
+ row->y = y;
+ y += row->h + config.divwidth;
+ if (recursive)
+ rowcalctabs(row);
+ i++;
+ }
+}
+
+/* create new tab */
+static struct Tab *
+tabnew(Window win, Window leader, int ignoreunmap)
+{
+ struct Tab *tab;
+
+ tab = emalloc(sizeof(*tab));
+ *tab = (struct Tab){
+ .ignoreunmap = ignoreunmap,
+ .pix = None,
+ .pixtitle = None,
+ .title = None,
+ .leader = leader,
+ .obj.win = win,
+ .obj.type = TYPE_NORMAL,
+ };
+ TAILQ_INIT(&tab->dialq);
+ TAILQ_INIT(&tab->menuq);
+ ((struct Object *)tab)->type = TYPE_NORMAL;
+ tab->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, CopyFromParent, visual, clientmask, &clientswa),
+ XReparentWindow(dpy, tab->obj.win, tab->frame, 0, 0);
+ XMapWindow(dpy, tab->obj.win);
+ icccmwmstate(win, NormalState);
+ clientsincr();
+ return tab;
+}
+
+/* delete tab */
+static void
+tabdel(struct Tab *tab)
+{
+ struct Dialog *dial;
+ struct Menu *menu;
+
+ while ((dial = (struct Dialog *)TAILQ_FIRST(&tab->dialq)) != NULL) {
+ XDestroyWindow(dpy, dial->obj.win);
+ unmanagedialog((struct Object *)dial, 0);
+ }
+ while ((menu = (struct Menu *)TAILQ_FIRST(&tab->menuq)) != NULL) {
+ XDestroyWindow(dpy, menu->obj.win);
+ unmanagemenu((struct Object *)menu, 0);
+ }
+ tabdetach(tab, 0, 0);
+ if (tab->pixtitle != None)
+ XFreePixmap(dpy, tab->pixtitle);
+ if (tab->pix != None)
+ XFreePixmap(dpy, tab->pix);
+ icccmdeletestate(tab->obj.win);
+ XReparentWindow(dpy, tab->obj.win, root, 0, 0);
+ XDestroyWindow(dpy, tab->title);
+ XDestroyWindow(dpy, tab->frame);
+ clientsdecr();
+ free(tab->name);
+ free(tab);
+}
+
+/* create new row */
+struct Row *
+rownew(void)
+{
+ struct Row *row;
+
+ row = emalloc(sizeof(*row));
+ *row = (struct Row){
+ .pixbar = None,
+ .isunmapped = 0,
+ };
+ TAILQ_INIT(&row->tabq);
+ row->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ row->bar = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ row->bl = XCreateWindow(dpy, row->bar, 0, 0, config.titlewidth, config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ row->pixbl = XCreatePixmap(dpy, row->bl, config.titlewidth, config.titlewidth, depth);
+ row->br = XCreateWindow(dpy, row->bar, 0, 0, config.titlewidth, config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ row->div = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent, CWCursor,
+ &(XSetWindowAttributes){.cursor = wm.cursors[CURSOR_V]});
+ row->pixbr = XCreatePixmap(dpy, row->bl, config.titlewidth, config.titlewidth, depth);
+ XMapWindow(dpy, row->bl);
+ XMapWindow(dpy, row->br);
+ XDefineCursor(dpy, row->bl, wm.cursors[CURSOR_HAND]);
+ XDefineCursor(dpy, row->br, wm.cursors[CURSOR_PIRATE]);
+ return row;
+}
+
+/* detach row from column */
+static void
+rowdetach(struct Row *row, int recalc)
+{
+ struct Column *col;
+
+ col = row->col;
+ if (col->selrow == row) {
+ col->selrow = TAILQ_PREV(row, RowQueue, entry);
+ if (col->selrow == NULL) {
+ col->selrow = TAILQ_NEXT(row, entry);
+ }
+ }
+ col->nrows--;
+ TAILQ_REMOVE(&col->rowq, row, entry);
+ if (recalc) {
+ colcalcrows(row->col, 1, 0);
+ }
+}
+
+/* delete row */
+static void
+rowdel(struct Row *row)
+{
+ struct Tab *tab;
+
+ while ((tab = (struct Tab *)TAILQ_FIRST(&row->tabq)) != NULL)
+ tabdel(tab);
+ rowdetach(row, 1);
+ XDestroyWindow(dpy, row->frame);
+ XDestroyWindow(dpy, row->bar);
+ XDestroyWindow(dpy, row->bl);
+ XDestroyWindow(dpy, row->br);
+ XDestroyWindow(dpy, row->div);
+ if (row->pixbar != None)
+ XFreePixmap(dpy, row->pixbar);
+ XFreePixmap(dpy, row->pixbl);
+ XFreePixmap(dpy, row->pixbr);
+ free(row);
+}
+
+/* create new column */
+static struct Column *
+colnew(void)
+{
+ struct Column *col;
+
+ col = emalloc(sizeof(*col));
+ *col = (struct Column){ };
+ TAILQ_INIT(&col->rowq);
+ col->div = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent, CWCursor,
+ &(XSetWindowAttributes){.cursor = wm.cursors[CURSOR_H]});
+ return col;
+}
+
+/* detach column from container */
+static void
+coldetach(struct Column *col)
+{
+ struct Container *c;
+
+ c = col->c;
+ if (c->selcol == col) {
+ c->selcol = TAILQ_PREV(col, ColumnQueue, entry);
+ if (c->selcol == NULL) {
+ c->selcol = TAILQ_NEXT(col, entry);
+ }
+ }
+ c->ncols--;
+ TAILQ_REMOVE(&c->colq, col, entry);
+ containercalccols(col->c, 1, 0);
+}
+
+/* delete column */
+static void
+coldel(struct Column *col)
+{
+ struct Row *row;
+
+ while ((row = TAILQ_FIRST(&col->rowq)) != NULL)
+ rowdel(row);
+ coldetach(col);
+ XDestroyWindow(dpy, col->div);
+ free(col);
+}
+
+/* add row to column */
+static void
+coladdrow(struct Column *col, struct Row *row, struct Row *prev)
+{
+ struct Container *c;
+ struct Column *oldcol;
+
+ c = col->c;
+ oldcol = row->col;
+ row->col = col;
+ col->selrow = row;
+ col->nrows++;
+ if (prev == NULL || TAILQ_EMPTY(&col->rowq))
+ TAILQ_INSERT_HEAD(&col->rowq, row, entry);
+ else
+ TAILQ_INSERT_AFTER(&col->rowq, prev, row, entry);
+ colcalcrows(col, 1, 0); /* set row->y, row->h, etc */
+ XReparentWindow(dpy, row->div, c->frame, col->x + col->w, c->b);
+ XReparentWindow(dpy, row->bar, c->frame, col->x, row->y);
+ XReparentWindow(dpy, row->frame, c->frame, col->x, row->y);
+ XMapWindow(dpy, row->bar);
+ XMapWindow(dpy, row->frame);
+ if (oldcol != NULL && oldcol->nrows == 0) {
+ coldel(oldcol);
+ }
+}
+
+/* add tab to row */
+static void
+rowaddtab(struct Row *row, struct Tab *tab, struct Tab *prev)
+{
+ struct Row *oldrow;
+
+ oldrow = tab->row;
+ tab->row = row;
+ row->seltab = tab;
+ row->ntabs++;
+ if (prev == NULL || TAILQ_EMPTY(&row->tabq))
+ TAILQ_INSERT_HEAD(&row->tabq, (struct Object *)tab, entry);
+ else
+ TAILQ_INSERT_AFTER(&row->tabq, (struct Object *)prev, (struct Object *)tab, entry);
+ rowcalctabs(row); /* set tab->x, tab->w, etc */
+ if (tab->title == None) {
+ tab->title = XCreateWindow(dpy, row->bar, tab->x, 0, tab->w, config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ } else {
+ XReparentWindow(dpy, tab->title, row->bar, tab->x, 0);
+ }
+ XReparentWindow(dpy, tab->frame, row->frame, 0, 0);
+ XMapWindow(dpy, tab->frame);
+ XMapWindow(dpy, tab->title);
+ if (oldrow != NULL) { /* deal with the row this tab came from */
+ if (oldrow->ntabs == 0) {
+ rowdel(oldrow);
+ } else {
+ rowcalctabs(oldrow);
+ }
+ }
+}
+
+/* decorate dialog window */
+static void
+dialogdecorate(struct Dialog *d)
+{
+ int fullw, fullh; /* size of dialog window + borders */
+
+ fullw = d->w + 2 * config.borderwidth;
+ fullh = d->h + 2 * config.borderwidth;
+
+ /* (re)create pixmap */
+ if (d->pw != fullw || d->ph != fullh || d->pix == None)
+ pixmapnew(&d->pix, d->frame, fullw, fullh);
+ d->pw = fullw;
+ d->ph = fullh;
+
+ drawborders(d->pix, fullw, fullh, tabgetstyle(d->tab));
+ drawcommit(d->pix, d->frame, fullw, fullh);
+}
+
+/* unmap menus */
+static void
+menuunmap(struct Tab *tab)
+{
+ struct Object *menu;
+
+ if (tab == NULL)
+ return;
+ TAILQ_FOREACH(menu, &tab->menuq, entry) {
+ XUnmapWindow(dpy, ((struct Menu *)menu)->frame);
+ icccmwmstate(menu->win, IconicState);
+ }
+}
+
+/* raise menus */
+static void
+menuraise(struct Tab *tab)
+{
+ struct Container *c;
+ struct Object *p;
+ struct Menu *menu;
+ Window wins[2], layer;
+
+ c = tab->row->col->c;
+ if (c == NULL || c->isminimized)
+ return;
+ if (c->isfullscreen)
+ layer = wm.layerwins[LAYER_FULLSCREEN];
+ else if (c->layer > 0)
+ layer = wm.layerwins[LAYER_ABOVE];
+ else if (c->layer < 0)
+ layer = wm.layerwins[LAYER_BELOW];
+ else
+ layer = wm.layerwins[LAYER_NORMAL];
+ wins[0] = layer;
+ TAILQ_FOREACH(p, &tab->menuq, entry) {
+ menu = (struct Menu *)p;
+ wins[1] = menu->frame;
+ XRestackWindows(dpy, wins, 2);
+ wins[0] = menu->frame;
+ }
+}
+
+/* get focused fullscreen window in given monitor and desktop */
+static struct Container *
+getfullscreen(struct Monitor *mon, int desk)
+{
+ struct Container *c;
+
+ TAILQ_FOREACH(c, &wm.fullq, raiseentry)
+ if (!c->isminimized && c->mon == mon && (c->issticky || c->desk == desk))
+ return c;
+ return NULL;
+}
+
+/* add container into head of focus queue */
+static void
+containerinsertfocus(struct Container *c)
+{
+ TAILQ_INSERT_HEAD(&wm.focusq, c, entry);
+}
+
+/* add container into head of focus queue */
+static void
+containerinsertraise(struct Container *c)
+{
+ if (c->isfullscreen) {
+ TAILQ_INSERT_HEAD(&wm.fullq, c, raiseentry);
+ } else if (c->layer > 0) {
+ TAILQ_INSERT_HEAD(&wm.aboveq, c, raiseentry);
+ } else if (c->layer < 0) {
+ TAILQ_INSERT_HEAD(&wm.belowq, c, raiseentry);
+ } else {
+ TAILQ_INSERT_HEAD(&wm.centerq, c, raiseentry);
+ }
+}
+
+/* remove container from the focus list */
+static void
+containerdelfocus(struct Container *c)
+{
+ TAILQ_REMOVE(&wm.focusq, c, entry);
+}
+
+/* put container on beginning of focus list */
+static void
+containeraddfocus(struct Container *c)
+{
+ if (c == NULL || c->isminimized)
+ return;
+ containerdelfocus(c);
+ containerinsertfocus(c);
+}
+
+/* remove container from the raise list */
+static void
+containerdelraise(struct Container *c)
+{
+ if (c->isfullscreen) {
+ if (!TAILQ_EMPTY(&wm.fullq)) {
+ TAILQ_REMOVE(&wm.fullq, c, raiseentry);
+ }
+ } else if (c->layer > 0) {
+ if (!TAILQ_EMPTY(&wm.aboveq)) {
+ TAILQ_REMOVE(&wm.aboveq, c, raiseentry);
+ }
+ } else if (c->layer < 0) {
+ if (!TAILQ_EMPTY(&wm.belowq)) {
+ TAILQ_REMOVE(&wm.belowq, c, raiseentry);
+ }
+ } else {
+ if (!TAILQ_EMPTY(&wm.centerq)) {
+ TAILQ_REMOVE(&wm.centerq, c, raiseentry);
+ }
+ }
+}
+
+/* hide container */
+static void
+containerhide(struct Container *c, int hide)
+{
+ struct Object *t, *d;
+
+ if (c == NULL)
+ return;
+ c->ishidden = hide;
+ if (hide) {
+ XUnmapWindow(dpy, c->frame);
+ menuunmap(c->selcol->selrow->seltab);
+ } else {
+ XMapWindow(dpy, c->frame);
+ }
+ TAB_FOREACH_BEGIN(c, t) {
+ icccmwmstate(t->win, (hide ? IconicState : NormalState));
+ TAILQ_FOREACH(d, &((struct Tab *)t)->dialq, entry) {
+ icccmwmstate(d->win, (hide ? IconicState : NormalState));
+ }
+ }TAB_FOREACH_END
+}
+
+/* add column to container */
+static void
+containeraddcol(struct Container *c, struct Column *col, struct Column *prev)
+{
+ struct Container *oldc;
+
+ oldc = col->c;
+ col->c = c;
+ c->selcol = col;
+ c->ncols++;
+ if (prev == NULL || TAILQ_EMPTY(&c->colq))
+ TAILQ_INSERT_HEAD(&c->colq, col, entry);
+ else
+ TAILQ_INSERT_AFTER(&c->colq, prev, col, entry);
+ XReparentWindow(dpy, col->div, c->frame, 0, 0);
+ containercalccols(c, 1, 0);
+ if (oldc != NULL && oldc->ncols == 0) {
+ containerdel(oldc);
+ }
+}
+
+/* send container to desktop, raise it and optionally place it */
+static void
+containersendtodesk(struct Container *c, struct Monitor *mon, unsigned long desk, int place, int userplaced)
+{
+ void containerstick(struct Container *c, int stick);
+
+ if (c == NULL || c->isminimized)
+ return;
+ if (desk == 0xFFFFFFFF) {
+ containerstick(c, ADD);
+ } else if ((int)desk < config.ndesktops) {
+ c->desk = (int)desk;
+ c->mon = mon;
+ if (c->issticky) {
+ c->issticky = 0;
+ }
+ if (place)
+ containerplace(c, mon, desk, userplaced);
+ if ((int)desk != mon->seldesk) /* container was sent to invisible desktop */
+ containerhide(c, 1);
+ containerraise(c, c->isfullscreen, c->layer);
+ } else {
+ return;
+ }
+ ewmhsetwmdesktop(c);
+ ewmhsetstate(c);
+}
+
+/* make a container occupy the whole monitor */
+static void
+containerfullscreen(struct Container *c, int fullscreen)
+{
+ if (fullscreen != REMOVE && !c->isfullscreen)
+ containerraise(c, 1, c->layer);
+ else if (fullscreen != ADD && c->isfullscreen)
+ containerraise(c, 0, c->layer);
+ else
+ return;
+ containercalccols(c, 0, 1);
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, 0);
+ ewmhsetstate(c);
+}
+
+/* maximize a container on the monitor */
+static void
+containermaximize(struct Container *c, int maximize)
+{
+ if (maximize != REMOVE && !c->ismaximized)
+ c->ismaximized = 1;
+ else if (maximize != ADD && c->ismaximized)
+ c->ismaximized = 0;
+ else
+ return;
+ containercalccols(c, 0, 1);
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, 0);
+}
+
+/* minimize container; optionally focus another container */
+static void
+containerminimize(struct Container *c, int minimize, int focus)
+{
+ struct Container *tofocus;
+
+ if (minimize != REMOVE && !c->isminimized) {
+ c->isminimized = 1;
+ containerhide(c, 1);
+ if (focus) {
+ if ((tofocus = getnextfocused(c->mon, 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, wm.selmon->seldesk, 1, 0);
+ containermoveresize(c);
+ containerhide(c, 0);
+ tabfocus(c->selcol->selrow->seltab, 0);
+ } else {
+ return;
+ }
+}
+
+/* shade container title bar */
+static void
+containershade(struct Container *c, int shade)
+{
+ if (shade != REMOVE && !c->isshaded) {
+ c->isshaded = 1;
+ XDefineCursor(dpy, c->curswin[BORDER_NW], wm.cursors[CURSOR_W]);
+ XDefineCursor(dpy, c->curswin[BORDER_SW], wm.cursors[CURSOR_W]);
+ XDefineCursor(dpy, c->curswin[BORDER_NE], wm.cursors[CURSOR_E]);
+ XDefineCursor(dpy, c->curswin[BORDER_SE], wm.cursors[CURSOR_E]);
+ } else if (shade != ADD && c->isshaded) {
+ c->isshaded = 0;
+ XDefineCursor(dpy, c->curswin[BORDER_NW], wm.cursors[CURSOR_NW]);
+ XDefineCursor(dpy, c->curswin[BORDER_SW], wm.cursors[CURSOR_SW]);
+ XDefineCursor(dpy, c->curswin[BORDER_NE], wm.cursors[CURSOR_NE]);
+ XDefineCursor(dpy, c->curswin[BORDER_SE], wm.cursors[CURSOR_SE]);
+ } else {
+ return;
+ }
+ containercalccols(c, 0, 1);
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, 0);
+ if (c == wm.focused) {
+ tabfocus(c->selcol->selrow->seltab, 0);
+ }
+}
+
+/* stick a container on the monitor */
+void
+containerstick(struct Container *c, int stick)
+{
+ if (stick != REMOVE && !c->issticky) {
+ c->issticky = 1;
+ ewmhsetwmdesktop(c);
+ } else if (stick != ADD && c->issticky) {
+ c->issticky = 0;
+ containersendtodesk(c, c->mon, c->mon->seldesk, 0, 0);
+ } else {
+ return;
+ }
+}
+
+/* raise container above others */
+static void
+containerabove(struct Container *c, int above)
+{
+ if (above != REMOVE && c->layer != 1)
+ containerraise(c, c->isfullscreen, 1);
+ else if (above != ADD && c->layer != 0)
+ containerraise(c, c->isfullscreen, 0);
+ else
+ return;
+}
+
+/* lower container below others */
+static void
+containerbelow(struct Container *c, int below)
+{
+ if (below != REMOVE && c->layer != -1)
+ containerraise(c, c->isfullscreen, -1);
+ else if (below != ADD && c->layer != 0)
+ containerraise(c, c->isfullscreen, 0);
+ else
+ return;
+}
+
+/* create new container */
+struct Container *
+containernew(int x, int y, int w, int h, int state)
+{
+ struct Container *c;
+ int i;
+
+ x -= config.borderwidth,
+ y -= config.borderwidth,
+ w += 2 * config.borderwidth,
+ h += 2 * config.borderwidth + config.titlewidth,
+ c = emalloc(sizeof *c);
+ *c = (struct Container) {
+ .x = x, .y = y, .w = w, .h = h,
+ .nx = x, .ny = y, .nw = w, .nh = h,
+ .b = config.borderwidth,
+ .pix = None,
+ .isfullscreen = (state & FULLSCREEN),
+ .ismaximized = (state & MAXIMIZED),
+ .isminimized = (state & MINIMIZED),
+ .issticky = (state & STICKY),
+ .isshaded = (state & SHADED),
+ .layer = (state & ABOVE) ? +1 : (state & BELOW) ? -1 : 0,
+ };
+ TAILQ_INIT(&c->colq);
+ c->frame = XCreateWindow(dpy, root, c->x, c->y, c->w, c->h, 0, depth, CopyFromParent, visual, clientmask, &clientswa);
+ c->curswin[BORDER_N] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = wm.cursors[CURSOR_N],
+ }
+ );
+ c->curswin[BORDER_S] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = wm.cursors[CURSOR_S],
+ }
+ );
+ c->curswin[BORDER_W] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = wm.cursors[CURSOR_W],
+ }
+ );
+ c->curswin[BORDER_E] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = wm.cursors[CURSOR_E],
+ }
+ );
+ c->curswin[BORDER_NW] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = c->isshaded ? wm.cursors[CURSOR_W] : wm.cursors[CURSOR_NW],
+ }
+ );
+ c->curswin[BORDER_SW] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = c->isshaded ? wm.cursors[CURSOR_W] : wm.cursors[CURSOR_SW],
+ }
+ );
+ c->curswin[BORDER_NE] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = c->isshaded ? wm.cursors[CURSOR_E] : wm.cursors[CURSOR_NE],
+ }
+ );
+ c->curswin[BORDER_SE] = XCreateWindow(
+ dpy, c->frame, 0, 0, 1, 1, 0,
+ CopyFromParent, InputOnly, CopyFromParent,
+ CWCursor,
+ &(XSetWindowAttributes){
+ .cursor = c->isshaded ? wm.cursors[CURSOR_E] : wm.cursors[CURSOR_SE],
+ }
+ );
+ for (i = 0; i < BORDER_LAST; i++)
+ XMapWindow(dpy, c->curswin[i]);
+ containerinsertfocus(c);
+ containerinsertraise(c);
+ return c;
+}
+
+/* delete container */
+void
+containerdel(struct Container *c)
+{
+ struct Column *col;
+ int i;
+
+ containerdelfocus(c);
+ containerdelraise(c);
+ if (wm.focused == c)
+ wm.focused = NULL;
+ TAILQ_REMOVE(&wm.focusq, c, entry);
+ while ((col = TAILQ_FIRST(&c->colq)) != NULL)
+ coldel(col);
+ if (c->pix != None)
+ XFreePixmap(dpy, c->pix);
+ XDestroyWindow(dpy, c->frame);
+ for (i = 0; i < BORDER_LAST; i++)
+ XDestroyWindow(dpy, c->curswin[i]);
+ free(c);
+}
+
+/* commit container size and position */
+void
+containermoveresize(struct Container *c)
+{
+ struct Object *t, *d;
+ struct Column *col;
+ struct Row *row;
+ struct Tab *tab;
+ struct Dialog *dial;
+ int rowy;
+ int isshaded;
+
+ if (c == NULL)
+ return;
+ XMoveResizeWindow(dpy, c->frame, c->x, c->y, c->w, c->h);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_N], config.corner, 0, c->w - 2 * config.corner, c->b);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_S], config.corner, c->h - c->b, c->w - 2 * config.corner, c->b);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_W], 0, config.corner, c->b, c->h - 2 * config.corner);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_E], c->w - c->b, config.corner, c->b, c->h - 2 * config.corner);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_NW], 0, 0, config.corner, config.corner);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_NE], c->w - config.corner, 0, config.corner, config.corner);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_SW], 0, c->h - config.corner, config.corner, config.corner);
+ XMoveResizeWindow(dpy, c->curswin[BORDER_SE], c->w - config.corner, c->h - config.corner, config.corner, config.corner);
+ isshaded = containerisshaded(c);
+ TAILQ_FOREACH(col, &c->colq, entry) {
+ rowy = c->b;
+ if (TAILQ_NEXT(col, entry) != NULL) {
+ XMoveResizeWindow(dpy, col->div, col->x + col->w, c->b, config.divwidth, c->h - 2 * c->b);
+ XMapWindow(dpy, col->div);
+ } else {
+ XUnmapWindow(dpy, col->div);
+ }
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ if (!isshaded) {
+ if (TAILQ_NEXT(row, entry) != NULL) {
+ XMoveResizeWindow(dpy, row->div, col->x, row->y + row->h, col->w, config.divwidth);
+ XMapWindow(dpy, row->div);
+ }
+ titlebarmoveresize(row, col->x, row->y, col->w);
+ if (row->h - config.titlewidth > 0) {
+ XMoveResizeWindow(dpy, row->frame, col->x, row->y + config.titlewidth, col->w, row->h - config.titlewidth);
+ XMapWindow(dpy, row->frame);
+ row->isunmapped = 0;
+ } else {
+ XUnmapWindow(dpy, row->frame);
+ row->isunmapped = 1;
+ }
+ } else {
+ titlebarmoveresize(row, col->x, rowy, col->w);
+ XUnmapWindow(dpy, row->frame);
+ XUnmapWindow(dpy, row->div);
+ row->isunmapped = 1;
+ }
+ rowy += config.titlewidth;
+ TAILQ_FOREACH(t, &row->tabq, entry) {
+ tab = (struct Tab *)t;
+ XMoveResizeWindow(dpy, tab->frame, 0, 0, tab->winw, tab->winh);
+ TAILQ_FOREACH(d, &tab->dialq, entry) {
+ dial = (struct Dialog *)d;
+ dialogmoveresize(dial);
+ ewmhsetframeextents(dial->obj.win, c->b, 0);
+ }
+ XResizeWindow(dpy, tab->obj.win, tab->winw, tab->winh);
+ ewmhsetframeextents(tab->obj.win, c->b, TITLEWIDTH(c));
+ tabmoveresize(tab);
+ }
+ }
+ }
+}
+
+/* draw decoration on container frame */
+void
+containerdecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, int recursive, enum Octant o)
+{
+ struct Column *col;
+ struct Row *row;
+ struct Object *t, *d;
+ int style;
+ int isshaded;
+
+ if (c == NULL)
+ return;
+ isshaded = containerisshaded(c);
+ style = containergetstyle(c);
+
+ /* (re)create pixmap */
+ if (c->pw != c->w || c->ph != c->h || c->pix == None)
+ pixmapnew(&c->pix, c->frame, c->w, c->h);
+ c->pw = c->w;
+ c->ph = c->h;
+
+ /* draw background */
+ drawbackground(c->pix, 0, 0, c->w, c->h, style);
+
+ if (c->b > 0)
+ drawframe(c->pix, isshaded, c->w, c->h, o, style);
+
+ TAILQ_FOREACH(col, &c->colq, entry) {
+ /* draw column division */
+ if (TAILQ_NEXT(col, entry) != NULL)
+ drawshadow(c->pix, col->x + col->w, c->b, config.divwidth, c->h - 2 * c->b, style, col == cdiv);
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ /* draw row division */
+ if (TAILQ_NEXT(row, entry) != NULL)
+ drawshadow(c->pix, col->x, row->y + row->h, col->w, config.divwidth, style, row == rdiv);
+
+ /* (re)create titlebar pixmap */
+ if (row->pw != col->w || row->pixbar == None)
+ pixmapnew(&row->pixbar, row->bar, col->w, config.titlewidth);
+ row->pw = col->w;
+
+ /* draw background of titlebar pixmap */
+ drawbackground(row->pixbar, 0, 0, col->w, config.titlewidth, style);
+ drawcommit(row->pixbar, row->bar, col->w, config.titlewidth);
+
+ /* draw buttons */
+ buttonleftdecorate(row->bl, row->pixbl, style, 0);
+ buttonrightdecorate(row->br, row->pixbr, style, 0);
+
+ /* decorate tabs, if necessary */
+ if (recursive) {
+ TAILQ_FOREACH(t, &row->tabq, entry) {
+ tabdecorate((struct Tab *)t, 0);
+ TAILQ_FOREACH(d, &((struct Tab *)t)->dialq, entry) {
+ dialogdecorate((struct Dialog *)d);
+ }
+ }
+ }
+ }
+ }
+
+ drawcommit(c->pix, c->frame, c->w, c->h);
+}
+
+/* check if container needs to be redecorated and redecorate it */
+void
+containerredecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, enum Octant o)
+{
+ if (c->pw != c->w || c->ph != c->h) {
+ containerdecorate(c, cdiv, rdiv, 0, o);
+ }
+}
+
+/* calculate position and width of columns of a container */
+void
+containercalccols(struct Container *c, int recalcfact, int recursive)
+{
+ struct Column *col;
+ int i, x, w;
+ int sumw;
+ int content;
+ int recalc;
+
+ if (c->isfullscreen) {
+ c->x = c->mon->mx;
+ c->y = c->mon->my;
+ c->w = c->mon->mw;
+ c->h = c->mon->mh;
+ c->b = 0;
+ } else if (c->ismaximized) {
+ c->x = c->mon->wx;
+ c->y = c->mon->wy;
+ c->w = c->mon->ww;
+ c->h = c->mon->wh;
+ c->b = config.borderwidth;
+ } else {
+ c->x = c->nx;
+ c->y = c->ny;
+ c->w = c->nw;
+ c->h = c->nh;
+ c->b = config.borderwidth;
+ }
+ if (containerisshaded(c)) {
+ c->h = 0;
+ }
+
+ /* check if columns sum up the width of the container */
+ content = c->w - (c->ncols - 1) * config.divwidth - 2 * c->b;
+ sumw = 0;
+ recalc = 0;
+ TAILQ_FOREACH(col, &c->colq, entry) {
+ if (!recalcfact) {
+ if (TAILQ_NEXT(col, entry) == NULL) {
+ col->w = content - sumw;
+ } else {
+ col->w = col->fact * content;
+ }
+ if (col->w == 0) {
+ recalc = 1;
+ }
+ }
+ sumw += col->w;
+ }
+ if (sumw != content)
+ recalc = 1;
+
+ w = c->w - 2 * c->b - (c->ncols - 1) * config.divwidth;
+ x = c->b;
+ i = 0;
+ TAILQ_FOREACH(col, &c->colq, entry) {
+ if (containerisshaded(c))
+ c->h = max(c->h, col->nrows * config.titlewidth);
+ if (recalc)
+ col->w = max(1, ((i + 1) * w / c->ncols) - (i * w / c->ncols));
+ col->fact = (double)col->w/(double)c->w;
+ col->x = x;
+ x += col->w + config.divwidth;
+ if (recursive)
+ colcalcrows(col, recalcfact, 1);
+ i++;
+ }
+ if (containerisshaded(c)) {
+ c->h += 2 * c->b;
+ }
+}
+
+/* send container to desktop and focus another on the original desktop */
+void
+containersendtodeskandfocus(struct Container *c, struct Monitor *mon, unsigned long desk)
+{
+ int prevdesk;
+
+ if (c == NULL)
+ return;
+ prevdesk = c->desk;
+ containersendtodesk(c, mon, desk, 0, 0);
+ c = getnextfocused(mon, prevdesk);
+ if (c != NULL) {
+ tabfocus(c->selcol->selrow->seltab, 0);
+ } else {
+ tabfocus(NULL, 0);
+ }
+}
+
+/* move container x pixels to the right and y pixels down */
+void
+containerincrmove(struct Container *c, int x, int y)
+{
+ struct Monitor *monto;
+ struct Object *t;
+ struct Tab *tab;
+
+ if (c == NULL || c->isminimized || c->ismaximized || c->isfullscreen)
+ return;
+ c->nx += x;
+ c->ny += y;
+ c->x = c->nx;
+ c->y = c->ny;
+ snaptoedge(&c->x, &c->y, c->w, c->h);
+ XMoveWindow(dpy, c->frame, c->x, c->y);
+ TAB_FOREACH_BEGIN(c, t){
+ tab = (struct Tab *)t;
+ winnotify(tab->obj.win, c->x + col->x, c->y + row->y + config.titlewidth, tab->winw, tab->winh);
+ }TAB_FOREACH_END
+ if (!c->issticky) {
+ monto = getmon(c->nx + c->nw / 2, c->ny + c->nh / 2);
+ if (monto != NULL && monto != c->mon) {
+ containersendtodesk(c, monto, monto->seldesk, 0, 0);
+ if (wm.focused == c) {
+ deskfocus(monto, monto->seldesk, 0);
+ }
+ }
+ }
+}
+
+/* raise container */
+void
+containerraise(struct Container *c, int isfullscreen, int layer)
+{
+ Window wins[2];
+
+ if (c == NULL || c->isminimized)
+ return;
+ containerdelraise(c);
+ wins[1] = c->frame;
+ if (isfullscreen) {
+ TAILQ_INSERT_HEAD(&wm.fullq, c, raiseentry);
+ wins[0] = wm.layerwins[LAYER_FULLSCREEN];
+ } else if (layer > 0) {
+ TAILQ_INSERT_HEAD(&wm.aboveq, c, raiseentry);
+ wins[0] = wm.layerwins[LAYER_ABOVE];
+ } else if (layer < 0) {
+ TAILQ_INSERT_HEAD(&wm.belowq, c, raiseentry);
+ wins[0] = wm.layerwins[LAYER_BELOW];
+ } else {
+ TAILQ_INSERT_HEAD(&wm.centerq, c, raiseentry);
+ wins[0] = wm.layerwins[LAYER_NORMAL];
+ }
+ c->isfullscreen = isfullscreen;
+ c->layer = layer;
+ XRestackWindows(dpy, wins, 2);
+ menuraise(c->selcol->selrow->seltab);
+ ewmhsetclientsstacking();
+}
+
+/* configure container size and position */
+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) && wc->width >= wm.minsize)
+ c->nw = wc->width;
+ if ((valuemask & CWHeight) && wc->height >= wm.minsize)
+ c->nh = wc->height;
+ containercalccols(c, 0, 1);
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, 0);
+}
+
+/* set container state from client message */
+void
+containersetstate(struct Tab *tab, Atom *props, unsigned long set)
+{
+ struct Container *c;
+
+ if (tab == NULL)
+ return;
+ c = tab->row->col->c;
+ if (props[0] == atoms[_NET_WM_STATE_MAXIMIZED_VERT] ||
+ props[0] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ] ||
+ props[1] == atoms[_NET_WM_STATE_MAXIMIZED_VERT] ||
+ props[1] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ])
+ containermaximize(c, set);
+ if (props[0] == atoms[_NET_WM_STATE_FULLSCREEN] ||
+ props[1] == atoms[_NET_WM_STATE_FULLSCREEN])
+ containerfullscreen(c, set);
+ if (props[0] == atoms[_NET_WM_STATE_SHADED] ||
+ props[1] == atoms[_NET_WM_STATE_SHADED])
+ containershade(c, set);
+ if (props[0] == atoms[_NET_WM_STATE_STICKY] ||
+ props[1] == atoms[_NET_WM_STATE_STICKY])
+ containerstick(c, set);
+ if (props[0] == atoms[_NET_WM_STATE_HIDDEN] ||
+ props[1] == atoms[_NET_WM_STATE_HIDDEN])
+ containerminimize(c, set, (c == wm.focused));
+ if (props[0] == atoms[_NET_WM_STATE_ABOVE] ||
+ props[1] == atoms[_NET_WM_STATE_ABOVE])
+ containerabove(c, set);
+ if (props[0] == atoms[_NET_WM_STATE_BELOW] ||
+ props[1] == atoms[_NET_WM_STATE_BELOW])
+ containerbelow(c, set);
+ if (props[0] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION] ||
+ props[1] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION])
+ tabupdateurgency(tab, set == ADD || (set == TOGGLE && !tab->isurgent));
+ ewmhsetstate(c);
+}
+
+/* find best position to place a container on screen */
+void
+containerplace(struct Container *c, struct Monitor *mon, int desk, int userplaced)
+{
+ 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 */
+
+ if (desk < 0 || desk >= config.ndesktops || c == NULL || c->isminimized)
+ return;
+
+ fitmonitor(mon, &c->nx, &c->ny, &c->nw, &c->nh, 1.0);
+
+ /* if the user placed the window, we should not re-place it */
+ if (userplaced)
+ return;
+
+ /*
+ * The container area is the region of the screen where containers live,
+ * that is, the area of the monitor not occupied by bars or the dock; it
+ * corresponds to the region occupied by a maximized container.
+ *
+ * Shod tries to find an empty region on the container area or a region
+ * with few containers in it to place a new container. To do that, shod
+ * cuts the container area in DIV divisions horizontally and vertically,
+ * creating DIV*DIV regions; shod then counts how many containers are on
+ * each region; and places the new container on those regions with few
+ * containers over them.
+ *
+ * After some trial and error, I found out that a DIV equals to 15 is
+ * optimal. It is not too low to provide a incorrect placement, nor too
+ * high to take so much computer time.
+ */
+
+ /* increment cells of grid a window is in */
+ TAILQ_FOREACH(tmp, &wm.focusq, entry) {
+ if (tmp != c && !tmp->isminimized && ((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;
+ xa = tmp->nx;
+ xb = tmp->nx + tmp->nw;
+ 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, max(mon->wx, mon->wx + subx + subw / 2 - c->nw / 2));
+ c->ny = min(mon->wy + mon->wh - c->nh, max(mon->wy, mon->wy + suby + subh / 2 - c->nh / 2));
+ containercalccols(c, 0, 1);
+}
+
+/* check if container can be shaded */
+int
+containerisshaded(struct Container *c)
+{
+ return c->isshaded && !c->isfullscreen;
+}
+
+/* attach tab into row; return nonzero if an attachment was performed */
+int
+tabattach(struct Container *c, struct Tab *det, int x, int y)
+{
+ enum { CREATTAB = 0x0, CREATROW = 0x1, CREATCOL = 0x2 };
+ struct Column *col, *ncol;
+ struct Row *row, *nrow;
+ struct Tab *tab;
+ struct Object *obj;
+ int flag;
+
+ flag = CREATTAB;
+ col = NULL;
+ row = NULL;
+ tab = NULL;
+ if (x < config.borderwidth) {
+ flag = CREATCOL | CREATROW;
+ goto found;
+ }
+ if (x >= c->w - config.borderwidth) {
+ flag = CREATCOL | CREATROW;
+ col = TAILQ_LAST(&c->colq, ColumnQueue);
+ goto found;
+ }
+ TAILQ_FOREACH(col, &c->colq, entry) {
+ if (TAILQ_NEXT(col, entry) != NULL && x >= col->x + col->w && x < col->x + col->w + config.divwidth) {
+ flag = CREATCOL | CREATROW;
+ goto found;
+ }
+ if (x >= col->x && x < col->x + col->w) {
+ if (y < config.borderwidth) {
+ flag = CREATROW;
+ goto found;
+ }
+ if (y >= c->h - config.borderwidth) {
+ flag = CREATROW;
+ row = TAILQ_LAST(&col->rowq, RowQueue);
+ goto found;
+ }
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ if (y > row->y && y <= row->y + config.titlewidth) {
+ TAILQ_FOREACH_REVERSE(obj, &row->tabq, Queue, entry) {
+ tab = (struct Tab *)obj;
+ if (x > col->x + tab->x + tab->w / 2) {
+ flag = CREATTAB;
+ goto found;
+ }
+ }
+ tab = NULL;
+ goto found;
+ }
+ if (TAILQ_NEXT(row, entry) != NULL && y >= row->y + row->h && y < row->y + row->h + config.divwidth) {
+ flag = CREATROW;
+ goto found;
+ }
+ }
+ }
+ }
+ return 0;
+found:
+ ncol = NULL;
+ nrow = NULL;
+ if (flag & CREATCOL) {
+ ncol = colnew();
+ containeraddcol(c, ncol, col);
+ col = ncol;
+ }
+ if (flag & CREATROW) {
+ nrow = rownew();
+ coladdrow(col, nrow, row);
+ row = nrow;
+ }
+ rowaddtab(row, det, tab);
+ if (ncol != NULL)
+ containercalccols(c, 1, 1);
+ else if (nrow != NULL)
+ colcalcrows(col, 1, 1);
+ else
+ rowcalctabs(row);
+ tabfocus(det, 0);
+ XMapSubwindows(dpy, c->frame);
+ /* no need to call shodgrouptab() and shodgroupcontainer(); tabfocus() already calls them */
+ ewmhsetclientsstacking();
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, 0);
+ return 1;
+}
+
+/* delete row, then column if empty, then container if empty */
+void
+containerdelrow(struct Row *row)
+{
+ struct Container *c;
+ struct Column *col;
+ int recalc, redraw;
+
+ col = row->col;
+ c = col->c;
+ recalc = 1;
+ redraw = 0;
+ if (row->ntabs == 0) {
+ rowdel(row);
+ redraw = 1;
+ }
+ if (col->nrows == 0) {
+ coldel(col);
+ redraw = 1;
+ }
+ if (c->ncols == 0) {
+ containerdel(c);
+ recalc = 0;
+ }
+ if (recalc) {
+ containercalccols(c, 1, 1);
+ containermoveresize(c);
+ shodgrouptab(c);
+ shodgroupcontainer(c);
+ if (redraw) {
+ containerdecorate(c, NULL, NULL, 0, 0);
+ }
+ }
+}
+
+/* detach tab from row */
+void
+tabdetach(struct Tab *tab, int x, int y)
+{
+ struct Row *row;
+
+ row = tab->row;
+ if (row->seltab == tab) {
+ row->seltab = (struct Tab *)TAILQ_PREV((struct Object *)tab, Queue, entry);
+ if (row->seltab == NULL) {
+ row->seltab = (struct Tab *)TAILQ_NEXT((struct Object *)tab, entry);
+ }
+ }
+ row->ntabs--;
+ tab->ignoreunmap = IGNOREUNMAP;
+ XReparentWindow(dpy, tab->title, root, x, y);
+ TAILQ_REMOVE(&row->tabq, (struct Object *)tab, entry);
+ tab->row = NULL;
+ rowcalctabs(row);
+}
+
+/* focus tab */
+void
+tabfocus(struct Tab *tab, int gotodesk)
+{
+ struct Container *c;
+ struct Dialog *dial;
+
+ wm.prevfocused = wm.focused;
+ if (tab == NULL) {
+ wm.focused = NULL;
+ XSetInputFocus(dpy, wm.wmcheckwin, RevertToParent, CurrentTime);
+ ewmhsetactivewindow(None);
+ } else {
+ c = tab->row->col->c;
+ if (!c->isfullscreen && getfullscreen(c->mon, c->desk) != NULL)
+ return; /* we should not focus a client below a fullscreen client */
+ wm.focused = c;
+ tab->row->seltab = tab;
+ tab->row->col->selrow = tab->row;
+ tab->row->col->c->selcol = tab->row->col;
+ if (gotodesk)
+ deskfocus(c->mon, c->issticky ? c->mon->seldesk : c->desk, 0);
+ if (tab->row->fact == 0.0)
+ rowstack(tab->row->col, tab->row);
+ XRaiseWindow(dpy, tab->frame);
+ if (c->isshaded || tab->row->isunmapped) {
+ XSetInputFocus(dpy, tab->row->bar, RevertToParent, CurrentTime);
+ } else if (!TAILQ_EMPTY(&tab->dialq)) {
+ dial = (struct Dialog *)TAILQ_FIRST(&tab->dialq);
+ XRaiseWindow(dpy, dial->frame);
+ XSetInputFocus(dpy, dial->obj.win, RevertToParent, CurrentTime);
+ } else {
+ XSetInputFocus(dpy, tab->obj.win, RevertToParent, CurrentTime);
+ }
+ ewmhsetactivewindow(tab->obj.win);
+ if (tab->isurgent)
+ tabclearurgency(tab);
+ menumap(tab);
+ containeraddfocus(c);
+ containerdecorate(c, NULL, NULL, 1, 0);
+ containerminimize(c, 0, 0);
+ containerraise(c, c->isfullscreen, c->layer);
+ shodgrouptab(c);
+ shodgroupcontainer(c);
+ ewmhsetstate(c);
+ }
+ if (wm.prevfocused != NULL && wm.prevfocused != wm.focused) {
+ TAILQ_REMOVE(&wm.focusq, wm.prevfocused, entry);
+ TAILQ_INSERT_TAIL(&wm.focusq, wm.prevfocused, entry);
+ if (tab != wm.prevfocused->selcol->selrow->seltab)
+ menuunmap(wm.prevfocused->selcol->selrow->seltab);
+ containerdecorate(wm.prevfocused, NULL, NULL, 1, 0);
+ ewmhsetstate(wm.prevfocused);
+ }
+}
+
+/* decorate tab */
+void
+tabdecorate(struct Tab *t, int pressed)
+{
+ int style;
+ int drawlines = 0;
+
+ style = tabgetstyle(t);
+ if (t->row != NULL && t != t->row->col->c->selcol->selrow->seltab) {
+ pressed = 0;
+ drawlines = 0;
+ } else if (t->row != NULL && pressed) {
+ pressed = 1;
+ drawlines = 1;
+ } else {
+ pressed = 0;
+ drawlines = 1;
+ }
+
+ /* (re)create pixmap */
+ if (t->ptw != t->w || t->pixtitle == None)
+ pixmapnew(&t->pixtitle, t->title, t->w, config.titlewidth);
+ if (t->pw != t->winw || t->ph != t->winh || t->pix == None)
+ pixmapnew(&t->pix, t->frame, t->winw, t->winh);
+ t->ptw = t->w;
+ t->pw = t->winw;
+ t->ph = t->winh;
+
+ /* draw background */
+ drawbackground(t->pixtitle, 0, 0, t->w, config.titlewidth, style);
+
+ /* draw shadows */
+ drawshadow(t->pixtitle, 0, 0, t->w, config.titlewidth, style, pressed);
+
+ /* write tab title */
+ if (t->name != NULL)
+ drawtitle(t->pixtitle, t->name, t->w, drawlines, style, pressed);
+
+ /* draw frame background */
+ drawbackground(t->pix, 0, 0, t->winw, t->winh, style);
+
+ drawcommit(t->pixtitle, t->title, t->w, config.titlewidth);
+ drawcommit(t->pix, t->frame, t->winw, t->winh);
+}
+
+/* update tab urgency */
+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);
+ }
+}
+
+/* stack rows */
+void
+rowstack(struct Column *col, struct Row *row)
+{
+ struct Row *r;
+ double fact;
+ int refact;
+
+ fact = 1.0 / (double)col->nrows;
+ refact = (row->fact == 1.0);
+ TAILQ_FOREACH(r, &col->rowq, entry) {
+ if (refact) {
+ r->fact = fact;
+ } else if (r == row) {
+ r->fact = 1.0;
+ } else {
+ r->fact = 0.0;
+ }
+ }
+ colcalcrows(col, 0, 1);
+ containermoveresize(col->c);
+}
+
+/* configure dialog window */
+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);
+}
+
+/* configure menu window */
+void
+menuconfigure(struct Menu *menu, unsigned int valuemask, XWindowChanges *wc)
+{
+ if (menu == NULL)
+ return;
+ if (valuemask & CWX)
+ menu->x = wc->x;
+ if (valuemask & CWY)
+ menu->y = wc->y;
+ if (valuemask & CWWidth)
+ menu->w = wc->width;
+ if (valuemask & CWHeight)
+ menu->h = wc->height;
+ menumoveresize(menu);
+ menudecorate(menu, 0);
+}
+
+/* commit dialog size and position */
+void
+dialogmoveresize(struct Dialog *dial)
+{
+ struct Container *c;
+ int dx, dy, dw, dh;
+
+ dialogcalcsize(dial);
+ c = dial->tab->row->col->c;
+ dx = dial->x - config.borderwidth;
+ dy = dial->y - config.borderwidth;
+ dw = dial->w + 2 * config.borderwidth;
+ dh = dial->h + 2 * config.borderwidth;
+ XMoveResizeWindow(dpy, dial->frame, dx, dy, dw, dh);
+ XMoveResizeWindow(dpy, dial->obj.win, config.borderwidth, config.borderwidth, dial->w, dial->h);
+ winnotify(dial->obj.win, c->x + dial->tab->row->col->x + dial->x, c->y + dial->tab->row->y + dial->y, dial->w, dial->h);
+ if (dial->pw != dw || dial->ph != dh) {
+ dialogdecorate(dial);
+ }
+}
+
+void
+menuincrmove(struct Menu *menu, int x, int y)
+{
+ menu->x += x;
+ menu->y += y;
+ snaptoedge(&menu->x, &menu->y, menu->w, menu->h);
+ XMoveWindow(dpy, menu->frame, menu->x, menu->y);
+}
+
+/* commit menu geometry */
+void
+menumoveresize(struct Menu *menu)
+{
+ XMoveResizeWindow(dpy, menu->frame, menu->x, menu->y, menu->w, menu->h);
+ XMoveWindow(dpy, menu->button, menu->w - config.borderwidth - config.titlewidth, config.borderwidth);
+ XResizeWindow(dpy, menu->titlebar, max(1, menu->w - 2 * config.borderwidth - config.titlewidth), config.titlewidth);
+ XResizeWindow(dpy, menu->obj.win, menu->w - 2 * config.borderwidth, menu->h - 2 * config.borderwidth - config.titlewidth);
+}
+
+/* decorate menu */
+void
+menudecorate(struct Menu *menu, int titlepressed)
+{
+ int tw, th;
+
+ if (menu->pw != menu->w || menu->ph != menu->h || menu->pix == None)
+ pixmapnew(&menu->pix, menu->frame, menu->w, menu->h);
+ menu->pw = menu->w;
+ menu->ph = menu->h;
+ tw = max(1, menu->w - 2 * config.borderwidth - config.titlewidth);
+ th = config.titlewidth;
+ if (menu->tw != tw || menu->th != th || menu->pixtitlebar == None)
+ pixmapnew(&menu->pixtitlebar, menu->titlebar, tw, th);
+ menu->tw = tw;
+ menu->th = th;
+
+ drawbackground(menu->pix, 0, 0, menu->tw, menu->th, FOCUSED);
+ drawborders(menu->pix, menu->w, menu->h, FOCUSED);
+ drawshadow(menu->pixtitlebar, 0, 0, menu->tw, config.titlewidth, FOCUSED, titlepressed);
+ /* write menu title */
+ if (menu->name != NULL)
+ drawtitle(menu->pixtitlebar, menu->name, menu->tw, 0, FOCUSED, 0);
+ buttonrightdecorate(menu->button, menu->pixbutton, FOCUSED, 0);
+ drawcommit(menu->pix, menu->frame, menu->pw, menu->ph);
+ drawcommit(menu->pixtitlebar, menu->titlebar, menu->tw, menu->th);
+}
+
+/* put menu on beginning of menu list */
+void
+menuaddraise(struct Tab *tab, struct Menu *menu)
+{
+ menudelraise(tab, menu);
+ TAILQ_INSERT_HEAD(&tab->menuq, (struct Object *)menu, entry);
+ XSetInputFocus(dpy, menu->obj.win, RevertToParent, CurrentTime);
+}
+
+/* place menu next to its container */
+void
+menuplace(struct Menu *menu)
+{
+ struct Container *c;
+
+ c = menu->tab->row->col->c;
+ fitmonitor(c->mon, &menu->x, &menu->y, &menu->w, &menu->h, 1.0);
+ menumoveresize(menu);
+}
+
+/* change desktop */
+void
+deskfocus(struct Monitor *mon, int desk, int focus)
+{
+ struct Container *c;
+
+ if (desk < 0 || desk >= config.ndesktops || desk == wm.selmon->seldesk)
+ return;
+ if (!deskisvisible(mon, desk)) {
+ /* unhide cointainers of new current desktop
+ * hide containers of previous current desktop */
+ TAILQ_FOREACH(c, &wm.focusq, entry) {
+ if (!c->isminimized && c->desk == desk) {
+ containerhide(c, 0);
+ } else if (!c->issticky && c->desk == mon->seldesk) {
+ containerhide(c, 1);
+ }
+ }
+ }
+
+ /* update current desktop */
+ wm.selmon = mon;
+ wm.selmon->seldesk = desk;
+ if (wm.showingdesk)
+ deskshow(0);
+ ewmhsetcurrentdesktop(desk);
+
+ /* focus client on the new current desktop */
+ if (focus) {
+ c = getnextfocused(mon, desk);
+ if (c != NULL) {
+ tabfocus(c->selcol->selrow->seltab, 0);
+ } else {
+ tabfocus(NULL, 0);
+ }
+ }
+}
+
+/* (un)show desktop */
+void
+deskshow(int show)
+{
+ struct Container *c;
+
+ TAILQ_FOREACH(c, &wm.focusq, entry)
+ if (!c->isminimized)
+ containerhide(c, show);
+ wm.showingdesk = show;
+ ewmhsetshowingdesktop(show);
+}
+
+/* create container for tab */
+void
+managetab(struct Tab *tab, struct Monitor *mon, int desk, Window win, Window leader, XRectangle rect, int state, int ignoreunmap)
+{
+ struct Container *c;
+ struct Column *col;
+ struct Row *row;
+
+ if (tab == NULL) {
+ tab = tabnew(win, leader, ignoreunmap);
+ winupdatetitle(tab->obj.win, &tab->name);
+ }
+ c = containernew(rect.x, rect.y, rect.width, rect.height, state);
+ c->mon = mon;
+ c->desk = desk;
+ row = rownew();
+ col = colnew();
+ containeraddcol(c, col, NULL);
+ coladdrow(col, row, NULL);
+ rowaddtab(row, tab, NULL);
+ containerredecorate(c, NULL, NULL, 0);
+ XMapSubwindows(dpy, c->frame);
+ if (!c->isminimized) {
+ containerplace(c, mon, desk, (state & USERPLACED));
+ containermoveresize(c);
+ containerhide(c, 0);
+ tabfocus(tab, 0);
+ } else {
+ containermoveresize(c);
+ }
+ /* no need to call shodgrouptab() and shodgroupcontainer(); tabfocus() already calls them */
+ ewmhsetwmdesktop(c);
+ ewmhsetclients();
+ ewmhsetclientsstacking();
+}
+
+/* create container for tab */
+void
+managedialog(struct Tab *tab, struct Monitor *mon, int desk, Window win, Window leader, XRectangle rect, int state, int ignoreunmap)
+{
+ struct Dialog *dial;
+
+ (void)mon;
+ (void)desk;
+ (void)leader;
+ (void)state;
+ dial = dialognew(win, rect.width, rect.height, ignoreunmap);
+ dial->tab = tab;
+ TAILQ_INSERT_HEAD(&tab->dialq, (struct Object *)dial, entry);
+ XReparentWindow(dpy, dial->frame, tab->frame, 0, 0);
+ icccmwmstate(dial->obj.win, NormalState);
+ dialogmoveresize(dial);
+ XMapRaised(dpy, dial->frame);
+ if (wm.focused != NULL && wm.focused->selcol->selrow->seltab == tab)
+ tabfocus(tab, 0);
+ ewmhsetclients();
+ ewmhsetclientsstacking();
+}
+
+/* assign menu to tab */
+void
+managemenu(struct Tab *tab, struct Monitor *mon, int desk, Window win, Window leader, XRectangle rect, int state, int ignoreunmap)
+{
+ struct Menu *menu;
+
+ (void)mon;
+ (void)desk;
+ (void)leader;
+ (void)state;
+ menu = menunew(win, rect.x, rect.y, rect.width, rect.height, ignoreunmap);
+ menu->tab = tab;
+ winupdatetitle(menu->obj.win, &menu->name);
+ TAILQ_INSERT_HEAD(&tab->menuq, (struct Object *)menu, entry);
+ icccmwmstate(menu->obj.win, NormalState);
+ menuplace(menu);
+ menudecorate(menu, 0);
+ if (wm.focused != NULL && wm.focused->selcol->selrow->seltab == tab)
+ tabfocus(tab, 0);
+ ewmhsetclients();
+ ewmhsetclientsstacking();
+}
+
+/* unmanage tab (and delete its row if it is the only tab); return whether deletion occurred */
+int
+unmanagetab(struct Object *obj, int ignoreunmap)
+{
+ struct Container *c, *next;
+ struct Column *col;
+ struct Row *row;
+ struct Tab *t;
+ struct Monitor *mon;
+ int desk;
+ int moveresize;
+ int focus;
+
+ t = (struct Tab *)obj;
+ if (ignoreunmap && t->ignoreunmap) {
+ t->ignoreunmap--;
+ return 0;
+ }
+ row = t->row;
+ col = row->col;
+ c = col->c;
+ desk = c->desk;
+ mon = c->mon;
+ moveresize = 1;
+ next = c;
+ tabdel(t);
+ focus = (c == wm.focused);
+ if (row->ntabs == 0) {
+ rowdel(row);
+ if (col->nrows == 0) {
+ coldel(col);
+ if (c->ncols == 0) {
+ containerdel(c);
+ next = getnextfocused(mon, desk);
+ moveresize = 0;
+ }
+ }
+ }
+ if (moveresize) {
+ containercalccols(c, 1, 1);
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, 0);
+ shodgrouptab(c);
+ shodgroupcontainer(c);
+ }
+ if (focus) {
+ tabfocus((next != NULL) ? next->selcol->selrow->seltab : NULL, 0);
+ }
+ return 1;
+}
+
+/* delete dialog; return whether dialog was deleted */
+int
+unmanagedialog(struct Object *obj, int ignoreunmap)
+{
+ struct Dialog *dial;
+
+ dial = (struct Dialog *)obj;
+ if (ignoreunmap && dial->ignoreunmap) {
+ dial->ignoreunmap--;
+ return 0;
+ }
+ TAILQ_REMOVE(&dial->tab->dialq, (struct Object *)dial, entry);
+ if (dial->pix != None)
+ XFreePixmap(dpy, dial->pix);
+ icccmdeletestate(dial->obj.win);
+ XReparentWindow(dpy, dial->obj.win, root, 0, 0);
+ XDestroyWindow(dpy, dial->frame);
+ free(dial);
+ return 1;
+}
+
+/* delete menu; return whether menu was deleted */
+int
+unmanagemenu(struct Object *obj, int ignoreunmap)
+{
+ struct Menu *menu;
+
+ menu = (struct Menu *)obj;
+ if (ignoreunmap && menu->ignoreunmap) {
+ menu->ignoreunmap--;
+ return 0;
+ }
+ menudelraise(menu->tab, menu);
+ if (menu->pix != None)
+ XFreePixmap(dpy, menu->pix);
+ if (menu->pixbutton != None)
+ XFreePixmap(dpy, menu->pixbutton);
+ if (menu->pixtitlebar != None)
+ XFreePixmap(dpy, menu->pixtitlebar);
+ icccmdeletestate(menu->obj.win);
+ XReparentWindow(dpy, menu->obj.win, root, 0, 0);
+ XDestroyWindow(dpy, menu->frame);
+ XDestroyWindow(dpy, menu->titlebar);
+ XDestroyWindow(dpy, menu->button);
+ free(menu->name);
+ free(menu);
+ return 1;
+}
diff --git a/xbar.c b/xbar.c
@@ -0,0 +1,65 @@
+#include "shod.h"
+
+/* fill strut array of bar */
+void
+barstrut(struct Bar *bar)
+{
+ unsigned long *arr;
+ unsigned long l, i;
+
+ for (i = 0; i < STRUT_LAST; i++)
+ bar->strut[i] = 0;
+ bar->ispartial = 1;
+ l = getcardsprop(bar->obj.win, atoms[_NET_WM_STRUT_PARTIAL], &arr);
+ if (arr == NULL) {
+ bar->ispartial = 0;
+ l = getcardsprop(bar->obj.win, atoms[_NET_WM_STRUT], &arr);
+ if (arr == NULL) {
+ return;
+ }
+ }
+ for (i = 0; i < STRUT_LAST && i < l; i++)
+ bar->strut[i] = arr[i];
+ XFree(arr);
+}
+
+/* map bar window */
+void
+managebar(struct Tab *tab, struct Monitor *mon, int desk, Window win, Window leader, XRectangle rect, int state, int ignoreunmap)
+{
+ struct Bar *bar;
+
+ (void)tab;
+ (void)mon;
+ (void)desk;
+ (void)leader;
+ (void)rect;
+ (void)state;
+ (void)ignoreunmap;
+ Window wins[2] = {wm.layerwins[LAYER_DOCK], win};
+
+ bar = emalloc(sizeof(*bar));
+ *bar = (struct Bar){
+ .obj.win = win,
+ .obj.type = TYPE_DOCK,
+ };
+ TAILQ_INSERT_HEAD(&wm.barq, (struct Object *)bar, entry);
+ XRestackWindows(dpy, wins, 2);
+ XMapWindow(dpy, win);
+ barstrut(bar);
+ monupdatearea();
+}
+
+/* delete bar */
+int
+unmanagebar(struct Object *obj, int dummy)
+{
+ struct Bar *bar;
+
+ (void)dummy;
+ bar = (struct Bar *)obj;
+ TAILQ_REMOVE(&wm.barq, (struct Object *)bar, entry);
+ free(bar);
+ monupdatearea();
+ return 0;
+}
diff --git a/xdock.c b/xdock.c
@@ -0,0 +1,195 @@
+#include "shod.h"
+
+/* decorate dock */
+static void
+dockdecorate(void)
+{
+ if (dock.pw != dock.w || dock.ph != dock.h || dock.pix == None)
+ pixmapnew(&dock.pix, dock.win, dock.w, dock.h);
+ dock.pw = dock.w;
+ dock.ph = dock.h;
+ drawdock(dock.pix, dock.w, dock.h);
+ drawcommit(dock.pix, dock.win, dock.w, dock.h);
+}
+
+/* create dockapp */
+static void
+dockappnew(Window win, int w, int h, int dockpos, int ignoreunmap)
+{
+ struct Dockapp *dapp;
+ struct Object *prev;
+
+ dapp = emalloc(sizeof(*dapp));
+ *dapp = (struct Dockapp){
+ .obj.type = TYPE_DOCKAPP,
+ .obj.win = win,
+ .w = w,
+ .h = h,
+ .ignoreunmap = ignoreunmap,
+ .dockpos = dockpos,
+ };
+ TAILQ_FOREACH_REVERSE(prev, &dock.dappq, Queue, entry)
+ if (((struct Dockapp *)prev)->dockpos <= dockpos)
+ break;
+ if (prev != NULL) {
+ TAILQ_INSERT_AFTER(&dock.dappq, prev, (struct Object *)dapp, entry);
+ } else {
+ TAILQ_INSERT_HEAD(&dock.dappq, (struct Object *)dapp, entry);
+ }
+}
+
+/* update dock position; create it, if necessary */
+void
+dockupdate(void)
+{
+ struct Object *p;
+ struct Dockapp *dapp;
+ Window wins[2];
+ int size;
+ int n;
+
+ size = 0;
+ TAILQ_FOREACH(p, &dock.dappq, entry) {
+ dapp = (struct Dockapp *)p;
+ switch (config.dockgravity[0]) {
+ case 'N':
+ dapp->x = DOCKBORDER + size;
+ dapp->y = DOCKBORDER;
+ n = dapp->w / config.dockspace + (dapp->w % config.dockspace ? 1 : 0);
+ n *= config.dockspace;
+ dapp->x += max(0, (n - dapp->w) / 2);
+ dapp->y += max(0, (config.dockwidth - dapp->h) / 2);
+ break;
+ case 'S':
+ dapp->x = DOCKBORDER + size;
+ dapp->y = DOCKBORDER;
+ n = dapp->w / config.dockspace + (dapp->w % config.dockspace ? 1 : 0);
+ n *= config.dockspace;
+ dapp->x += max(0, (n - dapp->w) / 2);
+ dapp->y += max(0, (config.dockwidth - dapp->h) / 2);
+ break;
+ case 'W':
+ dapp->x = DOCKBORDER;
+ dapp->y = DOCKBORDER + size;
+ n = dapp->h / config.dockspace + (dapp->h % config.dockspace ? 1 : 0);
+ n *= config.dockspace;
+ dapp->x += max(0, (config.dockwidth - dapp->w) / 2);
+ dapp->y += max(0, (n - dapp->h) / 2);
+ break;
+ case 'E':
+ default:
+ dapp->y = DOCKBORDER + size;
+ dapp->x = DOCKBORDER;
+ n = dapp->h / config.dockspace + (dapp->h % config.dockspace ? 1 : 0);
+ n *= config.dockspace;
+ dapp->x += max(0, (config.dockwidth - dapp->w) / 2);
+ dapp->y += max(0, (n - dapp->h) / 2);
+ break;
+ }
+ size += n;
+ }
+ if (size == 0) {
+ XUnmapWindow(dpy, dock.win);
+ dock.mapped = 0;
+ return;
+ }
+ dock.mapped = 1;
+ size += DOCKBORDER * 2;
+ switch (config.dockgravity[0]) {
+ case 'N':
+ dock.h = config.dockwidth;
+ dock.y = 0;
+ break;
+ case 'S':
+ dock.h = config.dockwidth;
+ dock.y = TAILQ_FIRST(&wm.monq)->mh - config.dockwidth;
+ break;
+ case 'W':
+ dock.w = config.dockwidth;
+ dock.x = 0;
+ break;
+ case 'E':
+ default:
+ dock.w = config.dockwidth;
+ dock.x = TAILQ_FIRST(&wm.monq)->mw - config.dockwidth;
+ dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
+ dock.y = TAILQ_FIRST(&wm.monq)->mh / 2 - size / 2;
+ break;
+ }
+ if (config.dockgravity[0] == 'N' || config.dockgravity[0] == 'S') {
+ switch (config.dockgravity[1]) {
+ case 'W':
+ dock.w = min(size, TAILQ_FIRST(&wm.monq)->mw);
+ dock.x = 0;
+ break;
+ case 'E':
+ dock.w = min(size, TAILQ_FIRST(&wm.monq)->mw);
+ dock.x = TAILQ_FIRST(&wm.monq)->mw - size;
+ break;
+ default:
+ dock.w = min(size, TAILQ_FIRST(&wm.monq)->mw);
+ dock.x = TAILQ_FIRST(&wm.monq)->mw / 2 - size / 2;
+ break;
+ }
+ } else if (config.dockgravity[0] != '\0') {
+ switch (config.dockgravity[1]) {
+ case 'N':
+ dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
+ dock.y = 0;
+ break;
+ case 'S':
+ dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
+ dock.y = TAILQ_FIRST(&wm.monq)->mh - size;
+ break;
+ default:
+ dock.h = min(size, TAILQ_FIRST(&wm.monq)->mh);
+ dock.y = TAILQ_FIRST(&wm.monq)->mh / 2 - size / 2;
+ break;
+ }
+ }
+ TAILQ_FOREACH(p, &dock.dappq, entry) {
+ dapp = (struct Dockapp *)p;
+ XMoveWindow(dpy, dapp->obj.win, dapp->x, dapp->y);
+ winnotify(dapp->obj.win, dock.x + dapp->x, dock.y + dapp->y, dapp->w, dapp->h);
+ }
+ dockdecorate();
+ wins[0] = wm.layerwins[LAYER_DOCK];
+ wins[1] = dock.win;
+ XMoveResizeWindow(dpy, dock.win, dock.x, dock.y, dock.w, dock.h);
+ XRestackWindows(dpy, wins, 2);
+ XMapWindow(dpy, dock.win);
+ XMapSubwindows(dpy, dock.win);
+}
+
+/* map dockapp window */
+void
+managedockapp(struct Tab *tab, struct Monitor *mon, int desk, Window win, Window leader, XRectangle rect, int pos, int ignoreunmap)
+{
+ (void)tab;
+ (void)mon;
+ (void)desk;
+ (void)leader;
+ XReparentWindow(dpy, win, dock.win, 0, 0);
+ dockappnew(win, rect.width, rect.height, pos, ignoreunmap);
+ dockupdate();
+ monupdatearea();
+}
+
+/* delete dockapp */
+int
+unmanagedockapp(struct Object *obj, int ignoreunmap)
+{
+ struct Dockapp *dapp;
+
+ dapp = (struct Dockapp *)obj;
+ if (ignoreunmap && dapp->ignoreunmap) {
+ dapp->ignoreunmap--;
+ return 0;
+ }
+ TAILQ_REMOVE(&dock.dappq, (struct Object *)dapp, entry);
+ XReparentWindow(dpy, dapp->obj.win, root, 0, 0);
+ free(dapp);
+ dockupdate();
+ monupdatearea();
+ return 0;
+}
diff --git a/xdraw.c b/xdraw.c
@@ -0,0 +1,752 @@
+#include <err.h>
+
+#include "shod.h"
+
+#define DOCK_BORDER_THICKNESS 1
+
+static GC gc;
+static struct Theme {
+ XftFont *font;
+ XftColor fg[STYLE_LAST][2];
+ unsigned long border[STYLE_LAST][COLOR_LAST];
+ unsigned long dock[2];
+} theme;
+
+/* get color from color string */
+static unsigned long
+ealloccolor(const char *s)
+{
+ XColor color;
+
+ if(!XAllocNamedColor(dpy, colormap, s, &color, &color)) {
+ warnx("could not allocate color: %s", s);
+ return BlackPixel(dpy, screen);
+ }
+ return color.pixel;
+}
+
+/* get XftColor from color string */
+static void
+eallocxftcolor(const char *s, XftColor *color)
+{
+ if(!XftColorAllocName(dpy, visual, colormap, s, color))
+ errx(1, "could not allocate color: %s", s);
+}
+
+/* win was exposed, return the pixmap of its contents and the pixmap's size */
+static int
+getexposed(Window win, Pixmap *pix, int *pw, int *ph)
+{
+ struct Object *n, *t, *d, *m;
+ struct Container *c;
+ struct Column *col;
+ struct Row *row;
+ struct Tab *tab;
+ struct Dialog *dial;
+ struct Menu *menu;
+ struct Notification *notif;
+
+ if (wm.wmcheckwin == win) {
+ *pix = wm.wmcheckpix;
+ *pw = 2 * config.borderwidth + config.titlewidth;
+ *ph = 2 * config.borderwidth + config.titlewidth;
+ return 1;
+ }
+ if (dock.win == win) {
+ *pix = dock.pix;
+ *pw = dock.w;
+ *ph = dock.h;
+ return 1;
+ }
+ TAILQ_FOREACH(n, &wm.notifq, entry) {
+ notif = (struct Notification *)n;
+ if (notif->frame == win) {
+ *pix = notif->frame;
+ *pw = notif->pw;
+ *ph = notif->ph;
+ return 1;
+ }
+ }
+ TAILQ_FOREACH(c, &wm.focusq, entry) {
+ if (c->frame == win) {
+ *pix = c->pix;
+ *pw = c->pw;
+ *ph = c->ph;
+ return 1;
+ }
+ TAILQ_FOREACH(col, &(c)->colq, entry) {
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ if (row->bar == win) {
+ *pix = row->pixbar;
+ *pw = row->pw;
+ *ph = config.titlewidth;
+ return 1;
+ }
+ if (row->bl == win) {
+ *pix = row->pixbl;
+ *pw = config.titlewidth;
+ *ph = config.titlewidth;
+ return 1;
+ }
+ if (row->br == win) {
+ *pix = row->pixbr;
+ *pw = config.titlewidth;
+ *ph = config.titlewidth;
+ return 1;
+ }
+ TAILQ_FOREACH(t, &row->tabq, entry) {
+ tab = (struct Tab *)t;
+ if (tab->frame == win) {
+ *pix = tab->pix;
+ *pw = tab->pw;
+ *ph = tab->ph;
+ return 1;
+ }
+ if (tab->title == win) {
+ *pix = tab->pixtitle;
+ *pw = tab->ptw;
+ *ph = config.titlewidth;
+ return 1;
+ }
+ TAILQ_FOREACH(d, &tab->dialq, entry) {
+ dial = (struct Dialog *)d;
+ if (dial->frame == win) {
+ *pix = dial->pix;
+ *pw = dial->pw;
+ *ph = dial->ph;
+ return 1;
+ }
+ }
+ TAILQ_FOREACH(m, &tab->menuq, entry) {
+ menu = (struct Menu *)m;
+ if (menu->frame == win) {
+ *pix = menu->pix;
+ *pw = menu->pw;
+ *ph = menu->ph;
+ return 1;
+ }
+ if (menu->titlebar == win) {
+ *pix = menu->pixtitlebar;
+ *pw = menu->tw;
+ *ph = menu->th;
+ return 1;
+ }
+ if (menu->button == win) {
+ *pix = menu->pixbutton;
+ *pw = config.titlewidth;
+ *ph = config.titlewidth;
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+void
+pixmapnew(Pixmap *pix, Window win, int w, int h)
+{
+ if (*pix != None)
+ XFreePixmap(dpy, *pix);
+ *pix = XCreatePixmap(dpy, win, w, h, depth);
+}
+
+void
+drawcommit(Pixmap pix, Window win, int w, int h)
+{
+ XCopyArea(dpy, pix, win, gc, 0, 0, w, h, 0, 0);
+}
+
+/* draw text into drawable */
+void
+drawtitle(Drawable pix, const char *text, int w, int drawlines, int style, int pressed)
+{
+ XGCValues val;
+ XGlyphInfo box;
+ XftColor *color;
+ XftDraw *draw;
+ size_t len;
+ unsigned int top, bot;
+ int i, x, y;
+
+ top = theme.border[style][pressed ? COLOR_DARK : COLOR_LIGHT];
+ bot = theme.border[style][pressed ? COLOR_LIGHT : COLOR_DARK];
+ color = &theme.fg[style][1];
+ draw = XftDrawCreate(dpy, pix, visual, colormap);
+ len = strlen(text);
+ XftTextExtentsUtf8(dpy, theme.font, text, len, &box);
+ x = max(0, (w - box.width) / 2 + box.x);
+ y = (config.titlewidth - box.height) / 2 + box.y;
+ for (i = 3; drawlines && i < config.titlewidth - 3; i += 3) {
+ val.foreground = top;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangle(dpy, pix, gc, 4, i, x - 8, 1);
+ XFillRectangle(dpy, pix, gc, w - x + 2, i, x - 6, 1);
+ }
+ for (i = 4; drawlines && i < config.titlewidth - 2; i += 3) {
+ val.foreground = bot;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangle(dpy, pix, gc, 4, i, x - 8, 1);
+ XFillRectangle(dpy, pix, gc, w - x + 2, i, x - 6, 1);
+ }
+ XftDrawStringUtf8(draw, color, theme.font, x, y, text, len);
+ XftDrawDestroy(draw);
+}
+
+/* draw borders with shadows */
+void
+drawborders(Pixmap pix, int w, int h, int style)
+{
+ XGCValues val;
+ XRectangle *recs;
+ unsigned long *decor;
+ int partw, parth;
+ int i;
+
+ if (w <= 0 || h <= 0)
+ return;
+
+ decor = theme.border[style];
+ partw = w - 2 * config.borderwidth;
+ parth = h - 2 * config.borderwidth;
+
+ recs = ecalloc(config.shadowthickness * 4, sizeof(*recs));
+
+ /* draw background */
+ val.foreground = decor[COLOR_MID];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangle(dpy, pix, gc, 0, 0, w, h);
+
+ /* draw light shadow */
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 4 + 0] = (XRectangle){.x = i, .y = i, .width = 1, .height = h - 1 - i};
+ recs[i * 4 + 1] = (XRectangle){.x = i, .y = i, .width = w - 1 - i, .height = 1};
+ recs[i * 4 + 2] = (XRectangle){.x = w - config.borderwidth + i, .y = config.borderwidth - 1 - i, .width = 1, .height = parth + 2 * (i + 1)};
+ recs[i * 4 + 3] = (XRectangle){.x = config.borderwidth - 1 - i, .y = h - config.borderwidth + i, .width = partw + 2 * (i + 1), .height = 1};
+ }
+ val.foreground = decor[COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 4);
+
+ /* draw dark shadow */
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 4 + 0] = (XRectangle){.x = w - 1 - i, .y = i, .width = 1, .height = h - i * 2};
+ recs[i * 4 + 1] = (XRectangle){.x = i, .y = h - 1 - i, .width = w - i * 2, .height = 1};
+ recs[i * 4 + 2] = (XRectangle){.x = config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = 1, .height = parth + 1 + i * 2};
+ recs[i * 4 + 3] = (XRectangle){.x = config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = partw + 1 + i * 2, .height = 1};
+ }
+ val.foreground = decor[COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 4);
+
+ free(recs);
+}
+
+/* draw and fill rectangle */
+void
+drawbackground(Pixmap pix, int x, int y, int w, int h, int style)
+{
+ XGCValues val;
+
+ val.foreground = theme.border[style][COLOR_MID];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangle(dpy, pix, gc, x, y, w, h);
+}
+
+/* draw rectangle shadows */
+void
+drawshadow(Pixmap pix, int x, int y, int w, int h, int style, int pressed)
+{
+ XGCValues val;
+ XRectangle *recs;
+ unsigned long top, bot;
+ int i;
+
+ if (w <= 0 || h <= 0)
+ return;
+
+ top = theme.border[style][pressed ? COLOR_DARK : COLOR_LIGHT];
+ bot = theme.border[style][pressed ? COLOR_LIGHT : COLOR_DARK];
+ recs = ecalloc(config.shadowthickness * 2, sizeof(*recs));
+
+ /* draw light shadow */
+ for(i = 0; i < config.shadowthickness; i++) {
+ recs[i * 2] = (XRectangle){.x = x + i, .y = y + i, .width = 1, .height = h - (i * 2 + 1)};
+ recs[i * 2 + 1] = (XRectangle){.x = x + i, .y = y + i, .width = w - (i * 2 + 1), .height = 1};
+ }
+ val.foreground = top;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 2);
+
+ /* draw dark shadow */
+ for(i = 0; i < config.shadowthickness; i++) {
+ recs[i * 2] = (XRectangle){.x = x + w - 1 - i, .y = y + i, .width = 1, .height = h - i * 2};
+ recs[i * 2 + 1] = (XRectangle){.x = x + i, .y = y + h - 1 - i, .width = w - i * 2, .height = 1};
+ }
+ val.foreground = bot;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 2);
+
+ free(recs);
+}
+
+void
+drawframe(Pixmap pix, int isshaded, int w, int h, enum Octant o, int style)
+{
+ XRectangle *recs;
+ XGCValues val;
+ unsigned long *decor;
+ int x, y, i;
+
+ decor = theme.border[style];
+ recs = ecalloc(config.shadowthickness * 5, sizeof(*recs));
+
+ /* top edge */
+ drawshadow(pix, config.corner, 0, w - config.corner * 2, config.borderwidth, style, o == N);
+
+ /* bottom edge */
+ drawshadow(pix, config.corner, h - config.borderwidth, w - config.corner * 2, config.borderwidth, style, o == S);
+
+ /* left edge */
+ drawshadow(pix, 0, config.corner, config.borderwidth, h - config.corner * 2, style, o == W);
+
+ /* left edge */
+ drawshadow(pix, w - config.borderwidth, config.corner, config.borderwidth, h - config.corner * 2, style, o == E);
+
+ if (isshaded) {
+ /* left corner */
+ x = 0;
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = x + i, .y = 0, .width = 1, .height = h - 1 - i};
+ recs[i * 3 + 1] = (XRectangle){.x = x + 0, .y = i, .width = config.corner - 1 - i, .height = 1};
+ recs[i * 3 + 2] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = h - config.borderwidth + i, .width = config.titlewidth, .height = 1};
+ }
+ val.foreground = (o & W) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 5 + 0] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = 1, .height = h - config.borderwidth * 2 + 1 + i * 2};
+ recs[i * 5 + 1] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = config.borderwidth - 1 - i, .width = config.titlewidth + 1 + i, .height = 1};
+ recs[i * 5 + 2] = (XRectangle){.x = x + config.corner - 1 - i, .y = i, .width = 1, .height = config.borderwidth - i};
+ recs[i * 5 + 3] = (XRectangle){.x = x + config.corner - 1 - i, .y = h - config.borderwidth + i, .width = 1, .height = config.borderwidth - i};
+ recs[i * 5 + 4] = (XRectangle){.x = x + i, .y = h - 1 - i, .width = config.corner - i, .height = 1};
+ }
+ val.foreground = (o & W) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 5);
+
+ /* right corner */
+ x = w - config.corner;
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 5 + 0] = (XRectangle){.x = x + i, .y = 0, .width = 1, .height = config.borderwidth - 1 - i};
+ recs[i * 5 + 1] = (XRectangle){.x = x + 0, .y = i, .width = config.corner - 1 - i, .height = 1};
+ recs[i * 5 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = config.borderwidth - 1 - i, .width = 1, .height = h - config.borderwidth * 2 + 1 + i * 2};
+ recs[i * 5 + 3] = (XRectangle){.x = x + i, .y = h - config.borderwidth + i, .width = config.titlewidth + 1, .height = 1};
+ recs[i * 5 + 4] = (XRectangle){.x = x + i, .y = h - config.borderwidth + i, .width = 1, .height = config.borderwidth - 1 - i * 2};
+ }
+ val.foreground = (o == E) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 5);
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = x + config.corner - 1 - i, .y = i, .width = 1, .height = h - i};
+ recs[i * 3 + 1] = (XRectangle){.x = x + i, .y = config.borderwidth - 1 - i, .width = config.titlewidth, .height = 1};
+ recs[i * 3 + 2] = (XRectangle){.x = x + i, .y = h - 1 - i, .width = config.corner - i, .height = 1};
+ }
+ val.foreground = (o == E) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+ } else {
+ /* top left corner */
+ x = y = 0;
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 2 + 0] = (XRectangle){.x = x + i, .y = y + 0, .width = 1, .height = config.corner - 1 - i};
+ recs[i * 2 + 1] = (XRectangle){.x = x + 0, .y = y + i, .width = config.corner - 1 - i, .height = 1};
+ }
+ val.foreground = (o == NW) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 2);
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 4 + 0] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + config.borderwidth - 1 - i, .width = 1, .height = config.titlewidth + 1 + i};
+ recs[i * 4 + 1] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + config.borderwidth - 1 - i, .width = config.titlewidth + 1 + i, .height = 1};
+ recs[i * 4 + 2] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + i, .width = 1, .height = config.borderwidth - i};
+ recs[i * 4 + 3] = (XRectangle){.x = x + i, .y = y + config.corner - 1 - i, .width = config.borderwidth - i, .height = 1};
+ }
+ val.foreground = (o == NW) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 4);
+
+ /* bottom left corner */
+ x = 0;
+ y = h - config.corner;
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = x + i, .y = y + 0, .width = 1, .height = config.corner - 1 - i};
+ recs[i * 3 + 1] = (XRectangle){.x = x + 0, .y = y + i, .width = config.borderwidth - 1 - i, .height = 1};
+ recs[i * 3 + 2] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + config.titlewidth + i, .width = config.titlewidth, .height = 1};
+ }
+ val.foreground = (o == SW) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = x + config.borderwidth - 1 - i, .y = y + i, .width = 1, .height = config.titlewidth};
+ recs[i * 3 + 1] = (XRectangle){.x = x + i, .y = y + config.corner - 1 - i, .width = config.corner - i, .height = 1};
+ recs[i * 3 + 2] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + config.titlewidth + i, .width = 1, .height = config.borderwidth - i};
+ }
+ val.foreground = (o == SW) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+
+ /* top right corner */
+ x = w - config.corner;
+ y = 0;
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = x + i, .y = y + 0, .width = 1, .height = config.borderwidth - 1 - i};
+ recs[i * 3 + 1] = (XRectangle){.x = x + 0, .y = y + i, .width = config.corner - 1 - i, .height = 1};
+ recs[i * 3 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = y + config.borderwidth - 1 - i, .width = 1, .height = config.titlewidth};
+ }
+ val.foreground = (o == NE) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + i, .width = 1, .height = config.corner};
+ recs[i * 3 + 1] = (XRectangle){.x = x + i, .y = y + config.borderwidth - 1 - i, .width = config.titlewidth, .height = 1};
+ recs[i * 3 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = y + config.corner - 1 - i, .width = config.borderwidth - i, .height = 1};
+ }
+ val.foreground = (o == NE) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+
+ /* bottom right corner */
+ x = w - config.corner;
+ y = h - config.corner;
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 4 + 0] = (XRectangle){.x = x + i, .y = y + config.titlewidth + i, .width = 1, .height = config.borderwidth - 1 - i * 2};
+ recs[i * 4 + 1] = (XRectangle){.x = x + config.titlewidth + i, .y = y + i, .width = config.borderwidth - 1 - i * 2, .height = 1};
+ recs[i * 4 + 2] = (XRectangle){.x = x + config.titlewidth + i, .y = y + i, .width = 1, .height = config.titlewidth + 1};
+ recs[i * 4 + 3] = (XRectangle){.x = x + i, .y = y + config.titlewidth + i, .width = config.titlewidth + 1, .height = 1};
+ }
+ val.foreground = (o == SE) ? decor[COLOR_DARK] : decor[COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 4);
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 2 + 0] = (XRectangle){.x = x + config.corner - 1 - i, .y = y + i, .width = 1, .height = config.corner - i};
+ recs[i * 2 + 1] = (XRectangle){.x = x + i, .y = y + config.corner - 1 - i, .width = config.corner - i, .height = 1};
+ }
+ val.foreground = (o == SE) ? decor[COLOR_LIGHT] : decor[COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 2);
+ }
+ free(recs);
+}
+
+void
+drawprompt(Pixmap pix, int w, int h)
+{
+ XGCValues val;
+ XRectangle *recs;
+ int i, partw, parth;
+
+ recs = ecalloc(config.shadowthickness * 3, sizeof(*recs));
+ partw = w - 2 * config.borderwidth;
+ parth = h - 2 * config.borderwidth;
+
+ /* draw light shadow */
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = i, .y = i, .width = 1, .height = h - 1 - i};
+ recs[i * 3 + 1] = (XRectangle){.x = w - config.borderwidth + i, .y = 0, .width = 1, .height = parth + config.borderwidth + i};
+ recs[i * 3 + 2] = (XRectangle){.x = config.borderwidth - 1 - i, .y = h - config.borderwidth + i, .width = partw + 2 + i * 2, .height = 1};
+ }
+ val.foreground = theme.border[FOCUSED][COLOR_LIGHT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+
+ /* draw dark shadow */
+ for (i = 0; i < config.shadowthickness; i++) {
+ recs[i * 3 + 0] = (XRectangle){.x = w - 1 - i, .y = i, .width = 1, .height = h - i * 2};
+ recs[i * 3 + 1] = (XRectangle){.x = i, .y = h - 1 - i, .width = w - i * 2, .height = 1};
+ recs[i * 3 + 2] = (XRectangle){.x = config.borderwidth - 1 - i, .y = i, .width = 1, .height = parth + config.borderwidth};
+ }
+ val.foreground = theme.border[FOCUSED][COLOR_DARK];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, config.shadowthickness * 3);
+
+ free(recs);
+}
+
+void
+drawdock(Pixmap pix, int w, int h)
+{
+ XGCValues val;
+ XRectangle *recs;
+ int i;
+
+ if (pix == None || w <= 0 || h <= 0)
+ return;
+ recs = ecalloc(DOCK_BORDER_THICKNESS * 3, sizeof(*recs));
+
+ val.foreground = theme.dock[COLOR_DEF];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangle(dpy, pix, gc, 0, 0, w, h);
+
+ switch (config.dockgravity[0]) {
+ case 'N':
+ for(i = 0; i < DOCK_BORDER_THICKNESS; i++) {
+ recs[i * 3 + 0] = (XRectangle){
+ .x = i,
+ .y = 0,
+ .width = 1,
+ .height = h
+ };
+ recs[i * 3 + 1] = (XRectangle){
+ .x = 0,
+ .y = h - 1 - i,
+ .width = w,
+ .height = 1
+ };
+ recs[i * 3 + 2] = (XRectangle){
+ .x = w - 1 - i,
+ .y = 0,
+ .width = 1,
+ .height = h
+ };
+ }
+ break;
+ case 'W':
+ for(i = 0; i < DOCK_BORDER_THICKNESS; i++) {
+ recs[i * 3 + 0] = (XRectangle){
+ .x = 0,
+ .y = i,
+ .width = w,
+ .height = 1
+ };
+ recs[i * 3 + 1] = (XRectangle){
+ .x = w - 1 - i,
+ .y = 0,
+ .width = 1,
+ .height = h
+ };
+ recs[i * 3 + 2] = (XRectangle){
+ .x = 0,
+ .y = h - 1 - i,
+ .width = w,
+ .height = 1
+ };
+ }
+ break;
+ case 'E':
+ for(i = 0; i < DOCK_BORDER_THICKNESS; i++) {
+ recs[i * 3 + 0] = (XRectangle){
+ .x = 0,
+ .y = i,
+ .width = w,
+ .height = 1
+ };
+ recs[i * 3 + 1] = (XRectangle){
+ .x = i,
+ .y = 0,
+ .width = 1,
+ .height = h
+ };
+ recs[i * 3 + 2] = (XRectangle){
+ .x = 0,
+ .y = h - 1 - i,
+ .width = w,
+ .height = 1
+ };
+ }
+ break;
+ case 'S':
+ for(i = 0; i < DOCK_BORDER_THICKNESS; i++) {
+ recs[i * 3 + 0] = (XRectangle){
+ .x = i,
+ .y = 0,
+ .width = 1,
+ .height = h
+ };
+ recs[i * 3 + 1] = (XRectangle){
+ .x = 0,
+ .y = i,
+ .width = w,
+ .height = 1
+ };
+ recs[i * 3 + 2] = (XRectangle){
+ .x = w - 1 - i,
+ .y = 0,
+ .width = 1,
+ .height = h
+ };
+ }
+ break;
+ }
+ val.foreground = theme.dock[COLOR_ALT];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, DOCK_BORDER_THICKNESS * 3);
+ free(recs);
+}
+
+/* draw title bar buttons */
+void
+buttonleftdecorate(Window button, Pixmap pix, int style, int pressed)
+{
+ XGCValues val;
+ XRectangle recs[2];
+ unsigned long top, bot;
+ int x, y, w;
+
+ w = config.titlewidth - 9;
+ if (pressed) {
+ top = theme.border[style][COLOR_DARK];
+ bot = theme.border[style][COLOR_LIGHT];
+ } else {
+ top = theme.border[style][COLOR_LIGHT];
+ bot = theme.border[style][COLOR_DARK];
+ }
+
+ /* draw background */
+ drawbackground(pix, 0, 0, config.titlewidth, config.titlewidth, style);
+ drawshadow(pix, 0, 0, config.titlewidth, config.titlewidth, style, pressed);
+
+ if (w > 0) {
+ x = 4;
+ y = config.titlewidth / 2 - 1;
+ recs[0] = (XRectangle){.x = x, .y = y, .width = w, .height = 1};
+ recs[1] = (XRectangle){.x = x, .y = y, .width = 1, .height = 3};
+ val.foreground = (pressed) ? bot : top;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, 2);
+ recs[0] = (XRectangle){.x = x + 1, .y = y + 2, .width = w, .height = 1};
+ recs[1] = (XRectangle){.x = x + w, .y = y, .width = 1, .height = 3};
+ val.foreground = (pressed) ? top : bot;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangles(dpy, pix, gc, recs, 2);
+ }
+
+ drawcommit(pix, button, config.titlewidth, config.titlewidth);
+}
+
+/* draw title bar buttons */
+void
+buttonrightdecorate(Window button, Pixmap pix, int style, int pressed)
+{
+ XGCValues val;
+ XPoint pts[9];
+ unsigned long mid, top, bot;
+ int w;
+
+ w = (config.titlewidth - 11) / 2;
+ mid = theme.border[style][COLOR_MID];
+ if (pressed) {
+ top = theme.border[style][COLOR_DARK];
+ bot = theme.border[style][COLOR_LIGHT];
+ } else {
+ top = theme.border[style][COLOR_LIGHT];
+ bot = theme.border[style][COLOR_DARK];
+ }
+
+ /* draw background */
+ val.foreground = mid;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XFillRectangle(dpy, pix, gc, 0, 0, config.titlewidth, config.titlewidth);
+
+ drawshadow(pix, 0, 0, config.titlewidth, config.titlewidth, style, pressed);
+
+ if (w > 0) {
+ pts[0] = (XPoint){.x = 3, .y = config.titlewidth - 5};
+ pts[1] = (XPoint){.x = 0, .y = - 1};
+ pts[2] = (XPoint){.x = w, .y = -w};
+ pts[3] = (XPoint){.x = -w, .y = -w};
+ pts[4] = (XPoint){.x = 0, .y = -2};
+ pts[5] = (XPoint){.x = 2, .y = 0};
+ pts[6] = (XPoint){.x = w, .y = w};
+ pts[7] = (XPoint){.x = w, .y = -w};
+ pts[8] = (XPoint){.x = 1, .y = 0};
+ val.foreground = (pressed) ? bot : top;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XDrawLines(dpy, pix, gc, pts, 9, CoordModePrevious);
+
+ pts[0] = (XPoint){.x = 3, .y = config.titlewidth - 4};
+ pts[1] = (XPoint){.x = 2, .y = 0};
+ pts[2] = (XPoint){.x = w, .y = -w};
+ pts[3] = (XPoint){.x = w, .y = w};
+ pts[4] = (XPoint){.x = 2, .y = 0};
+ pts[5] = (XPoint){.x = 0, .y = -2};
+ pts[6] = (XPoint){.x = -w, .y = -w};
+ pts[7] = (XPoint){.x = w, .y = -w};
+ pts[8] = (XPoint){.x = 0, .y = -2};
+ val.foreground = (pressed) ? top : bot;
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XDrawLines(dpy, pix, gc, pts, 9, CoordModePrevious);
+ }
+
+ drawcommit(pix, button, config.titlewidth, config.titlewidth);
+}
+
+/* copy pixmap into exposed window */
+void
+copypixmap(Window win)
+{
+ Pixmap pix;
+ int pw, ph;
+
+ if (getexposed(win, &pix, &pw, &ph)) {
+ drawcommit(pix, win, pw, ph);
+ }
+}
+
+/* initialize decoration pixmap */
+void
+inittheme(void)
+{
+ int i, j;
+
+ gc = XCreateGC(dpy, wm.wmcheckwin, GCFillStyle, &(XGCValues){.fill_style = FillSolid});
+ config.corner = config.borderwidth + config.titlewidth;
+ config.divwidth = config.borderwidth;
+ wm.minsize = config.corner * 2 + 10;
+ for (i = 0; i < STYLE_LAST; i++) {
+ for (j = 0; j < COLOR_LAST; j++) {
+ theme.border[i][j] = ealloccolor(config.bordercolors[i][j]);
+ }
+ eallocxftcolor(config.bordercolors[i][COLOR_LIGHT], &theme.fg[i][0]);
+ eallocxftcolor(config.foreground, &theme.fg[i][1]);
+ }
+ for (j = 0; j < 2; j++)
+ theme.dock[j] = ealloccolor(config.dockcolors[j]);
+ theme.font = XftFontOpenXlfd(dpy, screen, config.font);
+ if (theme.font == NULL) {
+ theme.font = XftFontOpenName(dpy, screen, config.font);
+ if (theme.font == NULL) {
+ errx(1, "could not open font: %s", config.font);
+ }
+ }
+ drawbackground(
+ wm.wmcheckpix,
+ 0, 0,
+ 2 * config.borderwidth + config.titlewidth,
+ 2 * config.borderwidth + config.titlewidth,
+ FOCUSED
+ );
+ drawborders(
+ wm.wmcheckpix,
+ 2 * config.borderwidth + config.titlewidth,
+ 2 * config.borderwidth + config.titlewidth,
+ FOCUSED
+ );
+ drawshadow(
+ wm.wmcheckpix,
+ config.borderwidth,
+ config.borderwidth,
+ config.titlewidth,
+ config.titlewidth,
+ FOCUSED,
+ 0
+ );
+}
+
+/* free font */
+void
+cleantheme(void)
+{
+ XftFontClose(dpy, theme.font);
+}
diff --git a/xevents.c b/xevents.c
@@ -0,0 +1,1627 @@
+#include "shod.h"
+
+#define MOUSEEVENTMASK (ButtonReleaseMask | PointerMotionMask | ExposureMask)
+
+/* moveresize action */
+enum {
+ _NET_WM_MOVERESIZE_SIZE_TOPLEFT = 0,
+ _NET_WM_MOVERESIZE_SIZE_TOP = 1,
+ _NET_WM_MOVERESIZE_SIZE_TOPRIGHT = 2,
+ _NET_WM_MOVERESIZE_SIZE_RIGHT = 3,
+ _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4,
+ _NET_WM_MOVERESIZE_SIZE_BOTTOM = 5,
+ _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6,
+ _NET_WM_MOVERESIZE_SIZE_LEFT = 7,
+ _NET_WM_MOVERESIZE_MOVE = 8, /* movement only */
+ _NET_WM_MOVERESIZE_SIZE_KEYBOARD = 9, /* size via keyboard */
+ _NET_WM_MOVERESIZE_MOVE_KEYBOARD = 10, /* move via keyboard */
+ _NET_WM_MOVERESIZE_CANCEL = 11, /* cancel operation */
+};
+
+/* focus relative direction */
+enum {
+ _SHOD_FOCUS_ABSOLUTE = 0,
+ _SHOD_FOCUS_LEFT_CONTAINER = 1,
+ _SHOD_FOCUS_RIGHT_CONTAINER = 2,
+ _SHOD_FOCUS_TOP_CONTAINER = 3,
+ _SHOD_FOCUS_BOTTOM_CONTAINER = 4,
+ _SHOD_FOCUS_PREVIOUS_CONTAINER = 5,
+ _SHOD_FOCUS_NEXT_CONTAINER = 6,
+ _SHOD_FOCUS_LEFT_WINDOW = 7,
+ _SHOD_FOCUS_RIGHT_WINDOW = 8,
+ _SHOD_FOCUS_TOP_WINDOW = 9,
+ _SHOD_FOCUS_BOTTOM_WINDOW = 10,
+ _SHOD_FOCUS_PREVIOUS_WINDOW = 11,
+ _SHOD_FOCUS_NEXT_WINDOW = 12,
+};
+
+/* floating object type */
+enum {
+ FLOAT_CONTAINER,
+ FLOAT_MENU,
+};
+
+/* motif constants, mostly unused */
+enum {
+ /*
+ * Constants copied from lib/Xm/MwmUtil.h on motif's source code.
+ */
+
+ PROP_MOTIF_WM_HINTS_ELEMENTS = 5,
+ PROP_MWM_HINTS_ELEMENTS = PROP_MOTIF_WM_HINTS_ELEMENTS,
+
+ /* bit definitions for MwmHints.flags */
+ MWM_HINTS_FUNCTIONS = (1 << 0),
+ MWM_HINTS_DECORATIONS = (1 << 1),
+ MWM_HINTS_INPUT_MODE = (1 << 2),
+ MWM_HINTS_STATUS = (1 << 3),
+
+ /* bit definitions for MwmHints.functions */
+ MWM_FUNC_ALL = (1 << 0),
+ MWM_FUNC_RESIZE = (1 << 1),
+ MWM_FUNC_MOVE = (1 << 2),
+ MWM_FUNC_MINIMIZE = (1 << 3),
+ MWM_FUNC_MAXIMIZE = (1 << 4),
+ MWM_FUNC_CLOSE = (1 << 5),
+
+ /* bit definitions for MwmHints.decorations */
+ MWM_DECOR_ALL = (1 << 0),
+ MWM_DECOR_BORDER = (1 << 1),
+ MWM_DECOR_RESIZEH = (1 << 2),
+ MWM_DECOR_TITLE = (1 << 3),
+ MWM_DECOR_MENU = (1 << 4),
+ MWM_DECOR_MINIMIZE = (1 << 5),
+ MWM_DECOR_MAXIMIZE = (1 << 6),
+
+ /* values for MwmHints.input_mode */
+ MWM_INPUT_MODELESS = 0,
+ MWM_INPUT_PRIMARY_APPLICATION_MODAL = 1,
+ MWM_INPUT_SYSTEM_MODAL = 2,
+ MWM_INPUT_FULL_APPLICATION_MODAL = 3,
+
+ /* bit definitions for MwmHints.status */
+ MWM_TEAROFF_WINDOW = (1 << 0),
+};
+
+/* motif window manager (Mwm) hints */
+struct MwmHints {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long inputMode;
+ unsigned long status;
+};
+
+void (*managefuncs[TYPE_LAST])(struct Tab *, struct Monitor *, int, Window, Window, XRectangle, int, int) = {
+ [TYPE_NOTIFICATION] = managenotif,
+ [TYPE_DOCKAPP] = managedockapp,
+ [TYPE_NORMAL] = managetab,
+ [TYPE_DIALOG] = managedialog,
+ [TYPE_SPLASH] = managesplash,
+ [TYPE_PROMPT] = manageprompt,
+ [TYPE_MENU] = managemenu,
+ [TYPE_DOCK] = managebar,
+};
+
+int (*unmanagefuncs[TYPE_LAST])(struct Object *, int) = {
+ [TYPE_NOTIFICATION] = unmanagenotif,
+ [TYPE_DOCKAPP] = unmanagedockapp,
+ [TYPE_NORMAL] = unmanagetab,
+ [TYPE_DIALOG] = unmanagedialog,
+ [TYPE_SPLASH] = unmanagesplash,
+ [TYPE_PROMPT] = unmanageprompt,
+ [TYPE_MENU] = unmanagemenu,
+ [TYPE_DOCK] = unmanagebar,
+};
+
+#define GETMANAGED(head, p, w) \
+ TAILQ_FOREACH(p, &(head), entry) \
+ if (p->win == w) \
+ return (p); \
+
+/* 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));
+}
+
+/* get pointer to managed object given a window */
+struct Object *
+getmanaged(Window win)
+{
+ struct Object *p, *tab, *dial, *menu;
+ struct Container *c;
+ struct Column *col;
+ struct Row *row;
+ int i;
+
+ GETMANAGED(dock.dappq, p, win)
+ GETMANAGED(wm.barq, p, win)
+ GETMANAGED(wm.notifq, p, win)
+ GETMANAGED(wm.splashq, p, win)
+ TAILQ_FOREACH(c, &wm.focusq, entry) {
+ if (c->frame == win)
+ return (struct Object *)c->selcol->selrow->seltab;
+ for (i = 0; i < BORDER_LAST; i++)
+ if (c->curswin[i] == win)
+ return (struct Object *)c->selcol->selrow->seltab;
+ TAILQ_FOREACH(col, &(c)->colq, entry) {
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ if (row->bar == win || row->bl == win || row->br == win)
+ return (struct Object *)row->seltab;
+ TAILQ_FOREACH(tab, &row->tabq, entry) {
+ if (tab->win == win ||
+ ((struct Tab *)tab)->frame == win ||
+ ((struct Tab *)tab)->title == win)
+ return tab;
+ TAILQ_FOREACH(dial, &((struct Tab *)tab)->dialq, entry) {
+ if (dial->win == win ||
+ ((struct Dialog *)dial)->frame == win)
+ return dial;
+ }
+ TAILQ_FOREACH(menu, &((struct Tab *)tab)->menuq, entry) {
+ if (menu->win == win ||
+ ((struct Menu *)menu)->frame == win ||
+ ((struct Menu *)menu)->button == win ||
+ ((struct Menu *)menu)->titlebar == win)
+ return menu;
+ }
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+/* get tab given window is a dialog for */
+static struct Tab *
+getdialogfor(Window win)
+{
+ struct Object *obj;
+ Window tmpwin;
+
+ if (XGetTransientForHint(dpy, win, &tmpwin))
+ if ((obj = getmanaged(tmpwin)) != NULL && obj->type == TYPE_NORMAL)
+ return (struct Tab *)obj;
+ return NULL;
+}
+
+/* get tab equal to leader or having leader as group leader */
+static struct Tab *
+getleaderof(Window leader)
+{
+ struct Container *c;
+ struct Object *tab;
+
+ TAILQ_FOREACH(c, &wm.focusq, entry) {
+ TAB_FOREACH_BEGIN(c, tab){
+ if (tab->win == leader || ((struct Tab *)tab)->leader == leader)
+ return (struct Tab *)tab;
+ }TAB_FOREACH_END
+ }
+ return NULL;
+}
+
+/* get bitmask of container state from given window */
+static int
+getwinstate(Window win)
+{
+ int state;
+ unsigned long i, nstates;
+ unsigned char *list;
+ unsigned long dl; /* dummy variable */
+ int di; /* dummy variable */
+ Atom da; /* dummy variable */
+ Atom *as;
+
+ list = NULL;
+ if (XGetWindowProperty(dpy, win, atoms[_NET_WM_STATE], 0L, 1024, False, XA_ATOM, &da, &di, &nstates, &dl, &list) != Success || list == NULL) {
+ XFree(list);
+ return 0;
+ }
+ as = (Atom *)list;
+ state = 0;
+ for (i = 0; i < nstates; i++) {
+ if (as[i] == atoms[_NET_WM_STATE_STICKY]) {
+ state |= STICKY;
+ } else if (as[i] == atoms[_NET_WM_STATE_MAXIMIZED_VERT]) {
+ state |= MAXIMIZED;
+ } else if (as[i] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ]) {
+ state |= MAXIMIZED;
+ } else if (as[i] == atoms[_NET_WM_STATE_HIDDEN]) {
+ state |= MINIMIZED;
+ } else if (as[i] == atoms[_NET_WM_STATE_SHADED]) {
+ state |= SHADED;
+ } else if (as[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
+ state |= FULLSCREEN;
+ } else if (as[i] == atoms[_NET_WM_STATE_ABOVE]) {
+ state |= ABOVE;
+ } else if (as[i] == atoms[_NET_WM_STATE_BELOW]) {
+ state |= BELOW;
+ }
+ }
+ state |= isuserplaced(win);
+ XFree(list);
+ return state;
+}
+
+/* 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 text property from window; return `Success` on success */
+static int
+gettextprop(Window win, Atom atom, char *buf, size_t size)
+{
+ XTextProperty tprop;
+ int count;
+ char **list = NULL;
+
+ if (buf == NULL || size == 0)
+ return BadLength;
+ buf[0] = '\0';
+ if (!XGetTextProperty(dpy, win, &tprop, atom) || tprop.nitems == 0)
+ return BadAlloc;
+ if (XmbTextPropertyToTextList(dpy, &tprop, &list, &count) != Success ||
+ count < 1 || list == NULL || *list == NULL)
+ return BadAlloc;
+ strncpy(buf, *list, size - 1);
+ buf[size - 1] = '\0';
+ XFreeStringList(list);
+ XFree(tprop.value);
+ return Success;
+}
+
+/* get motif window manager hints property from window */
+static struct MwmHints *
+getmwmhints(Window win)
+{
+ struct MwmHints *mwmhints;
+ unsigned long dl;
+ Atom type;
+ int di;
+ int ret;
+
+ ret = XGetWindowProperty(dpy, win, atoms[_MOTIF_WM_HINTS],
+ 0L, PROP_MWM_HINTS_ELEMENTS,
+ False, atoms[_MOTIF_WM_HINTS],
+ &type, &di, &dl, &dl,
+ (unsigned char **)&mwmhints);
+ if ((ret == Success) && (type == atoms[_MOTIF_WM_HINTS]))
+ return mwmhints;
+ if (mwmhints != NULL)
+ XFree(mwmhints);
+ return NULL;
+}
+
+/* get window info based on its type */
+static int
+getwintype(Window win, Window *leader, struct Tab **tab, int *state)
+{
+ /* rules for identifying windows */
+ enum { CLASS = 0, INSTANCE = 1, ROLE = 2 };
+ char *rule[] = { "_", "_", "_" };
+
+ struct MwmHints *mwmhints;
+ XClassHint classh;
+ XWMHints *wmhints;
+ XrmValue xval;
+ Atom prop;
+ size_t i;
+ long n;
+ int type, isdockapp, ismenu;
+ char *ds;
+ char buf[NAMEMAXLEN];
+ char role[NAMEMAXLEN];
+
+ *tab = NULL;
+ *state = 0;
+ type = TYPE_UNKNOWN;
+ classh.res_class = NULL;
+ classh.res_name = NULL;
+ if (gettextprop(win, atoms[WM_WINDOW_ROLE], role, NAMEMAXLEN) == Success)
+ rule[ROLE] = role;
+ if (XGetClassHint(dpy, win, &classh)) {
+ rule[CLASS] = classh.res_class;
+ rule[INSTANCE] = classh.res_name;
+ }
+
+ /* get window state requested by application */
+ *state = getwinstate(win);
+
+ /* get window type (and other info) from default (hardcoded) rules */
+ for (i = 0; config.rules[i].class != NULL || config.rules[i].instance != NULL || config.rules[i].role != NULL; i++) {
+ if ((config.rules[i].class == NULL || strcmp(config.rules[i].class, rule[CLASS]) == 0)
+ && (config.rules[i].instance == NULL || strcmp(config.rules[i].instance, rule[INSTANCE]) == 0)
+ && (config.rules[i].role == NULL || strcmp(config.rules[i].role, rule[ROLE]) == 0)) {
+ if (config.rules[i].type != TYPE_MENU && config.rules[i].type != TYPE_DIALOG) {
+ type = config.rules[i].type;
+ }
+ if (config.rules[i].state >= 0) {
+ *state = config.rules[i].state;
+ }
+ }
+ }
+
+ /* get window type (and other info) from X resources */
+ if (xrm != NULL && xdb != NULL) {
+ /* check for window type */
+ (void)snprintf(buf, NAMEMAXLEN, "shod.%s.%s.%s.type", rule[CLASS], rule[INSTANCE], rule[ROLE]);
+ if (XrmGetResource(xdb, buf, "*", &ds, &xval) == True && xval.addr != NULL) {
+ if (strcasecmp(xval.addr, "DESKTOP") == 0) {
+ type = TYPE_DESKTOP;
+ } else if (strcasecmp(xval.addr, "DOCKAPP") == 0) {
+ type = TYPE_DOCKAPP;
+ } else if (strcasecmp(xval.addr, "PROMPT") == 0) {
+ type = TYPE_PROMPT;
+ } else if (strcasecmp(xval.addr, "NORMAL") == 0) {
+ type = TYPE_NORMAL;
+ }
+ }
+
+ /* check for window state */
+ (void)snprintf(buf, NAMEMAXLEN, "shod.%s.%s.%s.state", rule[CLASS], rule[INSTANCE], rule[ROLE]);
+ if (XrmGetResource(xdb, buf, "*", &ds, &xval) == True && xval.addr != NULL) {
+ *state = 0;
+ if (strcasestr(xval.addr, "above") != NULL) {
+ *state |= ABOVE;
+ }
+ if (strcasestr(xval.addr, "below") != NULL) {
+ *state |= BELOW;
+ }
+ if (strcasestr(xval.addr, "fullscreen") != NULL) {
+ *state |= FULLSCREEN;
+ }
+ if (strcasestr(xval.addr, "maximized") != NULL) {
+ *state |= MAXIMIZED;
+ }
+ if (strcasestr(xval.addr, "minimized") != NULL) {
+ *state |= MINIMIZED;
+ }
+ if (strcasestr(xval.addr, "shaded") != NULL) {
+ *state |= SHADED;
+ }
+ if (strcasestr(xval.addr, "sticky") != NULL) {
+ *state |= STICKY;
+ }
+ }
+
+ /* check for dockapp position */
+ (void)snprintf(buf, NAMEMAXLEN, "shod.%s.%s.%s.dockpos", rule[CLASS], rule[INSTANCE], rule[ROLE]);
+ if (XrmGetResource(xdb, buf, "*", &ds, &xval) == True) {
+ if ((n = strtol(xval.addr, NULL, 10)) >= 0 && n < INT_MAX) {
+ *state = n;
+ }
+ }
+ }
+
+ XFree(classh.res_class);
+ XFree(classh.res_name);
+
+ /* we already got the type of the window, return */
+ if (type != TYPE_UNKNOWN)
+ goto done;
+
+ /* try to guess window type */
+ type = TYPE_NORMAL;
+ prop = getatomprop(win, atoms[_NET_WM_WINDOW_TYPE]);
+ wmhints = XGetWMHints(dpy, win);
+ mwmhints = getmwmhints(win);
+ ismenu = mwmhints != NULL && (mwmhints->flags & MWM_HINTS_STATUS) && (mwmhints->status & MWM_TEAROFF_WINDOW);
+ isdockapp = (wmhints && (wmhints->flags & (IconWindowHint | StateHint)) && wmhints->initial_state == WithdrawnState);
+ *leader = (wmhints != NULL && (wmhints->flags & WindowGroupHint)) ? wmhints->window_group : None;
+ *tab = getdialogfor(win);
+ XFree(mwmhints);
+ XFree(wmhints);
+ if (isdockapp) {
+ type = TYPE_DOCKAPP;
+ } else if (prop == atoms[_NET_WM_WINDOW_TYPE_DESKTOP]) {
+ type = TYPE_DESKTOP;
+ } else if (prop == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
+ type = TYPE_DOCK;
+ } else if (prop == atoms[_NET_WM_WINDOW_TYPE_NOTIFICATION]) {
+ type = TYPE_NOTIFICATION;
+ } else if (prop == atoms[_NET_WM_WINDOW_TYPE_PROMPT]) {
+ type = TYPE_PROMPT;
+ } else if (prop == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
+ type = TYPE_SPLASH;
+ } else if (ismenu ||
+ prop == atoms[_NET_WM_WINDOW_TYPE_MENU] ||
+ prop == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
+ prop == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) {
+ if (*tab == NULL) {
+ *tab = getleaderof(*leader);
+ }
+ if (*tab != NULL) {
+ type = TYPE_MENU;
+ }
+ } else if (*tab != NULL) {
+ type = config.floatdialog ? TYPE_MENU : TYPE_DIALOG;
+ } else {
+ type = TYPE_NORMAL;
+ }
+
+done:
+ return type;
+}
+
+/* select window input events, grab mouse button presses, and clear its border */
+static void
+preparewin(Window win)
+{
+ XSelectInput(dpy, win, StructureNotifyMask | PropertyChangeMask | FocusChangeMask);
+ XGrabButton(dpy, AnyButton, AnyModifier, win, False, ButtonPressMask,
+ GrabModeSync, GrabModeSync, None, None);
+ XSetWindowBorderWidth(dpy, win, 0);
+}
+
+/* check whether window is urgent */
+static int
+getwinurgency(Window win)
+{
+ XWMHints *wmh;
+ int ret;
+
+ ret = 0;
+ if ((wmh = XGetWMHints(dpy, win)) != NULL) {
+ ret = wmh->flags & XUrgencyHint;
+ XFree(wmh);
+ }
+ return ret;
+}
+
+/* get row or column next to division the pointer is on */
+static void
+getdivisions(struct Container *c, struct Column **cdiv, struct Row **rdiv, int x, int y)
+{
+ struct Column *col;
+ struct Row *row;
+
+ *cdiv = NULL;
+ *rdiv = NULL;
+ TAILQ_FOREACH(col, &c->colq, entry) {
+ if (TAILQ_NEXT(col, entry) != NULL && x >= col->x + col->w && x < col->x + col->w + config.divwidth) {
+ *cdiv = col;
+ return;
+ }
+ if (x >= col->x && x < col->x + col->w) {
+ TAILQ_FOREACH(row, &col->rowq, entry) {
+ if (TAILQ_NEXT(row, entry) != NULL && y >= row->y + row->h && y < row->y + row->h + config.divwidth) {
+ *rdiv = row;
+ return;
+ }
+ }
+ }
+ }
+}
+
+/* get frame handle (NW/N/NE/W/E/SW/S/SE) the pointer is on */
+static enum Octant
+getframehandle(int w, int h, int x, int y)
+{
+ if ((y < config.borderwidth && x <= config.corner) || (x < config.borderwidth && y <= config.corner))
+ return NW;
+ else if ((y < config.borderwidth && x >= w - config.corner) || (x > w - config.borderwidth && y <= config.corner))
+ return NE;
+ else if ((y > h - config.borderwidth && x <= config.corner) || (x < config.borderwidth && y >= h - config.corner))
+ return SW;
+ else if ((y > h - config.borderwidth && x >= w - config.corner) || (x > w - config.borderwidth && y >= h - config.corner))
+ return SE;
+ else if (y < config.borderwidth)
+ return N;
+ else if (y >= h - config.borderwidth)
+ return S;
+ else if (x < config.borderwidth)
+ return W;
+ else if (x >= w - config.borderwidth)
+ return E;
+ return C;
+}
+
+/* get quadrant (NW/NE/SW/SE) the pointer is on */
+static enum Octant
+getquadrant(int w, int h, int x, int y)
+{
+ if (x >= w / 2 && y >= h / 2)
+ return SE;
+ if (x >= w / 2 && y <= h / 2)
+ return NE;
+ if (x <= w / 2 && y >= h / 2)
+ return SW;
+ if (x <= w / 2 && y <= h / 2)
+ return NW;
+ return C;
+}
+
+/* call one of the manage- functions */
+static void
+manage(Window win, XRectangle rect, int ignoreunmap)
+{
+ struct Tab *tab;
+ Window leader;
+ int state, type;
+
+ if (getmanaged(win) != NULL)
+ return;
+ type = getwintype(win, &leader, &tab, &state);
+ if (type == TYPE_DESKTOP) {
+ /* we do not handle desktop windows */
+ Window wins[2] = {wm.layerwins[LAYER_DESKTOP], win};
+
+ XRestackWindows(dpy, wins, 2);
+ XMapWindow(dpy, win);
+ return;
+ }
+ preparewin(win);
+ (*managefuncs[type])(tab, wm.selmon, wm.selmon->seldesk, win, leader, rect, state, ignoreunmap);
+}
+
+/* perform container switching (aka alt-tab) */
+static void
+alttab(int forward)
+{
+ struct Container *prev, *tohead;
+
+ if (TAILQ_EMPTY(&wm.focusq))
+ return;
+ prev = TAILQ_FIRST(&wm.focusq);
+ if (forward) {
+ TAILQ_FOREACH(tohead, &wm.focusq, entry) {
+ if (tohead != prev &&
+ !tohead->isminimized &&
+ tohead->mon == prev->mon &&
+ (tohead->issticky || tohead->desk == prev->desk)) {
+ break;
+ }
+ }
+ } else {
+ TAILQ_FOREACH_REVERSE(tohead, &wm.focusq, ContainerQueue, entry) {
+ if (tohead != prev &&
+ !tohead->isminimized &&
+ tohead->mon == prev->mon &&
+ (tohead->issticky || tohead->desk == prev->desk)) {
+ break;
+ }
+ }
+ }
+ if (tohead == NULL || tohead == prev)
+ return;
+ tabfocus(tohead->selcol->selrow->seltab, 1);
+}
+
+/* detach tab from window with mouse */
+static void
+mouseretab(struct Tab *tab, int xroot, int yroot, int x, int y)
+{
+ struct Monitor *mon;
+ struct Object *obj;
+ struct Row *row; /* row to be deleted, if emptied */
+ struct Container *c;
+ Window win;
+ XEvent ev;
+
+ row = tab->row;
+ c = row->col->c;
+ if (XGrabPointer(dpy, root, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime) != GrabSuccess)
+ goto done;
+ tabdetach(tab, xroot - x, yroot - y);
+ containermoveresize(c);
+ XUnmapWindow(dpy, tab->title);
+ XMoveWindow(
+ dpy, wm.wmcheckwin,
+ ev.xmotion.x_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth),
+ ev.xmotion.y_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth)
+ );
+ XRaiseWindow(dpy, wm.wmcheckwin);
+ while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
+ switch (ev.type) {
+ case Expose:
+ if (ev.xexpose.count == 0)
+ copypixmap(ev.xexpose.window);
+ break;
+ case MotionNotify:
+ XMoveWindow(
+ dpy, wm.wmcheckwin,
+ ev.xmotion.x_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth),
+ ev.xmotion.y_root - DNDDIFF - (2 * config.borderwidth + config.titlewidth)
+ );
+ break;
+ case ButtonRelease:
+ goto done;
+ }
+ }
+done:
+ XMoveWindow(
+ dpy, wm.wmcheckwin,
+ - (2 * config.borderwidth + config.titlewidth),
+ - (2 * config.borderwidth + config.titlewidth)
+ );
+ xroot = ev.xbutton.x_root - x;
+ yroot = ev.xbutton.y_root - y;
+ obj = getmanaged(ev.xbutton.subwindow);
+ c = NULL;
+ if (obj != NULL && obj->type == TYPE_NORMAL && ev.xbutton.subwindow == ((struct Tab *)obj)->row->col->c->frame) {
+ c = ((struct Tab *)obj)->row->col->c;
+ XTranslateCoordinates(dpy, ev.xbutton.window, c->frame, ev.xbutton.x_root, ev.xbutton.y_root, &x, &y, &win);
+ }
+ if (c == NULL || !tabattach(c, tab, x, y)) {
+ mon = getmon(x, y);
+ if (mon == NULL)
+ mon = wm.selmon;
+ managetab(
+ tab, mon, mon->seldesk,
+ None, None,
+ (XRectangle){
+ .x = xroot - config.titlewidth,
+ .y = yroot,
+ .width = tab->winw,
+ .height = tab->winh,
+ },
+ USERPLACED, 0
+ );
+ }
+ containerdelrow(row);
+ XUngrabPointer(dpy, CurrentTime);
+}
+
+/* resize container with mouse */
+static void
+mouseresize(int type, void *obj, int xroot, int yroot, enum Octant o)
+{
+ struct Container *c;
+ struct Menu *menu;
+ Window frame;
+ Cursor curs;
+ XEvent ev;
+ Time lasttime;
+ int *nx, *ny, *nw, *nh;
+ int x, y, dx, dy;
+
+ if (type == FLOAT_MENU) {
+ menu = (struct Menu *)obj;
+ nx = &menu->x;
+ ny = &menu->y;
+ nw = &menu->w;
+ nh = &menu->h;
+ frame = menu->frame;
+ menudecorate(menu, o != C);
+ } else {
+ c = (struct Container *)obj;
+ if (c->isfullscreen || c->b == 0)
+ return;
+ if (containerisshaded(c)) {
+ if (o & W) {
+ o = W;
+ } else if (o & E) {
+ o = E;
+ } else {
+ return;
+ }
+ }
+ nx = &c->nx;
+ ny = &c->ny;
+ nw = &c->nw;
+ nh = &c->nh;
+ frame = c->frame;
+ containerdecorate(c, NULL, NULL, 0, o);
+ }
+ switch (o) {
+ case NW:
+ curs = wm.cursors[CURSOR_NW];
+ break;
+ case NE:
+ curs = wm.cursors[CURSOR_NE];
+ break;
+ case SW:
+ curs = wm.cursors[CURSOR_SW];
+ break;
+ case SE:
+ curs = wm.cursors[CURSOR_SE];
+ break;
+ case N:
+ curs = wm.cursors[CURSOR_N];
+ break;
+ case S:
+ curs = wm.cursors[CURSOR_S];
+ break;
+ case W:
+ curs = wm.cursors[CURSOR_W];
+ break;
+ case E:
+ curs = wm.cursors[CURSOR_E];
+ break;
+ default:
+ curs = None;
+ break;
+ }
+ if (o & W)
+ x = xroot - *nx - config.borderwidth;
+ else if (o & E)
+ x = *nx + *nw - config.borderwidth - xroot;
+ else
+ x = 0;
+ if (o & N)
+ y = yroot - *ny - config.borderwidth;
+ else if (o & S)
+ y = *ny + *nh - config.borderwidth - yroot;
+ else
+ y = 0;
+ if (XGrabPointer(dpy, frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, curs, CurrentTime) != GrabSuccess)
+ goto done;
+ lasttime = 0;
+ while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
+ switch (ev.type) {
+ case Expose:
+ if (ev.xexpose.count == 0)
+ copypixmap(ev.xexpose.window);
+ break;
+ case ButtonRelease:
+ goto done;
+ break;
+ case MotionNotify:
+ if (x > *nw)
+ x = 0;
+ if (y > *nh)
+ y = 0;
+ if (o & W &&
+ ((ev.xmotion.x_root < xroot && x > ev.xmotion.x_root - *nx) ||
+ (ev.xmotion.x_root > xroot && x < ev.xmotion.x_root - *nx))) {
+ dx = xroot - ev.xmotion.x_root;
+ if (*nw + dx >= wm.minsize) {
+ *nx -= dx;
+ *nw += dx;
+ }
+ } else if (o & E &&
+ ((ev.xmotion.x_root > xroot && x > *nx + *nw - ev.xmotion.x_root) ||
+ (ev.xmotion.x_root < xroot && x < *nx + *nw - ev.xmotion.x_root))) {
+ dx = ev.xmotion.x_root - xroot;
+ if (*nw + dx >= wm.minsize) {
+ *nw += dx;
+ }
+ }
+ if (o & N &&
+ ((ev.xmotion.y_root < yroot && y > ev.xmotion.y_root - *ny) ||
+ (ev.xmotion.y_root > yroot && y < ev.xmotion.y_root - *ny))) {
+ dy = yroot - ev.xmotion.y_root;
+ if (*nh + dy >= wm.minsize) {
+ *ny -= dy;
+ *nh += dy;
+ }
+ } else if (o & S &&
+ ((ev.xmotion.y_root > yroot && *ny + *nh - ev.xmotion.y_root < y) ||
+ (ev.xmotion.y_root < yroot && *ny + *nh - ev.xmotion.y_root > y))) {
+ dy = ev.xmotion.y_root - yroot;
+ if (*nh + dy >= wm.minsize) {
+ *nh += dy;
+ }
+ }
+ if (ev.xmotion.time - lasttime > RESIZETIME) {
+ if (type == FLOAT_MENU) {
+ menumoveresize(menu);
+ menudecorate(menu, 0);
+ } else {
+ containercalccols(c, 0, 1);
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, o);
+ }
+ lasttime = ev.xmotion.time;
+ }
+ xroot = ev.xmotion.x_root;
+ yroot = ev.xmotion.y_root;
+ break;
+ }
+ }
+done:
+ if (type == FLOAT_MENU) {
+ menumoveresize(menu);
+ menudecorate(menu, 0);
+ } else {
+ containercalccols(c, 0, 1);
+ containermoveresize(c);
+ containerdecorate(c, NULL, NULL, 0, 0);
+ }
+ XUngrabPointer(dpy, CurrentTime);
+}
+
+/* move floating entity (container or menu) with mouse */
+static void
+mousemove(int type, void *p, int xroot, int yroot, enum Octant o)
+{
+ struct Container *c;
+ struct Menu *menu;
+ Window frame;
+ XEvent ev;
+ Time lasttime;
+ int x, y;
+
+ if (type == FLOAT_MENU) {
+ menu = (struct Menu *)p;
+ menudecorate(menu, o);
+ frame = menu->frame;
+ } else {
+ c = (struct Container *)p;
+ containerdecorate(c, NULL, NULL, 0, o);
+ frame = c->frame;
+ }
+ x = y = 0;
+ if (XGrabPointer(dpy, frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, wm.cursors[CURSOR_MOVE], CurrentTime) != GrabSuccess)
+ goto done;
+ lasttime = 0;
+ while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
+ switch (ev.type) {
+ case Expose:
+ if (ev.xexpose.count == 0)
+ copypixmap(ev.xexpose.window);
+ break;
+ case ButtonRelease:
+ goto done;
+ break;
+ case MotionNotify:
+ if (ev.xmotion.time - lasttime > MOVETIME) {
+ x = ev.xmotion.x_root - xroot;
+ y = ev.xmotion.y_root - yroot;
+ if (type == FLOAT_MENU) {
+ menuincrmove(menu, x, y);
+ } else {
+ containerincrmove(c, x, y);
+ }
+ lasttime = ev.xmotion.time;
+ xroot = ev.xmotion.x_root;
+ yroot = ev.xmotion.y_root;
+ }
+ break;
+ }
+ }
+done:
+ if (type == FLOAT_MENU) {
+ menumoveresize(menu);
+ menudecorate(menu, 0);
+ } else {
+ containermoveresize(c);
+ containerdecorate(c, NULL, NULL, 0, 0);
+ }
+ XUngrabPointer(dpy, CurrentTime);
+}
+
+/* close tab with mouse */
+static void
+mouseclose(int type, void *obj)
+{
+ struct Row *row;
+ struct Menu *menu;
+ Window win, button;
+ XEvent ev;
+
+ if (type == FLOAT_MENU) {
+ menu = (struct Menu *)obj;
+ button = menu->button;
+ win = menu->obj.win;
+ buttonrightdecorate(button, menu->pixbutton, FOCUSED, 1);
+ } else {
+ row = (struct Row *)obj;
+ button = row->br;
+ win = TAILQ_EMPTY(&row->seltab->dialq) ? row->seltab->obj.win : TAILQ_FIRST(&row->seltab->dialq)->win;
+ buttonrightdecorate(button, row->pixbr, FOCUSED, 1);
+ }
+ if (XGrabPointer(dpy, button, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime) != GrabSuccess)
+ goto done;
+ while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
+ switch(ev.type) {
+ case Expose:
+ if (ev.xexpose.count == 0)
+ copypixmap(ev.xexpose.window);
+ break;
+ case ButtonRelease:
+ if (ev.xbutton.window == button &&
+ ev.xbutton.x >= 0 && ev.xbutton.x >= 0 &&
+ ev.xbutton.x < config.titlewidth && ev.xbutton.x < config.titlewidth)
+ winclose(win);
+ goto done;
+ break;
+ }
+ }
+done:
+ if (type == FLOAT_MENU) {
+ buttonrightdecorate(menu->button, menu->pixbutton, FOCUSED, 0);
+ } else {
+ buttonrightdecorate(row->br, row->pixbr, FOCUSED, 0);
+ }
+ XUngrabPointer(dpy, CurrentTime);
+}
+
+/* resize tiles by dragging division with mouse */
+static void
+mouseretile(struct Container *c, struct Column *cdiv, struct Row *rdiv, int xroot, int yroot)
+{
+ struct Row *row;
+ XEvent ev;
+ Cursor curs;
+ Time lasttime;
+ int x, y;
+ int update;
+
+ if (cdiv != NULL && TAILQ_NEXT(cdiv, entry) != NULL)
+ curs = wm.cursors[CURSOR_H];
+ else if (rdiv != NULL && TAILQ_NEXT(rdiv, entry) != NULL)
+ curs = wm.cursors[CURSOR_V];
+ else
+ return;
+ x = y = 0;
+ update = 0;
+ lasttime = 0;
+ containerdecorate(c, cdiv, rdiv, 0, 0);
+ if (XGrabPointer(dpy, c->frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, curs, CurrentTime) != GrabSuccess)
+ goto done;
+ while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
+ switch (ev.type) {
+ case Expose:
+ if (ev.xexpose.count == 0)
+ copypixmap(ev.xexpose.window);
+ break;
+ case ButtonRelease:
+ goto done;
+ break;
+ case MotionNotify:
+ x = ev.xmotion.x_root - xroot;
+ y = ev.xmotion.y_root - yroot;
+ if (cdiv != NULL) {
+ if (x < 0 && cdiv->w + x >= wm.minsize) {
+ cdiv->w += x;
+ TAILQ_NEXT(cdiv, entry)->w -= x;
+ if (ev.xmotion.time - lasttime > RESIZETIME) {
+ update = 1;
+ }
+ } else if (x > 0 && TAILQ_NEXT(cdiv, entry)->w - x >= wm.minsize) {
+ TAILQ_NEXT(cdiv, entry)->w -= x;
+ cdiv->w += x;
+ if (ev.xmotion.time - lasttime > RESIZETIME) {
+ update = 1;
+ }
+ }
+ }
+ for (row = rdiv; row != NULL && TAILQ_NEXT(row, entry) != NULL; ) {
+ if (y < 0) {
+ if (row->h + y < config.titlewidth) {
+ row = TAILQ_PREV(row, RowQueue, entry);
+ ev.xmotion.y_root -= y;
+ continue;
+ }
+ row->h += y;
+ TAILQ_NEXT(row, entry)->h -= y;
+ if (ev.xmotion.time - lasttime > RESIZETIME) {
+ update = 1;
+ }
+ }
+ if (y > 0) {
+ if (TAILQ_NEXT(row, entry)->h - y < config.titlewidth) {
+ row = TAILQ_NEXT(row, entry);
+ ev.xmotion.y_root -= y;
+ continue;
+ }
+ TAILQ_NEXT(row, entry)->h -= y;
+ row->h += y;
+ if (ev.xmotion.time - lasttime > RESIZETIME) {
+ update = 1;
+ }
+ }
+ break;
+ }
+ if (update) {
+ containercalccols(c, 1, 1);
+ containermoveresize(c);
+ containerdecorate(c, cdiv, rdiv, 0, 0);
+ lasttime = ev.xmotion.time;
+ update = 0;
+ }
+ xroot = ev.xmotion.x_root;
+ yroot = ev.xmotion.y_root;
+ break;
+ }
+ }
+done:
+ containercalccols(c, 1, 1);
+ containermoveresize(c);
+ tabfocus(c->selcol->selrow->seltab, 0);
+ XUngrabPointer(dpy, CurrentTime);
+}
+
+/* stack rows in column with mouse */
+static void
+mousestack(struct Row *row)
+{
+ XEvent ev;
+
+ buttonleftdecorate(row->bl, row->pixbl, FOCUSED, 1);
+ if (XGrabPointer(dpy, row->bl, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime) != GrabSuccess)
+ goto done;
+ while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
+ switch(ev.type) {
+ case Expose:
+ if (ev.xexpose.count == 0)
+ copypixmap(ev.xexpose.window);
+ break;
+ case ButtonRelease:
+ rowstack(row->col, row);
+ goto done;
+ break;
+ }
+ }
+done:
+ tabfocus(row->seltab, 0);
+ buttonleftdecorate(row->bl, row->pixbl, FOCUSED, 0);
+ XUngrabPointer(dpy, CurrentTime);
+}
+
+/* handle mouse operation, focusing and raising */
+static void
+xeventbuttonpress(XEvent *e)
+{
+ struct Object *obj;
+ struct Monitor *mon;
+ struct Container *c;
+ struct Column *cdiv;
+ struct Row *rdiv;
+ struct Tab *tab;
+ struct Menu *menu;
+ enum Octant o;
+ XButtonPressedEvent *ev;
+ Window dw;
+ int x, y;
+
+ ev = &e->xbutton;
+
+ if ((obj = getmanaged(ev->window)) == NULL) {
+ /* if user clicked in no window, focus the monitor below cursor */
+ if ((mon = getmon(ev->x_root, ev->y_root)) != NULL)
+ deskfocus(mon, mon->seldesk, 1);
+ goto done;
+ }
+
+ menu = NULL;
+ tab = NULL;
+ switch (obj->type) {
+ case TYPE_NORMAL:
+ tab = (struct Tab *)obj;
+ c = tab->row->col->c;
+ break;
+ case TYPE_DIALOG:
+ tab = ((struct Dialog *)obj)->tab;
+ c = tab->row->col->c;
+ break;
+ case TYPE_MENU:
+ menu = (struct Menu *)obj;
+ tab = menu->tab;
+ c = tab->row->col->c;
+ break;
+ default:
+ if ((mon = getmon(ev->x_root, ev->y_root)) != NULL)
+ deskfocus(mon, mon->seldesk, 1);
+ goto done;
+ }
+
+ /* raise menu above others or focus tab */
+ if (menu != NULL)
+ menuaddraise(tab, menu);
+ else if ((wm.focused == NULL || tab != wm.focused->selcol->selrow->seltab) && ev->button == Button1)
+ tabfocus(tab, 1);
+
+ /* raise client */
+ if (ev->button == Button1)
+ containerraise(c, c->isfullscreen, c->layer);
+
+ /* get pointer position */
+ if (!XTranslateCoordinates(dpy, ev->window, c->frame, ev->x, ev->y, &x, &y, &dw))
+ goto done;
+
+ /* do action performed by mouse */
+ if (menu != NULL) {
+ if (ev->window == menu->titlebar && ev->button == Button1) {
+ mousemove(FLOAT_MENU, menu, ev->x_root, ev->y_root, 1);
+ } else if (ev->window == menu->button && ev->button == Button1) {
+ mouseclose(FLOAT_MENU, menu);
+ } else if ((ev->window == menu->frame && ev->button == Button3)
+ || (ev->state == config.modifier && ev->button == Button1)) {
+ mousemove(FLOAT_MENU, menu, ev->x_root, ev->y_root, 0);
+ } else if (ev->state == config.modifier && ev->button == Button3) {
+ o = getquadrant(menu->w, menu->h, x, y);
+ mouseresize(FLOAT_MENU, menu, ev->x_root, ev->y_root, o);
+ }
+ } else {
+ o = getframehandle(c->w, c->h, x, y);
+ if (ev->window == tab->title && ev->button == Button3) {
+ mouseretab(tab, ev->x_root, ev->y_root, ev->x, ev->y);
+ } else if (ev->window == tab->row->bl && ev->button == Button1) {
+ mousestack(tab->row);
+ } else if (ev->window == tab->row->br && ev->button == Button1) {
+ mouseclose(FLOAT_CONTAINER, tab->row);
+ } else if (ev->window == c->frame && ev->button == Button1 && o == C) {
+ getdivisions(c, &cdiv, &rdiv, x, y);
+ if (cdiv != NULL || rdiv != NULL) {
+ mouseretile(c, cdiv, rdiv, ev->x_root, ev->y_root);
+ }
+ } else if (!c->isfullscreen && !c->isminimized && !c->ismaximized) {
+ if (ev->state == config.modifier && ev->button == Button1) {
+ mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, 0);
+ } else if (ev->window == c->frame && ev->button == Button3) {
+ mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
+ } else if ((ev->state == config.modifier && ev->button == Button3) ||
+ (o != C && ev->window == c->frame && ev->button == Button1)) {
+ if (o == C)
+ o = getquadrant(c->w, c->h, x, y);
+ mouseresize(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
+ } else if (ev->window == tab->title && ev->button == Button1) {
+ tabdecorate(tab, 1);
+ mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, 0);
+ tabdecorate(tab, 0);
+ }
+ }
+ }
+
+done:
+ XAllowEvents(dpy, ReplayPointer, CurrentTime);
+}
+
+/* handle client message event */
+static void
+xeventclientmessage(XEvent *e)
+{
+ struct Container *c = NULL;
+ struct Tab *tab = NULL;
+ struct Menu *menu = NULL;
+ struct Object *obj;
+ XClientMessageEvent *ev;
+ XWindowChanges wc;
+ unsigned value_mask = 0;
+ int floattype;
+ void *p;
+
+ ev = &e->xclient;
+ if ((obj = getmanaged(ev->window)) != NULL) {
+ switch (obj->type) {
+ case TYPE_NORMAL:
+ tab = (struct Tab *)obj;
+ c = tab->row->col->c;
+ break;
+ case TYPE_DIALOG:
+ tab = ((struct Dialog *)obj)->tab;
+ c = tab->row->col->c;
+ break;
+ case TYPE_MENU:
+ menu = (struct Menu *)obj;
+ tab = menu->tab;
+ c = tab->row->col->c;
+ break;
+ }
+ }
+ if (ev->message_type == atoms[_NET_CURRENT_DESKTOP]) {
+ deskfocus(wm.selmon, ev->data.l[0], 1);
+ } else if (ev->message_type == atoms[_NET_SHOWING_DESKTOP]) {
+ if (ev->data.l[0]) {
+ deskshow(1);
+ } else {
+ deskfocus(wm.selmon, wm.selmon->seldesk, 1);
+ }
+ } else if (ev->message_type == atoms[_NET_WM_STATE]) {
+ if (obj == NULL || obj->type != TYPE_NORMAL)
+ return;
+ containersetstate(tab, (Atom *)(ev->data.l + 1), ev->data.l[0]);
+ } else if (ev->message_type == atoms[_NET_ACTIVE_WINDOW]) {
+#define ACTIVATECOL(col) { if ((col) != NULL) tabfocus((col)->selrow->seltab, 1); }
+#define ACTIVATEROW(row) { if ((row) != NULL) tabfocus((row)->seltab, 1); }
+ if (tab == NULL && wm.focused != NULL) {
+ c = wm.focused;
+ tab = wm.focused->selcol->selrow->seltab;
+ }
+ if (tab == NULL)
+ return;
+ switch (ev->data.l[3]) {
+ case _SHOD_FOCUS_LEFT_CONTAINER:
+ case _SHOD_FOCUS_RIGHT_CONTAINER:
+ case _SHOD_FOCUS_TOP_CONTAINER:
+ case _SHOD_FOCUS_BOTTOM_CONTAINER:
+ // removed
+ break;
+ case _SHOD_FOCUS_PREVIOUS_CONTAINER:
+ alttab(1);
+ break;
+ case _SHOD_FOCUS_NEXT_CONTAINER:
+ alttab(0);
+ break;
+ case _SHOD_FOCUS_LEFT_WINDOW:
+ ACTIVATECOL(TAILQ_PREV(tab->row->col, ColumnQueue, entry))
+ break;
+ case _SHOD_FOCUS_RIGHT_WINDOW:
+ ACTIVATECOL(TAILQ_NEXT(tab->row->col, entry))
+ break;
+ case _SHOD_FOCUS_TOP_WINDOW:
+ ACTIVATEROW(TAILQ_PREV(tab->row, RowQueue, entry))
+ break;
+ case _SHOD_FOCUS_BOTTOM_WINDOW:
+ ACTIVATEROW(TAILQ_NEXT(tab->row, entry))
+ break;
+ case _SHOD_FOCUS_PREVIOUS_WINDOW:
+ obj = (struct Object *)tab;
+ if (TAILQ_PREV(obj, Queue, entry) != NULL)
+ tabfocus((struct Tab *)TAILQ_PREV(obj, Queue, entry), 1);
+ else
+ tabfocus((struct Tab *)TAILQ_LAST(&tab->row->tabq, Queue), 1);
+ break;
+ case _SHOD_FOCUS_NEXT_WINDOW:
+ obj = (struct Object *)tab;
+ if (TAILQ_NEXT(obj, entry) != NULL)
+ tabfocus((struct Tab *)TAILQ_NEXT(obj, entry), 1);
+ else
+ tabfocus((struct Tab *)TAILQ_FIRST(&tab->row->tabq), 1);
+ break;
+ default:
+ tabfocus(tab, 1);
+ break;
+ }
+ } else if (ev->message_type == atoms[_NET_CLOSE_WINDOW]) {
+ winclose(ev->window);
+ } else if (ev->message_type == atoms[_NET_MOVERESIZE_WINDOW]) {
+ if (c == NULL)
+ return;
+ value_mask = CWX | CWY | CWWidth | CWHeight;
+ wc.x = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->x + ev->data.l[1] : ev->data.l[1];
+ wc.y = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->y + ev->data.l[2] : ev->data.l[2];
+ wc.width = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->w + ev->data.l[3] : ev->data.l[3];
+ wc.height = (ev->data.l[0] & _SHOD_MOVERESIZE_RELATIVE) ? c->h + ev->data.l[4] : ev->data.l[4];
+ if (obj->type == TYPE_DIALOG) {
+ dialogconfigure((struct Dialog *)obj, value_mask, &wc);
+ } else if (obj->type == TYPE_NORMAL) {
+ containerconfigure(c, value_mask, &wc);
+ }
+ } else if (ev->message_type == atoms[_NET_WM_DESKTOP]) {
+ if (obj == NULL || obj->type != TYPE_NORMAL)
+ return;
+ containersendtodeskandfocus(c, c->mon, ev->data.l[0]);
+ } else if (ev->message_type == atoms[_NET_REQUEST_FRAME_EXTENTS]) {
+ if (c == NULL) {
+ /*
+ * A client can request an estimate for the frame size
+ * which the window manager will put around it before
+ * actually mapping its window. Java does this (as of
+ * openjdk-7).
+ */
+ ewmhsetframeextents(ev->window, config.borderwidth, config.titlewidth);
+ } else {
+ ewmhsetframeextents(ev->window, c->b, (obj->type == TYPE_DIALOG ? 0 : TITLEWIDTH(c)));
+ }
+ } else if (ev->message_type == atoms[_NET_WM_MOVERESIZE]) {
+ /*
+ * Client-side decorated Gtk3 windows emit this signal when being
+ * dragged by their GtkHeaderBar
+ */
+ if (obj == NULL || (obj->type != TYPE_NORMAL && obj->type != TYPE_MENU))
+ return;
+ if (menu != NULL) {
+ p = menu;
+ floattype = FLOAT_MENU;
+ } else {
+ p = c;
+ floattype = FLOAT_CONTAINER;
+ }
+ switch (ev->data.l[2]) {
+ case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], NW);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOP:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], N);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], NE);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_RIGHT:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], E);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], SE);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], S);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], SW);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_LEFT:
+ mouseresize(floattype, p, ev->data.l[0], ev->data.l[1], W);
+ break;
+ case _NET_WM_MOVERESIZE_MOVE:
+ mousemove(floattype, p, ev->data.l[0], ev->data.l[1], C);
+ break;
+ default:
+ XUngrabPointer(dpy, CurrentTime);
+ break;
+ }
+ }
+}
+
+/* handle configure notify event */
+static void
+xeventconfigurenotify(XEvent *e)
+{
+ XConfigureEvent *ev;
+
+ ev = &e->xconfigure;
+ if (ev->window == root) {
+ monupdate();
+ notifplace();
+ }
+}
+
+/* handle configure request event */
+static void
+xeventconfigurerequest(XEvent *e)
+{
+ XConfigureRequestEvent *ev;
+ XWindowChanges wc;
+ struct Object *obj;
+
+ 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;
+ obj = getmanaged(ev->window);
+ if (obj == NULL) {
+ XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
+ } else if (obj->type == TYPE_DIALOG) {
+ dialogconfigure((struct Dialog *)obj, ev->value_mask, &wc);
+ } else if (obj->type == TYPE_MENU) {
+ menuconfigure((struct Menu *)obj, ev->value_mask, &wc);
+ } else if (obj->type == TYPE_NORMAL) {
+ if (config.honorconfig) {
+ containerconfigure(((struct Tab *)obj)->row->col->c, ev->value_mask, &wc);
+ } else {
+ containermoveresize(((struct Tab *)obj)->row->col->c);
+ }
+ }
+}
+
+/* forget about client */
+static void
+xeventdestroynotify(XEvent *e)
+{
+ XDestroyWindowEvent *ev;
+ struct Object *obj;
+
+ ev = &e->xdestroywindow;
+ if ((obj = getmanaged(ev->window)) != NULL) {
+ if (obj->win == ev->window && (*unmanagefuncs[obj->type])(obj, 0)) {
+ ewmhsetclients();
+ ewmhsetclientsstacking();
+ }
+ }
+}
+
+/* focus window when cursor enter it (only if there is no focus button) */
+static void
+xevententernotify(XEvent *e)
+{
+ struct Object *obj;
+
+ if (!config.sloppyfocus)
+ return;
+ while (XCheckTypedEvent(dpy, EnterNotify, e))
+ ;
+ if ((obj = getmanaged(e->xcrossing.window)) == NULL)
+ return;
+ switch (obj->type) {
+ case TYPE_MENU:
+ tabfocus(((struct Menu *)obj)->tab, 1);
+ break;
+ case TYPE_DIALOG:
+ tabfocus(((struct Dialog *)obj)->tab, 1);
+ break;
+ case TYPE_NORMAL:
+ tabfocus((struct Tab *)obj, 1);
+ break;
+ }
+}
+
+/* redraw window decoration */
+static void
+xeventexpose(XEvent *e)
+{
+ XExposeEvent *ev;
+
+ ev = &e->xexpose;
+ if (ev->count == 0) {
+ copypixmap(ev->window);
+ }
+}
+
+/* handle focusin event */
+static void
+xeventfocusin(XEvent *e)
+{
+ XFocusChangeEvent *ev;
+ struct Object *obj;
+
+ ev = &e->xfocus;
+ if (wm.focused == NULL) {
+ tabfocus(NULL, 0);
+ return;
+ }
+ obj = getmanaged(ev->window);
+ if (obj == NULL)
+ goto focus;
+ switch (obj->type) {
+ case TYPE_MENU:
+ if (((struct Menu *)obj)->tab != wm.focused->selcol->selrow->seltab)
+ goto focus;
+ break;
+ case TYPE_DIALOG:
+ if (((struct Dialog *)obj)->tab != wm.focused->selcol->selrow->seltab)
+ goto focus;
+ break;
+ case TYPE_NORMAL:
+ if ((struct Tab *)obj != wm.focused->selcol->selrow->seltab)
+ goto focus;
+ break;
+ }
+ return;
+focus:
+ 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,
+ (XRectangle){
+ .x = wa.x,
+ .y = wa.y,
+ .width = wa.width,
+ .height = wa.height,
+ },
+ 0
+ );
+}
+
+/* update client properties */
+static void
+xeventpropertynotify(XEvent *e)
+{
+ XPropertyEvent *ev;
+ struct Object *obj;
+ struct Tab *tab;
+ struct Menu *menu;
+
+ ev = &e->xproperty;
+ if (ev->state == PropertyDelete)
+ return;
+ obj = getmanaged(ev->window);
+ if (obj == NULL)
+ return;
+ if (obj->type == TYPE_NORMAL && ev->window == obj->win) {
+ tab = (struct Tab *)obj;
+ if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) {
+ winupdatetitle(tab->obj.win, &tab->name);
+ tabdecorate(tab, 0);
+ } else if (ev->atom == XA_WM_HINTS) {
+ tabupdateurgency(tab, getwinurgency(tab->obj.win));
+ }
+ } else if (obj->type == TYPE_DOCK && (ev->atom == _NET_WM_STRUT_PARTIAL || ev->atom == _NET_WM_STRUT)) {
+ barstrut((struct Bar *)obj);
+ monupdatearea();
+ } else if (obj->type == TYPE_MENU && ev->window == obj->win) {
+ menu = (struct Menu *)obj;
+ if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) {
+ winupdatetitle(menu->obj.win, &menu->name);
+ menudecorate(menu, 0);
+ }
+ }
+}
+
+/* forget about client */
+static void
+xeventunmapnotify(XEvent *e)
+{
+ XUnmapEvent *ev;
+ struct Object *obj;
+
+ ev = &e->xunmap;
+ if ((obj = getmanaged(ev->window)) != NULL) {
+ if (obj->win == ev->window && (*unmanagefuncs[obj->type])(obj, 1)) {
+ ewmhsetclients();
+ ewmhsetclientsstacking();
+ }
+ }
+}
+
+/* scan for already existing windows and adopt them */
+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],
+ (XRectangle){
+ .x = wa.x,
+ .y = wa.y,
+ .width = wa.width,
+ .height = wa.height,
+ },
+ 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],
+ (XRectangle){
+ .x = wa.x,
+ .y = wa.y,
+ .width = wa.width,
+ .height = wa.height,
+ },
+ IGNOREUNMAP
+ );
+ }
+ }
+ if (wins != NULL) {
+ XFree(wins);
+ }
+ }
+}
+
+void (*xevents[LASTEvent])(XEvent *) = {
+ [ButtonPress] = xeventbuttonpress,
+ [ClientMessage] = xeventclientmessage,
+ [ConfigureNotify] = xeventconfigurenotify,
+ [ConfigureRequest] = xeventconfigurerequest,
+ [DestroyNotify] = xeventdestroynotify,
+ [EnterNotify] = xevententernotify,
+ [Expose] = xeventexpose,
+ [FocusIn] = xeventfocusin,
+ [MapRequest] = xeventmaprequest,
+ [PropertyNotify] = xeventpropertynotify,
+ [UnmapNotify] = xeventunmapnotify
+};
diff --git a/xhints.c b/xhints.c
@@ -0,0 +1,295 @@
+#include "shod.h"
+
+#define LOOPSTACKING(array, list, index) { \
+ struct Container *c; \
+ struct Column *col; \
+ struct Row *row; \
+ struct Object *p; \
+ struct Tab *t; \
+ \
+ TAILQ_FOREACH(c, &(list), raiseentry) { \
+ TAILQ_FOREACH(col, &c->colq, entry) { \
+ if (col->selrow->seltab != NULL) \
+ (array)[--(index)] = col->selrow->seltab->obj.win; \
+ TAILQ_FOREACH(p, &col->selrow->tabq, entry) { \
+ t = (struct Tab *)p; \
+ if (t != col->selrow->seltab) { \
+ (array)[--(index)] = t->obj.win; \
+ } \
+ } \
+ TAILQ_FOREACH(row, &col->rowq, entry) { \
+ if (row == col->selrow) \
+ continue; \
+ if (row->seltab != NULL) \
+ (array)[--(index)] = row->seltab->obj.win; \
+ TAILQ_FOREACH(p, &row->tabq, entry) { \
+ t = (struct Tab *)p; \
+ if (t != row->seltab) { \
+ (array)[--(index)] = t->obj.win; \
+ } \
+ } \
+ } \
+ } \
+ } \
+}
+
+/* get window name */
+static 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;
+}
+
+/* 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);
+}
+
+/* initialize ewmh hints */
+void
+ewmhinit(const char *wmname)
+{
+ /* 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, strlen(wmname));
+ 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 */
+void
+ewmhsetcurrentdesktop(unsigned long n)
+{
+ XChangeProperty(dpy, root, atoms[_NET_CURRENT_DESKTOP], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&n, 1);
+}
+
+/* set showing desktop hint */
+void
+ewmhsetshowingdesktop(int n)
+{
+ XChangeProperty(dpy, root, atoms[_NET_SHOWING_DESKTOP], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&n, 1);
+}
+
+/* set list of clients hint */
+void
+ewmhsetclients(void)
+{
+ struct Object *tab;
+ struct Container *c;
+ Window *wins = NULL;
+ int i = 0;
+
+ if (wm.nclients < 1) {
+ XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST], XA_WINDOW, 32, PropModeReplace, NULL, 0);
+ return;
+ }
+ wins = ecalloc(wm.nclients, sizeof *wins);
+ TAILQ_FOREACH(c, &wm.focusq, entry) {
+ TAB_FOREACH_BEGIN(c, tab){
+ wins[i++] = tab->win;
+ }TAB_FOREACH_END
+ }
+ XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST], XA_WINDOW, 32, PropModeReplace, (unsigned char *)wins, i);
+ free(wins);
+}
+
+/* set stacking list of clients hint */
+void
+ewmhsetclientsstacking(void)
+{
+ Window *wins = NULL;
+ int i = 0;
+
+ if (wm.nclients < 1) {
+ XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST_STACKING], XA_WINDOW, 32, PropModeReplace, NULL, 0);
+ return;
+ }
+ wins = ecalloc(wm.nclients, sizeof *wins);
+ i = wm.nclients;
+ LOOPSTACKING(wins, wm.fullq, i)
+ LOOPSTACKING(wins, wm.aboveq, i)
+ LOOPSTACKING(wins, wm.centerq, i)
+ LOOPSTACKING(wins, wm.belowq, i)
+ XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST_STACKING], XA_WINDOW, 32, PropModeReplace, (unsigned char *)wins+i, wm.nclients-i);
+ free(wins);
+}
+
+/* set active window hint */
+void
+ewmhsetactivewindow(Window w)
+{
+ XChangeProperty(dpy, root, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1);
+}
+
+/* set desktop for all windows in a container */
+void
+ewmhsetwmdesktop(struct Container *c)
+{
+ struct Object *t;
+ unsigned long n;
+
+ n = (c->issticky || c->isminimized) ? 0xFFFFFFFF : (unsigned long)c->desk;
+ TAB_FOREACH_BEGIN(c, t){
+ ewmhsetdesktop(t->win, n);
+ }TAB_FOREACH_END
+}
+
+/* set frames of window */
+void
+ewmhsetframeextents(Window win, int b, int t)
+{
+ unsigned long data[4];
+
+ data[0] = data[1] = data[3] = b;
+ data[2] = b + t;
+ XChangeProperty(dpy, win, atoms[_NET_FRAME_EXTENTS], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 4);
+}
+
+/* set state of windows */
+void
+ewmhsetstate(struct Container *c)
+{
+ struct Object *t;
+ Atom data[9];
+ int n = 0;
+
+ if (c == NULL)
+ return;
+ if (c == wm.focused)
+ data[n++] = atoms[_NET_WM_STATE_FOCUSED];
+ if (c->isfullscreen)
+ data[n++] = atoms[_NET_WM_STATE_FULLSCREEN];
+ if (c->issticky)
+ data[n++] = atoms[_NET_WM_STATE_STICKY];
+ if (c->isshaded)
+ data[n++] = atoms[_NET_WM_STATE_SHADED];
+ if (c->isminimized)
+ data[n++] = atoms[_NET_WM_STATE_HIDDEN];
+ if (c->ismaximized) {
+ data[n++] = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
+ data[n++] = atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
+ }
+ if (c->layer > 0)
+ data[n++] = atoms[_NET_WM_STATE_ABOVE];
+ else if (c->layer < 0)
+ data[n++] = atoms[_NET_WM_STATE_BELOW];
+ TAB_FOREACH_BEGIN(c, t){
+ XChangeProperty(dpy, t->win, atoms[_NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char *)data, n);
+ }TAB_FOREACH_END
+}
+
+/* set icccm wmstate */
+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 */
+void
+icccmdeletestate(Window win)
+{
+ XDeleteProperty(dpy, win, atoms[WM_STATE]);
+}
+
+/* set group of windows in client */
+void
+shodgrouptab(struct Container *c)
+{
+ struct Object *t;
+
+ TAB_FOREACH_BEGIN(c, t){
+ XChangeProperty(dpy, t->win, atoms[_SHOD_GROUP_TAB], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&row->seltab->obj.win, 1);
+ }TAB_FOREACH_END
+}
+
+/* set group of windows in client */
+void
+shodgroupcontainer(struct Container *c)
+{
+ struct Object *t;
+
+ TAB_FOREACH_BEGIN(c, t){
+ XChangeProperty(dpy, t->win, atoms[_SHOD_GROUP_CONTAINER], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&c->selcol->selrow->seltab->obj.win, 1);
+ }TAB_FOREACH_END
+}
+
+/* update tab title */
+void
+winupdatetitle(Window win, char **name)
+{
+ free(*name);
+ *name = getwinname(win);
+}
+
+/* notify window of configuration changing */
+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);
+}
+
+/* send a WM_DELETE message to client */
+void
+winclose(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);
+}
diff --git a/xmon.c b/xmon.c
@@ -0,0 +1,257 @@
+#include "shod.h"
+
+#include <X11/extensions/Xinerama.h>
+
+/* 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;
+
+ mon = emalloc(sizeof *mon);
+ *mon = (struct Monitor){
+ .mx = info->x_org,
+ .my = info->y_org,
+ .mw = info->width,
+ .mh = info->height,
+ .wx = info->x_org,
+ .wy = info->y_org,
+ .ww = info->width,
+ .wh = info->height,
+ };
+ mon->seldesk = 0;
+ TAILQ_INSERT_TAIL(&wm.monq, mon, entry);
+}
+
+/* delete monitor and set monitor of clients on it to NULL */
+void
+mondel(struct Monitor *mon)
+{
+ struct Container *c;
+
+ TAILQ_REMOVE(&wm.monq, mon, entry);
+ TAILQ_FOREACH(c, &wm.focusq, entry)
+ if (c->mon == mon)
+ c->mon = NULL;
+ free(mon);
+}
+