shod

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

commit 4415c1b736c48e09387811d0042e2414a9d7653f
parent 19fd9059b377dae9c1266ab18c9bb502c7988dd8
Author: seninha <lucas@seninha.org>
Date:   Sat, 10 Sep 2022 19:17:35 -0300

split code; refactor; etc

Diffstat:
MMakefile | 37+++++++++++++++++++++++++------------
Aconfig.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dconfig.h | 63---------------------------------------------------------------
Mshod.c | 6431++-----------------------------------------------------------------------------
Ashod.h | 733+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mshodc.c | 181++++++-------------------------------------------------------------------------
Axapp.c | 2106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axbar.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axdock.c | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axdraw.c | 752+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axevents.c | 1627+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axhints.c | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axmon.c | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axnotif.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axprompt.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axsplash.c | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axutil.c | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axutil.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); +} +