aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.in38
-rw-r--r--src/border.c753
-rw-r--r--src/border.h80
-rw-r--r--src/button.c212
-rw-r--r--src/button.h63
-rw-r--r--src/client.c1423
-rw-r--r--src/client.h285
-rw-r--r--src/clock.c332
-rw-r--r--src/clock.h41
-rw-r--r--src/color.c606
-rw-r--r--src/color.h91
-rw-r--r--src/command.c124
-rw-r--r--src/command.h36
-rw-r--r--src/confirm.c412
-rw-r--r--src/confirm.h36
-rw-r--r--src/cursor.c313
-rw-r--r--src/cursor.h44
-rw-r--r--src/debug.c396
-rw-r--r--src/debug.h104
-rw-r--r--src/desktop.c239
-rw-r--r--src/desktop.h60
-rw-r--r--src/dock.c602
-rw-r--r--src/dock.h43
-rw-r--r--src/error.c108
-rw-r--r--src/error.h38
-rw-r--r--src/event.c1138
-rw-r--r--src/event.h28
-rw-r--r--src/font.c295
-rw-r--r--src/font.h43
-rw-r--r--src/group.c299
-rw-r--r--src/group.h48
-rw-r--r--src/help.c84
-rw-r--r--src/help.h26
-rw-r--r--src/hint.c970
-rw-r--r--src/hint.h173
-rw-r--r--src/icon.c726
-rw-r--r--src/icon.h110
-rw-r--r--src/image.c362
-rw-r--r--src/image.h47
-rw-r--r--src/jwm.h128
-rw-r--r--src/jxlib.h420
-rw-r--r--src/key.c452
-rw-r--r--src/key.h57
-rw-r--r--src/lex.c572
-rw-r--r--src/lex.h115
-rw-r--r--src/main.c507
-rw-r--r--src/main.h56
-rw-r--r--src/match.c80
-rw-r--r--src/match.h21
-rw-r--r--src/menu.c799
-rw-r--r--src/menu.h89
-rw-r--r--src/misc.c196
-rw-r--r--src/misc.h37
-rw-r--r--src/move.c729
-rw-r--r--src/move.h61
-rw-r--r--src/outline.c73
-rw-r--r--src/outline.h32
-rw-r--r--src/pager.c331
-rw-r--r--src/pager.h27
-rw-r--r--src/parse.c1551
-rw-r--r--src/parse.h19
-rw-r--r--src/place.c647
-rw-r--r--src/place.h34
-rw-r--r--src/popup.c232
-rw-r--r--src/popup.h35
-rw-r--r--src/render.c226
-rw-r--r--src/render.h25
-rw-r--r--src/resize.c491
-rw-r--r--src/resize.h42
-rw-r--r--src/root.c315
-rw-r--r--src/root.h65
-rw-r--r--src/screen.c138
-rw-r--r--src/screen.h53
-rw-r--r--src/status.c274
-rw-r--r--src/status.h55
-rw-r--r--src/swallow.c274
-rw-r--r--src/swallow.h43
-rw-r--r--src/taskbar.c936
-rw-r--r--src/taskbar.h64
-rw-r--r--src/theme.c88
-rw-r--r--src/theme.h31
-rw-r--r--src/timing.c71
-rw-r--r--src/timing.h43
-rw-r--r--src/tray.c1104
-rw-r--r--src/tray.h214
-rw-r--r--src/traybutton.c454
-rw-r--r--src/traybutton.h51
-rw-r--r--src/winmenu.c327
-rw-r--r--src/winmenu.h37
-rw-r--r--src/x.xpm32
90 files changed, 23581 insertions, 0 deletions
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..8b73c8d
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,38 @@
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+BINDIR = @BINDIR@
+
+VPATH=.:os
+
+OBJECTS = border.o button.o client.o clock.o color.o command.o confirm.o \
+ cursor.o debug.o desktop.o dock.o event.o error.o font.o group.o help.o \
+ hint.o icon.o image.o key.o lex.o main.o match.o menu.o misc.o move.o \
+ outline.o pager.o parse.o place.o popup.o render.o resize.o root.o \
+ screen.o status.o swallow.o taskbar.o theme.o timing.o tray.o \
+ traybutton.o winmenu.o
+
+EXE = jwm
+
+.SUFFIXES: .o .h .c
+
+all: $(EXE)
+
+install: all
+ strip $(EXE)
+ install -d $(BINDIR)
+ install $(EXE) $(BINDIR)/$(EXE)
+
+depend:
+ makedepend -m -- $(CFLAGS) -- *.c
+
+$(EXE): $(OBJECTS)
+ $(CC) -o $(EXE) $(OBJECTS) $(LDFLAGS)
+
+.c.o:
+ $(CC) -c $(CFLAGS) $<
+
+clean:
+ rm -f $(OBJECTS) $(EXE) core
+
diff --git a/src/border.c b/src/border.c
new file mode 100644
index 0000000..8baf7b7
--- /dev/null
+++ b/src/border.c
@@ -0,0 +1,753 @@
+/****************************************************************************
+ * Functions for dealing with window borders.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "border.h"
+#include "client.h"
+#include "color.h"
+#include "main.h"
+#include "icon.h"
+#include "font.h"
+#include "error.h"
+
+typedef enum {
+ BP_CLOSE,
+ BP_ACTIVE_CLOSE,
+ BP_MINIMIZE,
+ BP_ACTIVE_MINIMIZE,
+ BP_MAXIMIZE,
+ BP_ACTIVE_MAXIMIZE,
+ BP_MAXIMIZE_ACTIVE,
+ BP_ACTIVE_MAXIMIZE_ACTIVE,
+ BP_COUNT
+} BorderPixmapType;
+
+typedef unsigned char BorderPixmapDataType[32];
+
+static BorderPixmapDataType bitmaps[BP_COUNT >> 1] = {
+
+ /* Close */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x38, 0x38, 0x70, 0x1C,
+ 0xE0, 0x0E, 0xC0, 0x07, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x0E, 0x70, 0x1C,
+ 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 },
+
+ /* Minimize */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07,
+ 0xF8, 0x07, 0xF8, 0x07, 0x00, 0x00, 0x00, 0x00 },
+
+ /* Maximize */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0xF8, 0x3F, 0xF8, 0x3F,
+ 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20,
+ 0x08, 0x20, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00 },
+
+ /* Maximize Active */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0xC0, 0x1F,
+ 0x00, 0x10, 0xF8, 0x13, 0xF8, 0x13, 0x08, 0x12, 0x08, 0x1A, 0x08, 0x02,
+ 0x08, 0x02, 0xF8, 0x03, 0x00, 0x00, 0x00, 0x00 }
+
+};
+
+static Pixmap pixmaps[BP_COUNT];
+
+static Region borderRegion = NULL;
+static GC borderGC;
+
+static void DrawBorderHelper(const ClientNode *np,
+ unsigned int width, unsigned int height, int drawIcon);
+static void DrawButtonBorder(const ClientNode *np, int offset,
+ Pixmap canvas, GC gc);
+static int DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc);
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeBorders() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupBorders() {
+
+ XGCValues gcValues;
+ unsigned long gcMask;
+ long fg, bg;
+ int x;
+
+ for(x = 0; x < BP_COUNT; x++) {
+
+ if(x & 1) {
+ fg = colors[COLOR_BORDER_ACTIVE_FG];
+ bg = colors[COLOR_BORDER_ACTIVE_BG];
+ } else {
+ fg = colors[COLOR_BORDER_FG];
+ bg = colors[COLOR_BORDER_BG];
+ }
+
+ pixmaps[x] = JXCreatePixmapFromBitmapData(display, rootWindow,
+ (char*)bitmaps[x >> 1], 16, 16, fg, bg, rootDepth);
+
+ }
+
+ gcMask = GCGraphicsExposures;
+ gcValues.graphics_exposures = False;
+ borderGC = JXCreateGC(display, rootWindow, gcMask, &gcValues);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownBorders() {
+
+ int x;
+
+ JXFreeGC(display, borderGC);
+
+ for(x = 0; x < BP_COUNT; x++) {
+ JXFreePixmap(display, pixmaps[x]);
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyBorders() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GetBorderIconSize() {
+ return titleHeight - 4;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+BorderActionType GetBorderActionType(const ClientNode *np, int x, int y) {
+
+ int north;
+ int offset;
+ int height, width;
+ int bsize;
+
+ Assert(np);
+
+ if(np->state.border & BORDER_OUTLINE) {
+ bsize = borderWidth;
+ } else {
+ bsize = 0;
+ }
+
+ if(np->state.border & BORDER_TITLE) {
+
+ if(y > bsize && y <= bsize + titleHeight) {
+ if(np->icon && np->width >= titleHeight) {
+ if(x > bsize && x < bsize + titleHeight) {
+ return BA_MENU;
+ }
+ }
+ offset = np->width + bsize - titleHeight;
+ if((np->state.border & BORDER_CLOSE)
+ && offset > bsize + titleHeight) {
+ if(x > offset && x < offset + titleHeight) {
+ return BA_CLOSE;
+ }
+ offset -= titleHeight;
+ }
+ if((np->state.border & BORDER_MAX)
+ && offset > bsize + titleHeight) {
+ if(x > offset && x < offset + titleHeight) {
+ return BA_MAXIMIZE;
+ }
+ offset -= titleHeight;
+ }
+ if((np->state.border & BORDER_MIN) && offset > bsize + titleHeight) {
+ if(x > offset && x < offset + titleHeight) {
+ return BA_MINIMIZE;
+ }
+ }
+ }
+
+ if(y >= bsize && y <= bsize + titleHeight) {
+ if(x >= bsize && x <= np->width + bsize) {
+ if(np->state.border & BORDER_MOVE) {
+ return BA_MOVE;
+ } else {
+ return BA_NONE;
+ }
+ }
+ }
+
+ north = bsize + titleHeight;
+ } else {
+ north = bsize;
+ }
+
+ if(!(np->state.border & BORDER_RESIZE)) {
+ return BA_NONE;
+ }
+
+ width = np->width;
+
+ if(np->state.status & STAT_SHADED) {
+ if(x < bsize) {
+ return BA_RESIZE_W | BA_RESIZE;
+ } else if(x >= width + bsize) {
+ return BA_RESIZE_E | BA_RESIZE;
+ } else {
+ return BA_NONE;
+ }
+ }
+
+ height = np->height;
+
+ if(width >= titleHeight * 2 && height >= titleHeight * 2) {
+ if(x < bsize + titleHeight && y < titleHeight + bsize) {
+ return BA_RESIZE_N | BA_RESIZE_W | BA_RESIZE;
+ } else if(x < titleHeight + bsize
+ && y - north >= height - titleHeight) {
+ return BA_RESIZE_S | BA_RESIZE_W | BA_RESIZE;
+ } else if(x - bsize >= width - titleHeight
+ && y < titleHeight + bsize) {
+ return BA_RESIZE_N | BA_RESIZE_E | BA_RESIZE;
+ } else if(x - bsize >= width - titleHeight
+ && y - north >= height - titleHeight) {
+ return BA_RESIZE_S | BA_RESIZE_E | BA_RESIZE;
+ }
+ }
+ if(x < bsize) {
+ return BA_RESIZE_W | BA_RESIZE;
+ } else if(x >= width + bsize) {
+ return BA_RESIZE_E | BA_RESIZE;
+ } else if(y < bsize) {
+ return BA_RESIZE_N | BA_RESIZE;
+ } else if(y >= height) {
+ return BA_RESIZE_S | BA_RESIZE;
+ } else {
+ return BA_NONE;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DrawBorder(const ClientNode *np, const XExposeEvent *expose) {
+
+ XRectangle rect;
+ unsigned int width;
+ unsigned int height;
+ int bsize;
+ int drawIcon;
+ int temp;
+
+ Assert(np);
+
+ if(shouldExit) {
+ return;
+ }
+
+ if(!(np->state.status & (STAT_MAPPED | STAT_SHADED))) {
+ return;
+ }
+ if(np->state.status & (STAT_HIDDEN | STAT_FULLSCREEN)) {
+ return;
+ }
+
+ if(np->state.border & BORDER_OUTLINE) {
+ bsize = borderWidth;
+ } else {
+ bsize = 0;
+ }
+
+ if(bsize == 0 && !(np->state.border & BORDER_TITLE)) {
+ return;
+ }
+
+ if(expose) {
+
+ if(!borderRegion) {
+ borderRegion = XCreateRegion();
+ }
+
+ rect.x = (short)expose->x;
+ rect.y = (short)expose->y;
+ rect.width = (unsigned short)expose->width;
+ rect.height = (unsigned short)expose->height;
+ XUnionRectWithRegion(&rect, borderRegion, borderRegion);
+
+ if(expose->count) {
+ return;
+ }
+
+ /* Determine if the icon should be redrawn. This is needed
+ * since icons need a separate GC for applying shape masks.
+ * Note that if the icon were naively redrawn, icons with
+ * alpha channels would acquire artifacts since the area under
+ * them would not be cleared. So if any part of the icon needs
+ * to be redrawn, we clear the area and redraw the whole icon.
+ */
+ drawIcon = 0;
+ temp = GetBorderIconSize();
+ rect.x = (short)bsize + 2;
+ rect.y = (short)(bsize + titleHeight / 2 - temp / 2);
+ rect.width = (unsigned short)temp;
+ rect.height = (unsigned short)temp;
+ if(XRectInRegion(borderRegion, rect.x, rect.y, rect.width, rect.height)
+ != RectangleOut) {
+
+ drawIcon = 1;
+ XUnionRectWithRegion(&rect, borderRegion, borderRegion);
+
+ } else {
+
+ drawIcon = 0;
+
+ }
+
+ XSetRegion(display, borderGC, borderRegion);
+
+ } else {
+
+ drawIcon = 1;
+ XSetClipMask(display, borderGC, None);
+
+ }
+
+ if(np->state.status & STAT_SHADED) {
+ height = titleHeight + bsize * 2;
+ } else if(np->state.border & BORDER_TITLE) {
+ height = np->height + titleHeight + bsize * 2;
+ } else {
+ height = np->height + 2 * bsize;
+ }
+ width = np->width + bsize * 2;
+
+ DrawBorderHelper(np, width, height, drawIcon);
+
+ if(expose) {
+ XDestroyRegion(borderRegion);
+ borderRegion = NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DrawBorderHelper(const ClientNode *np,
+ unsigned int width, unsigned int height, int drawIcon) {
+
+ ColorType borderTextColor;
+ long borderPixel, borderTextPixel;
+ long pixelUp, pixelDown;
+ int buttonCount, titleWidth;
+ Pixmap canvas;
+ GC gc;
+ int iconSize;
+ int bsize;
+
+ Assert(np);
+
+ iconSize = GetBorderIconSize();
+
+ if(np->state.status & STAT_ACTIVE) {
+ borderTextColor = COLOR_BORDER_ACTIVE_FG;
+ borderPixel = colors[COLOR_BORDER_ACTIVE_BG];
+ borderTextPixel = colors[COLOR_BORDER_ACTIVE_FG];
+ pixelUp = colors[COLOR_BORDER_ACTIVE_UP];
+ pixelDown = colors[COLOR_BORDER_ACTIVE_DOWN];
+ } else {
+ borderTextColor = COLOR_BORDER_FG;
+ borderPixel = colors[COLOR_BORDER_BG];
+ borderTextPixel = colors[COLOR_BORDER_FG];
+ pixelUp = colors[COLOR_BORDER_UP];
+ pixelDown = colors[COLOR_BORDER_DOWN];
+ }
+
+ if(np->state.border & BORDER_OUTLINE) {
+ bsize = borderWidth;
+ } else {
+ bsize = 0;
+ }
+
+ canvas = np->parent;
+ gc = borderGC;
+
+ /* Set the window background to reduce perceived flicker on the
+ * parts of the window that will need to be redrawn. */
+ JXSetWindowBackground(display, canvas, borderPixel);
+
+ JXSetForeground(display, gc, borderPixel);
+ JXFillRectangle(display, canvas, gc, 0, 0, width + 1, height + 1);
+
+ buttonCount = DrawBorderButtons(np, canvas, gc);
+ titleWidth = width - (titleHeight + 2) * buttonCount - bsize
+ - (titleHeight + bsize + 4) - 2;
+
+ if(np->state.border & BORDER_TITLE) {
+
+ if(np->icon && np->width >= titleHeight && drawIcon) {
+ PutIcon(np->icon, canvas, bsize + 2,
+ bsize + titleHeight / 2 - iconSize / 2,
+ iconSize, iconSize);
+ }
+
+ if(np->name && np->name[0] && titleWidth > 0) {
+ RenderString(canvas, FONT_BORDER, borderTextColor,
+ titleHeight + bsize + 4, bsize + titleHeight / 2
+ - GetStringHeight(FONT_BORDER) / 2,
+ titleWidth, borderRegion, np->name);
+ }
+
+ }
+
+ if(np->state.border & BORDER_OUTLINE) {
+
+ /* Draw title outline */
+ JXSetForeground(display, gc, pixelUp);
+ JXDrawLine(display, canvas, gc, borderWidth, borderWidth,
+ width - borderWidth - 1, borderWidth);
+ JXDrawLine(display, canvas, gc, borderWidth, borderWidth + 1,
+ borderWidth, titleHeight + borderWidth - 1);
+
+ JXSetForeground(display, gc, pixelDown);
+ JXDrawLine(display, canvas, gc, borderWidth + 1,
+ titleHeight + borderWidth - 1, width - borderWidth,
+ titleHeight + borderWidth - 1);
+ JXDrawLine(display, canvas, gc, width - borderWidth - 1,
+ borderWidth + 1, width - borderWidth - 1, titleHeight + borderWidth);
+
+ /* Draw outline */
+ JXSetForeground(display, gc, pixelUp);
+ JXDrawLine(display, canvas, gc, width - borderWidth,
+ borderWidth, width - borderWidth, height - borderWidth);
+ JXDrawLine(display, canvas, gc, borderWidth,
+ height - borderWidth, width - borderWidth, height - borderWidth);
+
+ JXSetForeground(display, gc, pixelDown);
+ JXDrawLine(display, canvas, gc, borderWidth - 1,
+ borderWidth - 1, width - borderWidth, borderWidth - 1);
+ JXDrawLine(display, canvas, gc, borderWidth - 1, borderWidth,
+ borderWidth - 1, height - borderWidth);
+
+ JXFillRectangle(display, canvas, gc, width - 2, 0, 2, height);
+ JXFillRectangle(display, canvas, gc, 0, height - 2, width, 2);
+ JXSetForeground(display, gc, pixelUp);
+ JXDrawLine(display, canvas, gc, 0, 0, 0, height - 1);
+ JXDrawLine(display, canvas, gc, 1, 1, 1, height - 2);
+ JXDrawLine(display, canvas, gc, 1, 0, width - 1, 0);
+ JXDrawLine(display, canvas, gc, 1, 1, width - 2, 1);
+
+ if((np->state.border & BORDER_RESIZE)
+ && !(np->state.status & STAT_SHADED)
+ && np->width >= 2 * titleHeight * 2
+ && np->height >= titleHeight * 2) {
+
+ /* Draw marks */
+ JXSetForeground(display, gc, pixelDown);
+
+ /* Upper left */
+ JXDrawLine(display, canvas, gc,
+ titleHeight + borderWidth - 1, 2, titleHeight + borderWidth - 1,
+ borderWidth - 2);
+ JXDrawLine(display, canvas, gc, 2,
+ titleHeight + borderWidth - 1, borderWidth - 2,
+ titleHeight + borderWidth - 1);
+
+ /* Upper right */
+ JXDrawLine(display, canvas, gc,
+ width - titleHeight - borderWidth - 1,
+ 2, width - titleHeight - borderWidth - 1, borderWidth - 2);
+ JXDrawLine(display, canvas, gc, width - 3,
+ titleHeight + borderWidth - 1, width - borderWidth + 1,
+ titleHeight + borderWidth - 1);
+
+ /* Lower left */
+ JXDrawLine(display, canvas, gc, 2,
+ height - titleHeight - borderWidth - 1, borderWidth - 2,
+ height - titleHeight - borderWidth - 1);
+ JXDrawLine(display, canvas, gc,
+ titleHeight + borderWidth - 1, height - 3,
+ titleHeight + borderWidth - 1, height - borderWidth + 1);
+
+ /* Lower right */
+ JXDrawLine(display, canvas, gc, width - 3,
+ height - titleHeight - borderWidth - 1, width - borderWidth + 1,
+ height - titleHeight - borderWidth - 1);
+ JXDrawLine(display, canvas, gc,
+ width - titleHeight - borderWidth - 1,
+ height - 3, width - titleHeight - borderWidth - 1,
+ height - borderWidth + 1);
+
+ JXSetForeground(display, gc, pixelUp);
+
+ /* Upper left */
+ JXDrawLine(display, canvas, gc, titleHeight + borderWidth,
+ 2, titleHeight + borderWidth, borderWidth - 2);
+ JXDrawLine(display, canvas, gc, 2,
+ titleHeight + borderWidth, borderWidth - 2,
+ titleHeight + borderWidth);
+
+ /* Upper right */
+ JXDrawLine(display, canvas, gc,
+ width - titleHeight - borderWidth, 2,
+ width - titleHeight - borderWidth, borderWidth - 2);
+ JXDrawLine(display, canvas, gc, width - 3,
+ titleHeight + borderWidth, width - borderWidth + 1,
+ titleHeight + borderWidth);
+
+ /* Lower left */
+ JXDrawLine(display, canvas, gc, 2,
+ height - titleHeight - borderWidth,
+ borderWidth - 2, height - titleHeight - borderWidth);
+ JXDrawLine(display, canvas, gc, titleHeight + borderWidth,
+ height - 3, titleHeight + borderWidth, height - borderWidth + 1);
+
+ /* Lower right */
+ JXDrawLine(display, canvas, gc, width - 3,
+ height - titleHeight - borderWidth, width - borderWidth + 1,
+ height - titleHeight - borderWidth);
+ JXDrawLine(display, canvas, gc,
+ width - titleHeight - borderWidth, height - 3,
+ width - titleHeight - borderWidth, height - borderWidth + 1);
+
+ }
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DrawButtonBorder(const ClientNode *np, int offset,
+ Pixmap canvas, GC gc) {
+
+ long up, down;
+ long bsize;
+
+ Assert(np);
+
+ if(np->state.status & STAT_ACTIVE) {
+ up = colors[COLOR_BORDER_ACTIVE_UP];
+ down = colors[COLOR_BORDER_ACTIVE_DOWN];
+ } else {
+ up = colors[COLOR_BORDER_UP];
+ down = colors[COLOR_BORDER_DOWN];
+ }
+
+ if(np->state.border & BORDER_OUTLINE) {
+ bsize = borderWidth;
+ } else {
+ bsize = 0;
+ }
+
+ JXSetForeground(display, gc, up);
+ JXDrawLine(display, canvas, gc, offset, bsize + 1,
+ offset, titleHeight + bsize - 2);
+
+ JXSetForeground(display, gc, down);
+ JXDrawLine(display, canvas, gc, offset - 1,
+ bsize + 1, offset - 1, titleHeight + bsize - 2);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc) {
+
+ Pixmap pixmap;
+ int count = 0;
+ int offset;
+ int bsize;
+
+ Assert(np);
+
+ if(!(np->state.border & BORDER_TITLE)) {
+ return count;
+ }
+ if(np->state.border & BORDER_OUTLINE) {
+ bsize = borderWidth;
+ } else {
+ bsize = 0;
+ }
+
+ offset = np->width + bsize - titleHeight;
+
+ if(offset <= bsize + titleHeight) {
+ return count;
+ }
+
+ if(np->state.border & BORDER_CLOSE) {
+
+ DrawButtonBorder(np, offset, canvas, gc);
+
+ if(np->state.status & STAT_ACTIVE) {
+ pixmap = pixmaps[BP_ACTIVE_CLOSE];
+ } else {
+ pixmap = pixmaps[BP_CLOSE];
+ }
+
+ JXCopyArea(display, pixmap, canvas, gc, 0, 0, 16, 16,
+ offset + titleHeight / 2 - 8, bsize + titleHeight / 2 - 8);
+
+ offset -= titleHeight;
+ ++count;
+
+ if(offset <= bsize + titleHeight) {
+ return count;
+ }
+
+ }
+
+ if(np->state.border & BORDER_MAX) {
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ if(np->state.status & STAT_ACTIVE) {
+ pixmap = pixmaps[BP_ACTIVE_MAXIMIZE_ACTIVE];
+ } else {
+ pixmap = pixmaps[BP_MAXIMIZE_ACTIVE];
+ }
+ } else {
+ if(np->state.status & STAT_ACTIVE) {
+ pixmap = pixmaps[BP_ACTIVE_MAXIMIZE];
+ } else {
+ pixmap = pixmaps[BP_MAXIMIZE];
+ }
+ }
+ JXCopyArea(display, pixmap, canvas, gc, 0, 0, 16, 16,
+ offset + titleHeight / 2 - 8, bsize + titleHeight / 2 - 8);
+
+ DrawButtonBorder(np, offset, canvas, gc);
+
+ offset -= titleHeight;
+ ++count;
+
+ if(offset <= bsize + titleHeight) {
+ return count;
+ }
+
+ }
+
+ if(np->state.border & BORDER_MIN) {
+
+ DrawButtonBorder(np, offset, canvas, gc);
+
+ if(np->state.status & STAT_ACTIVE) {
+ pixmap = pixmaps[BP_ACTIVE_MINIMIZE];
+ } else {
+ pixmap = pixmaps[BP_MINIMIZE];
+ }
+
+ JXCopyArea(display, pixmap, canvas, gc, 0, 0, 16, 16,
+ offset + titleHeight / 2 - 8, bsize + titleHeight / 2 - 8);
+
+ ++count;
+
+ }
+
+ return count;
+
+}
+
+/****************************************************************************
+ * Redraw the borders on the current desktop.
+ * This should be done after loading clients since the stacking order
+ * may cause borders on the current desktop to become visible after moving
+ * clients to their assigned desktops.
+ ****************************************************************************/
+void ExposeCurrentDesktop() {
+
+ ClientNode *np;
+ int layer;
+
+ for(layer = 0; layer < LAYER_COUNT; layer++) {
+ for(np = nodes[layer]; np; np = np->next) {
+ if(!(np->state.status & (STAT_HIDDEN | STAT_MINIMIZED))) {
+ DrawBorder(np, NULL);
+ }
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void GetBorderSize(const ClientNode *np,
+ int *north, int *south, int *east, int *west) {
+
+ Assert(np);
+ Assert(north);
+ Assert(south);
+ Assert(east);
+ Assert(west);
+
+ /* Full screen is a special case. */
+ if(np->state.status & STAT_FULLSCREEN) {
+ *north = 0;
+ *south = 0;
+ *east = 0;
+ *west = 0;
+ return;
+ }
+
+ if(np->state.border & BORDER_OUTLINE) {
+
+ *north = borderWidth;
+ *south = borderWidth;
+ *east = borderWidth;
+ *west = borderWidth;
+
+ } else {
+
+ *north = 0;
+ *south = 0;
+ *east = 0;
+ *west = 0;
+
+ }
+
+ if(np->state.border & BORDER_TITLE) {
+ *north += titleHeight;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetBorderWidth(const char *str) {
+
+ int width;
+
+ if(str) {
+
+ width = atoi(str);
+ if(width < MIN_BORDER_WIDTH || width > MAX_BORDER_WIDTH) {
+ borderWidth = DEFAULT_BORDER_WIDTH;
+ Warning("invalid border width specified: %d", width);
+ } else {
+ borderWidth = width;
+ }
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetTitleHeight(const char *str) {
+
+ int height;
+
+ if(str) {
+
+ height = atoi(str);
+ if(height < MIN_TITLE_HEIGHT || height > MAX_TITLE_HEIGHT) {
+ titleHeight = DEFAULT_TITLE_HEIGHT;
+ Warning("invalid title height specified: %d", height);
+ } else {
+ titleHeight = height;
+ }
+
+ }
+
+}
+
+
diff --git a/src/border.h b/src/border.h
new file mode 100644
index 0000000..55458db
--- /dev/null
+++ b/src/border.h
@@ -0,0 +1,80 @@
+/**
+ * @file border.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header file for border functions.
+ *
+ */
+
+#ifndef BORDER_H
+#define BORDER_H
+
+struct ClientNode;
+
+/** Flags to determine what action to take on the border. */
+typedef enum {
+ BA_NONE = 0, /**< Do nothing. */
+ BA_RESIZE = 1, /**< Resize the window. */
+ BA_MOVE = 2, /**< Move the window. */
+ BA_CLOSE = 3, /**< Close the window. */
+ BA_MAXIMIZE = 4, /**< Maximize the window. */
+ BA_MINIMIZE = 5, /**< Minimize the window. */
+ BA_MENU = 6, /**< Show the window menu. */
+ BA_RESIZE_N = 0x10, /**< Resize north. */
+ BA_RESIZE_S = 0x20, /**< Resize south. */
+ BA_RESIZE_E = 0x40, /**< Resize east. */
+ BA_RESIZE_W = 0x80 /**< Resize west. */
+} BorderActionType;
+
+/*@{*/
+void InitializeBorders();
+void StartupBorders();
+void ShutdownBorders();
+void DestroyBorders();
+/*@}*/
+
+/** Determine the action to take for a client.
+ * @param np The client.
+ * @param x The x-coordinate of the mouse (frame relative).
+ * @param y The y-coordinate of the mouse (frame relative).
+ * @return The action to take.
+ */
+BorderActionType GetBorderActionType(const struct ClientNode *np, int x, int y);
+
+/** Draw a window border.
+ * @param np The client whose frame to draw.
+ * @param expose The expose event causing the redraw (or NULL).
+ */
+void DrawBorder(const struct ClientNode *np, const XExposeEvent *expose);
+
+/** Get the size of a border icon.
+ * @return The size in pixels (note that icons are square).
+ */
+int GetBorderIconSize();
+
+/** Get the size of a window border.
+ * @param np The client.
+ * @param north Pointer to the value to contain the north border size.
+ * @param south Pointer to the value to contain the south border size.
+ * @param east Pointer to the value to contain the east border size.
+ * @param west Pointer to the value to contain the west border size.
+ */
+void GetBorderSize(const struct ClientNode *np,
+ int *north, int *south, int *east, int *west);
+
+/** Set the size of window borders.
+ * @param str The size to use in string form.
+ */
+void SetBorderWidth(const char *str);
+
+/** Set the size of window title bars.
+ * @param str The size to use in string form.
+ */
+void SetTitleHeight(const char *str);
+
+/** Redraw all borders on the current desktop. */
+void ExposeCurrentDesktop();
+
+#endif
+
diff --git a/src/button.c b/src/button.c
new file mode 100644
index 0000000..0d62742
--- /dev/null
+++ b/src/button.c
@@ -0,0 +1,212 @@
+/***************************************************************************
+ * Functions to handle drawing buttons.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "button.h"
+#include "font.h"
+#include "color.h"
+#include "main.h"
+#include "icon.h"
+#include "image.h"
+
+static void GetScaledIconSize(IconNode *ip, int maxsize,
+ int *width, int *height);
+
+/***************************************************************************
+ ***************************************************************************/
+void DrawButton(ButtonNode *bp) {
+
+ long outlinePixel;
+ long topPixel, bottomPixel;
+ ColorType fg, bg;
+
+ Drawable drawable;
+ GC gc;
+ int x, y;
+ int width, height;
+ int xoffset, yoffset;
+
+ int iconWidth, iconHeight;
+ int textWidth, textHeight;
+
+ Assert(bp);
+
+ drawable = bp->drawable;
+ gc = bp->gc;
+ x = bp->x;
+ y = bp->y;
+ width = bp->width;
+ height = bp->height;
+
+ switch(bp->type) {
+ case BUTTON_LABEL:
+ fg = COLOR_MENU_FG;
+ bg = COLOR_MENU_BG;
+ outlinePixel = colors[COLOR_MENU_BG];
+ topPixel = colors[COLOR_MENU_BG];
+ bottomPixel = colors[COLOR_MENU_BG];
+ break;
+ case BUTTON_MENU_ACTIVE:
+ fg = COLOR_MENU_ACTIVE_FG;
+ bg = COLOR_MENU_ACTIVE_BG;
+ outlinePixel = colors[COLOR_MENU_ACTIVE_DOWN];
+ topPixel = colors[COLOR_MENU_ACTIVE_UP];
+ bottomPixel = colors[COLOR_MENU_ACTIVE_DOWN];
+ break;
+ case BUTTON_TASK:
+ fg = COLOR_TASK_FG;
+ bg = COLOR_TASK_BG;
+ outlinePixel = colors[COLOR_TASK_DOWN];
+ topPixel = colors[COLOR_TASK_UP];
+ bottomPixel = colors[COLOR_TASK_DOWN];
+ break;
+ case BUTTON_TASK_ACTIVE:
+ fg = COLOR_TASK_ACTIVE_FG;
+ bg = COLOR_TASK_ACTIVE_BG;
+ outlinePixel = colors[COLOR_TASK_ACTIVE_DOWN];
+ topPixel = colors[COLOR_TASK_ACTIVE_DOWN];
+ bottomPixel = colors[COLOR_TASK_ACTIVE_UP];
+ break;
+ case BUTTON_MENU:
+ default:
+ fg = COLOR_MENU_FG;
+ bg = COLOR_MENU_BG;
+ outlinePixel = colors[COLOR_MENU_DOWN];
+ topPixel = colors[COLOR_MENU_UP];
+ bottomPixel = colors[COLOR_MENU_DOWN];
+ break;
+ }
+
+ JXSetForeground(display, gc, colors[bg]);
+ JXFillRectangle(display, drawable, gc, x + 2, y + 2, width - 3, height - 3);
+
+ JXSetForeground(display, gc, outlinePixel);
+ JXDrawLine(display, drawable, gc, x + 1, y, x + width - 1, y);
+ JXDrawLine(display, drawable, gc, x + 1, y + height, x + width - 1,
+ y + height);
+ JXDrawLine(display, drawable, gc, x, y + 1, x, y + height - 1);
+ JXDrawLine(display, drawable, gc, x + width, y + 1, x + width,
+ y + height - 1);
+
+ JXSetForeground(display, gc, topPixel);
+ JXDrawLine(display, drawable, gc, x + 1, y + 1, x + width - 2, y + 1);
+ JXDrawLine(display, drawable, gc, x + 1, y + 2, x + 1, y + height - 2);
+
+ JXSetForeground(display, gc, bottomPixel);
+ JXDrawLine(display, drawable, gc, x + 1, y + height - 1, x + width - 1,
+ y + height - 1);
+ JXDrawLine(display, drawable, gc, x + width - 1, y + 1, x + width - 1,
+ y + height - 2);
+
+ iconWidth = 0;
+ iconHeight = 0;
+ if(bp->icon) {
+
+ if(width < height) {
+ GetScaledIconSize(bp->icon, width - 4, &iconWidth, &iconHeight);
+ } else {
+ GetScaledIconSize(bp->icon, height - 4, &iconWidth, &iconHeight);
+ }
+
+ }
+
+ textWidth = 0;
+ textHeight = 0;
+ if(bp->text) {
+ textWidth = GetStringWidth(bp->font, bp->text);
+ textHeight = GetStringHeight(bp->font);
+ if(textWidth + iconWidth + 10 > width) {
+ textWidth = width - iconWidth - 10;
+ if(textWidth < 0) {
+ textWidth = 0;
+ }
+ }
+ }
+
+ switch(bp->alignment) {
+ case ALIGN_RIGHT:
+ xoffset = width - iconWidth - textWidth + 4;
+ if(xoffset < 4) {
+ xoffset = 4;
+ }
+ break;
+ case ALIGN_CENTER:
+ xoffset = width / 2 - (iconWidth + textWidth) / 2;
+ if(xoffset < 0) {
+ xoffset = 0;
+ }
+ break;
+ case ALIGN_LEFT:
+ default:
+ xoffset = 4;
+ break;
+ }
+
+ if(bp->icon) {
+ yoffset = height / 2 - iconHeight / 2;
+ PutIcon(bp->icon, drawable, x + xoffset, y + yoffset,
+ iconWidth, iconHeight);
+ xoffset += iconWidth + 2;
+ }
+
+ if(bp->text) {
+ yoffset = height / 2 - textHeight / 2;
+ RenderString(drawable, bp->font, fg, x + xoffset, y + yoffset,
+ textWidth, NULL, bp->text);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ResetButton(ButtonNode *bp, Drawable d, GC g) {
+
+ Assert(bp);
+
+ bp->type = BUTTON_MENU;
+ bp->drawable = d;
+ bp->gc = g;
+ bp->font = FONT_TRAY;
+ bp->alignment = ALIGN_LEFT;
+ bp->x = 0;
+ bp->y = 0;
+ bp->width = 1;
+ bp->height = 1;
+ bp->icon = NULL;
+ bp->text = NULL;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void GetScaledIconSize(IconNode *ip, int maxsize,
+ int *width, int *height) {
+
+ double ratio;
+
+ Assert(ip);
+ Assert(width);
+ Assert(height);
+
+ /* width to height */
+ Assert(ip->image->height > 0);
+ ratio = (double)ip->image->width / ip->image->height;
+
+ if(ip->image->width > ip->image->height) {
+
+ /* Compute size wrt width */
+ *width = maxsize * ratio;
+ *height = *width / ratio;
+
+ } else {
+
+ /* Compute size wrt height */
+ *height = maxsize / ratio;
+ *width = *height * ratio;
+
+ }
+
+}
+
diff --git a/src/button.h b/src/button.h
new file mode 100644
index 0000000..9303ea5
--- /dev/null
+++ b/src/button.h
@@ -0,0 +1,63 @@
+/**
+ * @file button.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header file for button functions.
+ *
+ */
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include "font.h"
+
+struct IconNode;
+
+/** Button types. */
+typedef enum {
+ BUTTON_LABEL, /**< Label. */
+ BUTTON_MENU, /**< Menu item. */
+ BUTTON_MENU_ACTIVE, /**< Active menu item. */
+ BUTTON_TASK, /**< Item in the task list. */
+ BUTTON_TASK_ACTIVE /**< Active item in the task list. */
+} ButtonType;
+
+/** Alignment of content in a button. */
+typedef enum {
+ ALIGN_LEFT, /**< Left align. */
+ ALIGN_CENTER, /**< Center align. */
+ ALIGN_RIGHT /**< Right align. */
+} AlignmentType;
+
+/** Data used for drawing a button. */
+typedef struct {
+
+ ButtonType type; /**< The type of button to draw. */
+ Drawable drawable; /**< The place to put the button. */
+ GC gc; /**< Graphics context used for drawing. */
+ FontType font; /**< The font for button text. */
+ AlignmentType alignment; /**< Alignment of the button content. */
+
+ int x, y; /**< The coordinates to render the button. */
+ int width, height; /**< The size of the button. */
+
+ struct IconNode *icon; /**< Icon used in the button. */
+ const char *text; /**< Text used in the button. */
+
+} ButtonNode;
+
+/** Draw a button.
+ * @param bp The button to draw.
+ */
+void DrawButton(ButtonNode *bp);
+
+/** Reset the contents of a ButtonNode structure.
+ * @param bp The structure to reset.
+ * @param d The drawable to use.
+ * @param g The graphics context to use.
+ */
+void ResetButton(ButtonNode *bp, Drawable d, GC g);
+
+#endif
+
diff --git a/src/client.c b/src/client.c
new file mode 100644
index 0000000..6c1976e
--- /dev/null
+++ b/src/client.c
@@ -0,0 +1,1423 @@
+/**
+ * @file client.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Functions to handle client windows.
+ *
+ */
+
+#include "jwm.h"
+#include "client.h"
+#include "main.h"
+#include "icon.h"
+#include "hint.h"
+#include "group.h"
+#include "tray.h"
+#include "confirm.h"
+#include "key.h"
+#include "cursor.h"
+#include "taskbar.h"
+#include "screen.h"
+#include "pager.h"
+#include "color.h"
+#include "error.h"
+#include "place.h"
+
+static const int STACK_BLOCK_SIZE = 8;
+
+ClientNode *nodes[LAYER_COUNT];
+ClientNode *nodeTail[LAYER_COUNT];
+
+static ClientNode *activeClient;
+
+static int clientCount;
+
+static void LoadFocus();
+static void ReparentClient(ClientNode *np, int notOwner);
+
+static void MinimizeTransients(ClientNode *np);
+static void CheckShape(ClientNode *np);
+
+static void RestoreTransients(ClientNode *np, int raise);
+
+static void KillClientHandler(ClientNode *np);
+
+/** Initialize client data. */
+void InitializeClients() {
+}
+
+/** Load windows that are already mapped. */
+void StartupClients() {
+
+ XWindowAttributes attr;
+ Window rootReturn, parentReturn, *childrenReturn;
+ unsigned int childrenCount;
+ unsigned int x;
+
+ clientCount = 0;
+ activeClient = NULL;
+ currentDesktop = 0;
+
+ /* Clear out the client lists. */
+ for(x = 0; x < LAYER_COUNT; x++) {
+ nodes[x] = NULL;
+ nodeTail[x] = NULL;
+ }
+
+ /* Query client windows. */
+ JXQueryTree(display, rootWindow, &rootReturn, &parentReturn,
+ &childrenReturn, &childrenCount);
+
+ /* Add each client. */
+ for(x = 0; x < childrenCount; x++) {
+ if(JXGetWindowAttributes(display, childrenReturn[x], &attr)) {
+ if(attr.override_redirect == False
+ && attr.map_state == IsViewable) {
+ AddClientWindow(childrenReturn[x], 1, 1);
+ }
+ }
+ }
+
+ JXFree(childrenReturn);
+
+ LoadFocus();
+
+ UpdateTaskBar();
+ UpdatePager();
+
+}
+
+/** Release client windows. */
+void ShutdownClients() {
+
+ int x;
+
+ for(x = 0; x < LAYER_COUNT; x++) {
+ while(nodeTail[x]) {
+ RemoveClient(nodeTail[x]);
+ }
+ }
+
+}
+
+/** Destroy client data. */
+void DestroyClients() {
+}
+
+/** Set the focus to the window currently under the mouse pointer. */
+void LoadFocus() {
+
+ ClientNode *np;
+ Window rootReturn, childReturn;
+ int rootx, rooty;
+ int winx, winy;
+ unsigned int mask;
+
+ JXQueryPointer(display, rootWindow, &rootReturn, &childReturn,
+ &rootx, &rooty, &winx, &winy, &mask);
+
+ np = FindClientByWindow(childReturn);
+ if(np) {
+ FocusClient(np);
+ }
+
+}
+
+/** Add a window to management. */
+ClientNode *AddClientWindow(Window w, int alreadyMapped, int notOwner) {
+
+ XWindowAttributes attr;
+ ClientNode *np;
+
+ Assert(w != None);
+
+ /* Get window attributes. */
+ if(JXGetWindowAttributes(display, w, &attr) == 0) {
+ return NULL;
+ }
+
+ /* Determine if we should care about this window. */
+ if(attr.override_redirect == True) {
+ return NULL;
+ }
+ if(attr.class == InputOnly) {
+ return NULL;
+ }
+
+ /* Prepare a client node for this window. */
+ np = Allocate(sizeof(ClientNode));
+ memset(np, 0, sizeof(ClientNode));
+
+ np->window = w;
+ np->owner = None;
+ np->state.desktop = currentDesktop;
+ np->controller = NULL;
+ np->name = NULL;
+ np->colormaps = NULL;
+
+ np->x = attr.x;
+ np->y = attr.y;
+ np->width = attr.width;
+ np->height = attr.height;
+ np->cmap = attr.colormap;
+ np->colormaps = NULL;
+ np->state.status = STAT_NONE;
+ np->state.layer = LAYER_NORMAL;
+
+ np->state.border = BORDER_DEFAULT;
+ np->borderAction = BA_NONE;
+
+ ReadClientProtocols(np);
+
+ if(!notOwner) {
+ np->state.border = BORDER_OUTLINE | BORDER_TITLE | BORDER_MOVE;
+ np->state.status |= STAT_WMDIALOG | STAT_STICKY;
+ }
+
+ /* We now know the layer, so insert */
+ np->prev = NULL;
+ np->next = nodes[np->state.layer];
+ if(np->next) {
+ np->next->prev = np;
+ } else {
+ nodeTail[np->state.layer] = np;
+ }
+ nodes[np->state.layer] = np;
+
+ LoadIcon(np);
+
+ ApplyGroups(np);
+
+ SetDefaultCursor(np->window);
+ ReparentClient(np, notOwner);
+ PlaceClient(np, alreadyMapped);
+
+ /* If one of these fails we are SOL, so who cares. */
+ XSaveContext(display, np->window, clientContext, (void*)np);
+ XSaveContext(display, np->parent, frameContext, (void*)np);
+
+ if(np->state.status & STAT_MAPPED) {
+ JXMapWindow(display, np->window);
+ JXMapWindow(display, np->parent);
+ }
+
+ DrawBorder(np, NULL);
+
+ AddClientToTaskBar(np);
+
+ if(!alreadyMapped) {
+ RaiseClient(np);
+ }
+
+ ++clientCount;
+
+ if(np->state.status & STAT_STICKY) {
+ SetCardinalAtom(np->window, ATOM_NET_WM_DESKTOP, ~0UL);
+ } else {
+ SetCardinalAtom(np->window, ATOM_NET_WM_DESKTOP, np->state.desktop);
+ }
+
+ /* Shade the client if requested. */
+ if(np->state.status & STAT_SHADED) {
+ ShadeClient(np);
+ }
+
+ /* Minimize the client if requested. */
+ if(np->state.status & STAT_MINIMIZED) {
+ np->state.status &= ~STAT_MINIMIZED;
+ MinimizeClient(np);
+ }
+
+ /* Maximize the client if requested. */
+ if(np->state.status & STAT_MAXIMIZED) {
+ np->state.status &= ~STAT_MAXIMIZED;
+ MaximizeClient(np);
+ }
+
+ /* Make sure we're still in sync */
+ WriteState(np);
+ SendConfigureEvent(np);
+
+ /* Hide the client if we're not on the right desktop. */
+ if(np->state.desktop != currentDesktop
+ && !(np->state.status & STAT_STICKY)) {
+ HideClient(np);
+ }
+
+ ReadClientStrut(np);
+
+ /* Focus transients if their parent has focus. */
+ if(np->owner != None) {
+
+ if(activeClient && np->owner == activeClient->window) {
+ FocusClient(np);
+ }
+
+ }
+
+ return np;
+
+}
+
+/** Minimize a client window and all of its transients. */
+void MinimizeClient(ClientNode *np) {
+
+ Assert(np);
+
+ if(focusModel == FOCUS_CLICK && np == activeClient) {
+ FocusNextStacked(np);
+ }
+
+ MinimizeTransients(np);
+
+ UpdateTaskBar();
+ UpdatePager();
+
+}
+
+/** Minimize all transients as well as the specified client. */
+void MinimizeTransients(ClientNode *np) {
+
+ ClientNode *tp;
+ int x;
+
+ Assert(np);
+
+ /* A minimized client can't be active. */
+ if(activeClient == np) {
+ activeClient = NULL;
+ np->state.status &= ~STAT_ACTIVE;
+ }
+
+ /* Unmap the window and update its state. */
+ if(np->state.status & (STAT_MAPPED | STAT_SHADED)) {
+ JXUnmapWindow(display, np->window);
+ JXUnmapWindow(display, np->parent);
+ }
+ np->state.status |= STAT_MINIMIZED;
+ np->state.status &= ~STAT_MAPPED;
+ WriteState(np);
+
+ /* Minimize transient windows. */
+ for(x = 0; x < LAYER_COUNT; x++) {
+ for(tp = nodes[x]; tp; tp = tp->next) {
+ if(tp->owner == np->window
+ && (tp->state.status & (STAT_MAPPED | STAT_SHADED))
+ && !(tp->state.status & STAT_MINIMIZED)) {
+ MinimizeTransients(tp);
+ }
+ }
+ }
+
+}
+
+/** Shade a client. */
+void ShadeClient(ClientNode *np) {
+
+ int north, south, east, west;
+
+ Assert(np);
+
+ if(!(np->state.border & BORDER_TITLE)) {
+ return;
+ }
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ if(np->state.status & STAT_MAPPED) {
+ JXUnmapWindow(display, np->window);
+ }
+ np->state.status |= STAT_SHADED;
+ np->state.status &= ~STAT_MINIMIZED;
+ np->state.status &= ~STAT_SDESKTOP;
+ np->state.status &= ~STAT_MAPPED;
+
+ JXResizeWindow(display, np->parent, np->width + east + west,
+ north + south);
+
+ WriteState(np);
+
+#ifdef USE_SHAPE
+ if(np->state.status & STAT_SHAPE) {
+ SetShape(np);
+ }
+#endif
+
+}
+
+/** Unshade a client. */
+void UnshadeClient(ClientNode *np) {
+
+ int bsize;
+
+ Assert(np);
+
+ if(!(np->state.border & BORDER_TITLE)) {
+ return;
+ }
+ if(np->state.border & BORDER_OUTLINE) {
+ bsize = borderWidth;
+ } else {
+ bsize = 0;
+ }
+
+ if(np->state.status & STAT_SHADED) {
+ JXMapWindow(display, np->window);
+ np->state.status |= STAT_MAPPED;
+ np->state.status &= ~STAT_SHADED;
+ }
+
+ JXResizeWindow(display, np->parent, np->width + 2 * bsize,
+ np->height + titleHeight + 2 * bsize);
+
+ WriteState(np);
+
+#ifdef USE_SHAPE
+ if(np->state.status & STAT_SHAPE) {
+ SetShape(np);
+ }
+#endif
+
+ RefocusClient();
+ RestackClients();
+
+}
+
+/** Set a client's state to withdrawn. */
+void SetClientWithdrawn(ClientNode *np) {
+
+ Assert(np);
+
+ if(activeClient == np) {
+ activeClient = NULL;
+ np->state.status &= ~STAT_ACTIVE;
+ FocusNextStacked(np);
+ }
+
+ if(np->state.status & STAT_MAPPED) {
+ JXUnmapWindow(display, np->window);
+ JXUnmapWindow(display, np->parent);
+ WriteState(np);
+ } else if(np->state.status & STAT_SHADED) {
+ JXUnmapWindow(display, np->parent);
+ WriteState(np);
+ }
+
+ np->state.status &= ~STAT_SHADED;
+ np->state.status &= ~STAT_MAPPED;
+ np->state.status &= ~STAT_MINIMIZED;
+ np->state.status &= ~STAT_SDESKTOP;
+
+ UpdateTaskBar();
+ UpdatePager();
+
+}
+
+/** Restore a window with its transients (helper method). */
+void RestoreTransients(ClientNode *np, int raise) {
+
+ ClientNode *tp;
+ int x;
+
+ Assert(np);
+
+ /* Restore this window. */
+ if(!(np->state.status & STAT_MAPPED)) {
+ if(np->state.status & STAT_SHADED) {
+ JXMapWindow(display, np->parent);
+ } else {
+ JXMapWindow(display, np->window);
+ JXMapWindow(display, np->parent);
+ np->state.status |= STAT_MAPPED;
+ }
+ }
+ np->state.status &= ~STAT_MINIMIZED;
+ np->state.status &= ~STAT_SDESKTOP;
+
+ WriteState(np);
+
+ /* Restore transient windows. */
+ for(x = 0; x < LAYER_COUNT; x++) {
+ for(tp = nodes[x]; tp; tp = tp->next) {
+ if(tp->owner == np->window
+ && !(tp->state.status & (STAT_MAPPED | STAT_SHADED))
+ && (tp->state.status & STAT_MINIMIZED)) {
+ RestoreTransients(tp, raise);
+ }
+ }
+ }
+
+ if(raise) {
+ RaiseClient(np);
+ }
+
+}
+
+/** Restore a client window and its transients. */
+void RestoreClient(ClientNode *np, int raise) {
+
+ Assert(np);
+
+ RestoreTransients(np, raise);
+
+ RestackClients();
+ UpdateTaskBar();
+ UpdatePager();
+
+}
+
+/** Set the client layer. This will affect transients. */
+void SetClientLayer(ClientNode *np, unsigned int layer) {
+
+ ClientNode *tp, *next;
+ int x;
+
+ Assert(np);
+
+ if(layer > LAYER_TOP) {
+ Warning("Client %s requested an invalid layer: %d", np->name, layer);
+ return;
+ }
+
+ if(np->state.layer != layer) {
+
+ /* Loop through all clients so we get transients. */
+ for(x = 0; x < LAYER_COUNT; x++) {
+ tp = nodes[x];
+ while(tp) {
+ if(tp == np || tp->owner == np->window) {
+
+ next = tp->next;
+
+ /* Remove from the old node list */
+ if(next) {
+ next->prev = tp->prev;
+ } else {
+ nodeTail[tp->state.layer] = tp->prev;
+ }
+ if(tp->prev) {
+ tp->prev->next = next;
+ } else {
+ nodes[tp->state.layer] = next;
+ }
+
+ /* Insert into the new node list */
+ tp->prev = NULL;
+ tp->next = nodes[layer];
+ if(nodes[layer]) {
+ nodes[layer]->prev = tp;
+ } else {
+ nodeTail[layer] = tp;
+ }
+ nodes[layer] = tp;
+
+ /* Set the new layer */
+ tp->state.layer = layer;
+ SetCardinalAtom(tp->window, ATOM_WIN_LAYER, layer);
+
+ /* Make sure we continue on the correct layer list. */
+ tp = next;
+
+ } else {
+ tp = tp->next;
+ }
+ }
+ }
+
+ RestackClients();
+
+ }
+
+}
+
+/** Set a client's sticky status. This will update transients. */
+void SetClientSticky(ClientNode *np, int isSticky) {
+
+ ClientNode *tp;
+ int old;
+ int x;
+
+ Assert(np);
+
+ /* Get the old sticky status. */
+ if(np->state.status & STAT_STICKY) {
+ old = 1;
+ } else {
+ old = 0;
+ }
+
+ if(isSticky && !old) {
+
+ /* Change from non-sticky to sticky. */
+
+ for(x = 0; x < LAYER_COUNT; x++) {
+ for(tp = nodes[x]; tp; tp = tp->next) {
+ if(tp == np || tp->owner == np->window) {
+ tp->state.status |= STAT_STICKY;
+ SetCardinalAtom(tp->window, ATOM_NET_WM_DESKTOP, ~0UL);
+ WriteState(tp);
+ }
+ }
+ }
+
+ } else if(!isSticky && old) {
+
+ /* Change from sticky to non-sticky. */
+
+ for(x = 0; x < LAYER_COUNT; x++) {
+ for(tp = nodes[x]; tp; tp = tp->next) {
+ if(tp == np || tp->owner == np->window) {
+ tp->state.status &= ~STAT_STICKY;
+ WriteState(tp);
+ }
+ }
+ }
+
+ /* Since this client is no longer sticky, we need to assign
+ * a desktop. Here we use the current desktop.
+ * Note that SetClientDesktop updates transients (which is good).
+ */
+ SetClientDesktop(np, currentDesktop);
+
+ }
+
+}
+
+/** Set a client's desktop. This will update transients. */
+void SetClientDesktop(ClientNode *np, unsigned int desktop) {
+
+ ClientNode *tp;
+ int x;
+
+ Assert(np);
+
+ if(desktop >= desktopCount) {
+ return;
+ }
+
+ if(!(np->state.status & STAT_STICKY)) {
+ for(x = 0; x < LAYER_COUNT; x++) {
+ for(tp = nodes[x]; tp; tp = tp->next) {
+ if(tp == np || tp->owner == np->window) {
+
+ tp->state.desktop = desktop;
+
+ if(desktop == currentDesktop) {
+ ShowClient(tp);
+ } else {
+ HideClient(tp);
+ }
+
+ SetCardinalAtom(tp->window, ATOM_NET_WM_DESKTOP,
+ tp->state.desktop);
+ }
+ }
+ }
+ UpdatePager();
+ UpdateTaskBar();
+ }
+
+}
+
+/** Hide a client without unmapping. This will NOT update transients. */
+void HideClient(ClientNode *np) {
+
+ Assert(np);
+
+ if(activeClient == np) {
+ activeClient = NULL;
+ }
+ np->state.status |= STAT_HIDDEN;
+ if(np->state.status & (STAT_MAPPED | STAT_SHADED)) {
+ JXUnmapWindow(display, np->parent);
+ }
+
+}
+
+/** Show a hidden client. This will NOT update transients. */
+void ShowClient(ClientNode *np) {
+
+ Assert(np);
+
+ if(np->state.status & STAT_HIDDEN) {
+ np->state.status &= ~STAT_HIDDEN;
+ if(np->state.status & (STAT_MAPPED | STAT_SHADED)) {
+ JXMapWindow(display, np->parent);
+ if(np->state.status & STAT_ACTIVE) {
+ FocusClient(np);
+ }
+ }
+ }
+
+}
+
+/** Maximize a client window. */
+void MaximizeClient(ClientNode *np) {
+
+ int north, south, east, west;
+
+ Assert(np);
+
+ /* We don't want to mess with full screen clients. */
+ if(np->state.status & STAT_FULLSCREEN) {
+ SetClientFullScreen(np, 0);
+ }
+
+ if(np->state.status & STAT_SHADED) {
+ UnshadeClient(np);
+ }
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ np->x = np->oldx;
+ np->y = np->oldy;
+ np->width = np->oldWidth;
+ np->height = np->oldHeight;
+ np->state.status &= ~STAT_MAXIMIZED;
+ } else {
+ PlaceMaximizedClient(np);
+ }
+
+ JXMoveResizeWindow(display, np->parent,
+ np->x - west, np->y - north,
+ np->width + east + west,
+ np->height + north + south);
+ JXMoveResizeWindow(display, np->window, west,
+ north, np->width, np->height);
+
+ WriteState(np);
+ SendConfigureEvent(np);
+
+}
+
+/** Set a client's full screen state. */
+void SetClientFullScreen(ClientNode *np, int fullScreen) {
+
+ XEvent event;
+ int north, south, east, west;
+ const ScreenType *sp;
+
+ Assert(np);
+
+ /* Make sure there's something to do. */
+ if(fullScreen && (np->state.status & STAT_FULLSCREEN)) {
+ return;
+ } else if (!fullScreen && !(np->state.status & STAT_FULLSCREEN)) {
+ return;
+ }
+
+ if(np->state.status & STAT_SHADED) {
+ UnshadeClient(np);
+ }
+
+ if(fullScreen) {
+
+ np->state.status |= STAT_FULLSCREEN;
+
+ sp = GetCurrentScreen(np->x, np->y);
+
+ JXReparentWindow(display, np->window, rootWindow, 0, 0);
+ JXMoveResizeWindow(display, np->window, 0, 0,
+ sp->width, sp->height);
+
+ SetClientLayer(np, LAYER_TOP);
+
+ } else {
+
+ np->state.status &= ~STAT_FULLSCREEN;
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ JXReparentWindow(display, np->window, np->parent, west, north);
+ JXMoveResizeWindow(display, np->window, west,
+ north, np->width, np->height);
+
+ event.type = MapRequest;
+ event.xmaprequest.send_event = True;
+ event.xmaprequest.display = display;
+ event.xmaprequest.parent = np->parent;
+ event.xmaprequest.window = np->window;
+ JXSendEvent(display, rootWindow, False,
+ SubstructureRedirectMask, &event);
+
+ SetClientLayer(np, LAYER_NORMAL);
+
+ }
+
+ WriteState(np);
+ SendConfigureEvent(np);
+
+}
+
+/** Set the active client. */
+void FocusClient(ClientNode *np) {
+
+ Assert(np);
+
+ if(!(np->state.status & (STAT_MAPPED | STAT_SHADED))) {
+ return;
+ }
+ if(np->state.status & STAT_HIDDEN) {
+ return;
+ }
+
+ if(activeClient != np) {
+ if(activeClient) {
+ activeClient->state.status &= ~STAT_ACTIVE;
+ DrawBorder(activeClient, NULL);
+ }
+ np->state.status |= STAT_ACTIVE;
+ activeClient = np;
+
+ if(!(np->state.status & STAT_SHADED)) {
+ UpdateClientColormap(np);
+ SetWindowAtom(rootWindow, ATOM_NET_ACTIVE_WINDOW, np->window);
+ }
+
+ DrawBorder(np, NULL);
+ UpdatePager();
+ UpdateTaskBar();
+
+ }
+
+ if(np->state.status & STAT_MAPPED && !(np->state.status & STAT_HIDDEN)) {
+ JXSetInputFocus(display, np->window, RevertToPointerRoot, CurrentTime);
+ } else {
+ JXSetInputFocus(display, rootWindow, RevertToPointerRoot, CurrentTime);
+ }
+
+}
+
+/** Focus the next client in the stacking order. */
+void FocusNextStacked(ClientNode *np) {
+
+ int x;
+ ClientNode *tp;
+
+ Assert(np);
+
+ for(tp = np->next; tp; tp = tp->next) {
+ if((tp->state.status & (STAT_MAPPED | STAT_SHADED))
+ && !(tp->state.status & STAT_HIDDEN)) {
+ FocusClient(tp);
+ return;
+ }
+ }
+ for(x = np->state.layer - 1; x >= LAYER_BOTTOM; x--) {
+ for(tp = nodes[x]; tp; tp = tp->next) {
+ if((tp->state.status & (STAT_MAPPED | STAT_SHADED))
+ && !(tp->state.status & STAT_HIDDEN)) {
+ FocusClient(tp);
+ return;
+ }
+ }
+ }
+
+}
+
+/** Refocus the active client (if there is one). */
+void RefocusClient() {
+
+ if(activeClient) {
+ FocusClient(activeClient);
+ }
+
+}
+
+/** Send a delete message to a client. */
+void DeleteClient(ClientNode *np) {
+
+ ClientProtocolType protocols;
+
+ Assert(np);
+
+ protocols = ReadWMProtocols(np->window);
+ if(protocols & PROT_DELETE) {
+ SendClientMessage(np->window, ATOM_WM_PROTOCOLS,
+ ATOM_WM_DELETE_WINDOW);
+ } else {
+ KillClient(np);
+ }
+
+}
+
+/** Callback to kill a client after a confirm dialog. */
+void KillClientHandler(ClientNode *np) {
+
+ Assert(np);
+
+ if(np == activeClient) {
+ FocusNextStacked(np);
+ }
+
+ JXGrabServer(display);
+ JXSync(display, False);
+
+ JXKillClient(display, np->window);
+
+ JXSync(display, True);
+ JXUngrabServer(display);
+
+ RemoveClient(np);
+
+}
+
+/** Kill a client window. */
+void KillClient(ClientNode *np) {
+
+ Assert(np);
+
+ ShowConfirmDialog(np, KillClientHandler,
+ "Kill this window?",
+ "This may cause data to be lost!",
+ NULL);
+}
+
+/** Raise the client. This will affect transients. */
+void RaiseClient(ClientNode *np) {
+
+ ClientNode *tp, *next;
+ int x;
+
+ Assert(np);
+
+ if(nodes[np->state.layer] != np) {
+
+ /* Raise the window */
+ Assert(np->prev);
+ np->prev->next = np->next;
+ if(np->next) {
+ np->next->prev = np->prev;
+ } else {
+ nodeTail[np->state.layer] = np->prev;
+ }
+ np->next = nodes[np->state.layer];
+ nodes[np->state.layer]->prev = np;
+ np->prev = NULL;
+ nodes[np->state.layer] = np;
+
+ /* Place any transient windows on top of the owner */
+ for(x = 0; x < LAYER_COUNT; x++) {
+ for(tp = nodes[x]; tp; tp = tp->next) {
+ if(tp->owner == np->window && tp->prev) {
+
+ next = tp->next;
+
+ tp->prev->next = tp->next;
+ if(tp->next) {
+ tp->next->prev = tp->prev;
+ } else {
+ nodeTail[tp->state.layer] = tp->prev;
+ }
+ tp->next = nodes[tp->state.layer];
+ nodes[tp->state.layer]->prev = tp;
+ tp->prev = NULL;
+ nodes[tp->state.layer] = tp;
+
+ tp = next;
+
+ }
+
+ /* tp will be tp->next if the above code is executed. */
+ /* Thus, if it is NULL, we are done with this layer. */
+ if(!tp) {
+ break;
+ }
+ }
+ }
+
+ RestackClients();
+ }
+
+}
+
+/** Lower the client. This will not affect transients. */
+void LowerClient(ClientNode *np) {
+
+ ClientNode *tp;
+
+ Assert(np);
+
+ if(nodeTail[np->state.layer] != np) {
+
+ Assert(np->next);
+
+ /* Take the client out of the list. */
+ if(np->prev) {
+ np->prev->next = np->next;
+ } else {
+ nodes[np->state.layer] = np->next;
+ }
+ np->next->prev = np->prev;
+
+ /* Place the client at the end of the list. */
+ tp = nodeTail[np->state.layer];
+ nodeTail[np->state.layer] = np;
+ tp->next = np;
+ np->prev = tp;
+ np->next = NULL;
+
+ RestackClients();
+
+ }
+
+}
+
+/** Restack the clients according the way we want them. */
+void RestackClients() {
+
+ TrayType *tp;
+ ClientNode *np;
+ unsigned int layer, index;
+ int trayCount;
+ Window *stack;
+
+ /* Determine how many tray windows exist. */
+ trayCount = 0;
+ for(tp = GetTrays(); tp; tp = tp->next) {
+ ++trayCount;
+ }
+
+ /** Allocate memory for restacking. */
+ stack = AllocateStack((clientCount + trayCount) * sizeof(Window));
+
+ /* Prepare the stacking array. */
+ index = 0;
+ layer = LAYER_TOP;
+ for(;;) {
+
+ for(np = nodes[layer]; np; np = np->next) {
+ if((np->state.status & (STAT_MAPPED | STAT_SHADED))
+ && !(np->state.status & STAT_HIDDEN)) {
+ stack[index++] = np->parent;
+ }
+ }
+
+ for(tp = GetTrays(); tp; tp = tp->next) {
+ if(layer == tp->layer) {
+ stack[index++] = tp->window;
+ }
+ }
+
+ if(layer == 0) {
+ break;
+ }
+ --layer;
+
+ }
+
+ JXRestackWindows(display, stack, index);
+
+ ReleaseStack(stack);
+
+ UpdateNetClientList();
+
+}
+
+/** Send a client message to a window. */
+void SendClientMessage(Window w, AtomType type, AtomType message) {
+
+ XEvent event;
+ int status;
+
+ memset(&event, 0, sizeof(event));
+ event.xclient.type = ClientMessage;
+ event.xclient.window = w;
+ event.xclient.message_type = atoms[type];
+ event.xclient.format = 32;
+ event.xclient.data.l[0] = atoms[message];
+ event.xclient.data.l[1] = CurrentTime;
+
+ status = JXSendEvent(display, w, False, 0, &event);
+ if(status == False) {
+ Debug("SendClientMessage failed");
+ }
+
+}
+
+/** Set the border shape for windows using the shape extension. */
+#ifdef USE_SHAPE
+void SetShape(ClientNode *np) {
+
+ XRectangle rect[4];
+ int north, south, east, west;
+
+ Assert(np);
+
+ np->state.status |= STAT_SHAPE;
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ /* Shaded windows are a special case. */
+ if(np->state.status & STAT_SHADED) {
+
+ rect[0].x = 0;
+ rect[0].y = 0;
+ rect[0].width = np->width + east + west;
+ rect[0].height = north + south;
+
+ JXShapeCombineRectangles(display, np->parent, ShapeBounding,
+ 0, 0, rect, 1, ShapeSet, Unsorted);
+
+ return;
+ }
+
+ /* Add the shape of window. */
+ JXShapeCombineShape(display, np->parent, ShapeBounding, west, north,
+ np->window, ShapeBounding, ShapeSet);
+
+ /* Add the shape of the border. */
+ if(north > 0) {
+
+ /* Top */
+ rect[0].x = 0;
+ rect[0].y = 0;
+ rect[0].width = np->width + east + west;
+ rect[0].height = north;
+
+ /* Left */
+ rect[1].x = 0;
+ rect[1].y = 0;
+ rect[1].width = west;
+ rect[1].height = np->height + north + south;
+
+ /* Right */
+ rect[2].x = np->width + east;
+ rect[2].y = 0;
+ rect[2].width = west;
+ rect[2].height = np->height + north + south;
+
+ /* Bottom */
+ rect[3].x = 0;
+ rect[3].y = np->height + north;
+ rect[3].width = np->width + east + west;
+ rect[3].height = south;
+
+ JXShapeCombineRectangles(display, np->parent, ShapeBounding,
+ 0, 0, rect, 4, ShapeUnion, Unsorted);
+
+ }
+
+}
+#endif /* USE_SHAPE */
+
+/** Remove a client window from management. */
+void RemoveClient(ClientNode *np) {
+
+ ColormapNode *cp;
+
+ Assert(np);
+ Assert(np->window != None);
+ Assert(np->parent != None);
+
+ JXGrabServer(display);
+
+ /* Remove this client from the client list */
+ if(np->next) {
+ np->next->prev = np->prev;
+ } else {
+ nodeTail[np->state.layer] = np->prev;
+ }
+ if(np->prev) {
+ np->prev->next = np->next;
+ } else {
+ nodes[np->state.layer] = np->next;
+ }
+ --clientCount;
+ XDeleteContext(display, np->window, clientContext);
+ XDeleteContext(display, np->parent, frameContext);
+
+ /* Make sure this client isn't active */
+ if(activeClient == np && !shouldExit) {
+ FocusNextStacked(np);
+ }
+ if(activeClient == np) {
+ SetWindowAtom(rootWindow, ATOM_NET_ACTIVE_WINDOW, None);
+ activeClient = NULL;
+ }
+
+ /* If the window manager is exiting (ie, not the client), then
+ * reparent etc. */
+ if(shouldExit && !(np->state.status & STAT_WMDIALOG)) {
+ if(np->state.status & STAT_MAXIMIZED) {
+ np->x = np->oldx;
+ np->y = np->oldy;
+ np->width = np->oldWidth;
+ np->height = np->oldHeight;
+ JXMoveResizeWindow(display, np->window,
+ np->x, np->y, np->width, np->height);
+ }
+ GravitateClient(np, 1);
+ if(!(np->state.status & STAT_MAPPED)
+ && (np->state.status & (STAT_MINIMIZED | STAT_SHADED))) {
+ JXMapWindow(display, np->window);
+ }
+ JXUngrabButton(display, AnyButton, AnyModifier, np->window);
+ JXReparentWindow(display, np->window, rootWindow, np->x, np->y);
+ JXRemoveFromSaveSet(display, np->window);
+ }
+
+ /* Destroy the parent */
+ if(np->parent) {
+ JXDestroyWindow(display, np->parent);
+ }
+
+ if(np->name) {
+ JXFree(np->name);
+ }
+ if(np->instanceName) {
+ JXFree(np->instanceName);
+ }
+ if(np->className) {
+ JXFree(np->className);
+ }
+
+ RemoveClientFromTaskBar(np);
+ RemoveClientStrut(np);
+ UpdatePager();
+
+ while(np->colormaps) {
+ cp = np->colormaps->next;
+ Release(np->colormaps);
+ np->colormaps = cp;
+ }
+
+ DestroyIcon(np->icon);
+
+ Release(np);
+
+ JXUngrabServer(display);
+
+}
+
+/** Get the active client (possibly NULL). */
+ClientNode *GetActiveClient() {
+
+ return activeClient;
+
+}
+
+/** Find a client given a window (searches frame windows too). */
+ClientNode *FindClientByWindow(Window w) {
+
+ ClientNode *np;
+
+ if(!XFindContext(display, w, clientContext, (void*)&np)) {
+ return np;
+ } else {
+ return FindClientByParent(w);
+ }
+
+}
+
+/** Find a client by its frame window. */
+ClientNode *FindClientByParent(Window p) {
+
+ ClientNode *np;
+
+ if(!XFindContext(display, p, frameContext, (void*)&np)) {
+ return np;
+ } else {
+ return NULL;
+ }
+
+}
+
+/** Reparent a client window. */
+void ReparentClient(ClientNode *np, int notOwner) {
+
+ XSetWindowAttributes attr;
+ int attrMask;
+ int x, y, width, height;
+
+ Assert(np);
+
+ if(notOwner) {
+ JXAddToSaveSet(display, np->window);
+
+ attr.event_mask = EnterWindowMask | ColormapChangeMask
+ | PropertyChangeMask | StructureNotifyMask;
+ attr.do_not_propagate_mask = NoEventMask;
+ XChangeWindowAttributes(display, np->window,
+ CWEventMask | CWDontPropagate, &attr);
+
+ }
+ JXGrabButton(display, AnyButton, AnyModifier, np->window,
+ True, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
+ GrabKeys(np);
+
+ attrMask = 0;
+
+ attrMask |= CWOverrideRedirect;
+ attr.override_redirect = True;
+
+ /* We can't use PointerMotionHint mask here since the exact location
+ * of the mouse on the frame is important. */
+ attrMask |= CWEventMask;
+ attr.event_mask
+ = ButtonPressMask
+ | ButtonReleaseMask
+ | ExposureMask
+ | PointerMotionMask
+ | SubstructureRedirectMask
+ | SubstructureNotifyMask
+ | EnterWindowMask
+ | LeaveWindowMask
+ | KeyPressMask;
+
+ attrMask |= CWDontPropagate;
+ attr.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask;
+
+ attrMask |= CWBackPixel;
+ attr.background_pixel = colors[COLOR_BORDER_BG];
+
+ x = np->x;
+ y = np->y;
+ width = np->width;
+ height = np->height;
+ if(np->state.border & BORDER_OUTLINE) {
+ x -= borderWidth;
+ y -= borderWidth;
+ width += borderWidth * 2;
+ height += borderWidth * 2;
+ }
+ if(np->state.border & BORDER_TITLE) {
+ y -= titleHeight;
+ height += titleHeight;
+ }
+
+ /* Create the frame window. */
+ np->parent = JXCreateWindow(display, rootWindow,
+ x, y, width, height, 0, rootDepth, InputOutput,
+ rootVisual, attrMask, &attr);
+
+ /* Update the window to get only the events we want. */
+ attrMask = CWDontPropagate;
+ attr.do_not_propagate_mask
+ = ButtonPressMask
+ | ButtonReleaseMask
+ | PointerMotionMask
+ | ButtonMotionMask
+ | KeyPressMask;
+ JXChangeWindowAttributes(display, np->window, attrMask, &attr);
+ JXSetWindowBorderWidth(display, np->window, 0);
+
+ /* Reparent the client window. */
+ if((np->state.border & BORDER_OUTLINE)
+ && (np->state.border & BORDER_TITLE)) {
+ JXReparentWindow(display, np->window, np->parent,
+ borderWidth, borderWidth + titleHeight);
+ } else if(np->state.border & BORDER_OUTLINE) {
+ JXReparentWindow(display, np->window, np->parent,
+ borderWidth, borderWidth);
+ } else if(np->state.border & BORDER_TITLE) {
+ JXReparentWindow(display, np->window, np->parent,
+ 0, titleHeight);
+ } else {
+ JXReparentWindow(display, np->window, np->parent, 0, 0);
+ }
+
+#ifdef USE_SHAPE
+ if(haveShape) {
+ JXShapeSelectInput(display, np->window, ShapeNotifyMask);
+ CheckShape(np);
+ }
+#endif
+
+}
+
+/** Determine if a window uses the shape extension. */
+#ifdef USE_SHAPE
+void CheckShape(ClientNode *np) {
+
+ int xb, yb;
+ int xc, yc;
+ unsigned int wb, hb;
+ unsigned int wc, hc;
+ Bool boundingShaped, clipShaped;
+
+ JXShapeQueryExtents(display, np->window, &boundingShaped,
+ &xb, &yb, &wb, &hb, &clipShaped, &xc, &yc, &wc, &hc);
+
+ if(boundingShaped == True) {
+ SetShape(np);
+ }
+
+}
+#endif
+
+/** Send a configure event to a client window. */
+void SendConfigureEvent(ClientNode *np) {
+
+ XConfigureEvent event;
+ const ScreenType *sp;
+
+ Assert(np);
+
+ event.type = ConfigureNotify;
+ event.event = np->window;
+ event.window = np->window;
+
+ if(np->state.status & STAT_FULLSCREEN) {
+ sp = GetCurrentScreen(np->x, np->y);
+ event.x = sp->x;
+ event.y = sp->y;
+ event.width = sp->width;
+ event.height = sp->height;
+ } else {
+ event.x = np->x;
+ event.y = np->y;
+ event.width = np->width;
+ event.height = np->height;
+ }
+
+ event.border_width = 0;
+ event.above = None;
+ event.override_redirect = False;
+
+ JXSendEvent(display, np->window, False, StructureNotifyMask,
+ (XEvent*)&event);
+
+}
+
+/** Update a window's colormap.
+ * A call to this function indicates that the colormap(s) for the given
+ * client changed. This will change the active colormap(s) if the given
+ * client is active.
+ */
+void UpdateClientColormap(ClientNode *np) {
+
+ XWindowAttributes attr;
+ ColormapNode *cp;
+ int wasInstalled;
+
+ Assert(np);
+
+ cp = np->colormaps;
+
+ if(np == activeClient) {
+
+ wasInstalled = 0;
+ cp = np->colormaps;
+ while(cp) {
+ if(JXGetWindowAttributes(display, cp->window, &attr)) {
+ if(attr.colormap != None) {
+ if(attr.colormap == np->cmap) {
+ wasInstalled = 1;
+ }
+ JXInstallColormap(display, attr.colormap);
+ }
+ }
+ cp = cp->next;
+ }
+
+ if(!wasInstalled && np->cmap != None) {
+ JXInstallColormap(display, np->cmap);
+ }
+
+ }
+
+}
+
diff --git a/src/client.h b/src/client.h
new file mode 100644
index 0000000..7fc98db
--- /dev/null
+++ b/src/client.h
@@ -0,0 +1,285 @@
+/**
+ * @file client.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header file client window functions.
+ *
+ */
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "border.h"
+#include "hint.h"
+
+/** Window border flags. */
+typedef enum {
+ BORDER_NONE = 0,
+ BORDER_OUTLINE = 1, /**< Window has a border. */
+ BORDER_TITLE = 2, /**< Window has a title bar. */
+ BORDER_MIN = 4, /**< Window supports minimize. */
+ BORDER_MAX = 8, /**< Window supports maximize. */
+ BORDER_CLOSE = 16, /**< Window supports close. */
+ BORDER_RESIZE = 32, /**< Window supports resizing. */
+ BORDER_MOVE = 64 /**< Window supports moving. */
+} BorderFlags;
+
+/** The default border flags. */
+#define BORDER_DEFAULT ( \
+ BORDER_OUTLINE \
+ | BORDER_TITLE \
+ | BORDER_MIN \
+ | BORDER_MAX \
+ | BORDER_CLOSE \
+ | BORDER_RESIZE \
+ | BORDER_MOVE )
+
+/** Window status flags. */
+typedef enum {
+ STAT_NONE = 0,
+ STAT_ACTIVE = 1 << 0, /**< This client has focus. */
+ STAT_MAPPED = 1 << 1, /**< This client is shown (on some desktop). */
+ STAT_MAXIMIZED = 1 << 2, /**< This client is maximized. */
+ STAT_HIDDEN = 1 << 3, /**< This client is not on the current desktop. */
+ STAT_STICKY = 1 << 4, /**< This client is on all desktops. */
+ STAT_NOLIST = 1 << 5, /**< Skip this client in the task list. */
+ STAT_MINIMIZED = 1 << 6, /**< This client is minimized. */
+ STAT_SHADED = 1 << 7, /**< This client is shaded. */
+ STAT_WMDIALOG = 1 << 8, /**< This is a JWM dialog window. */
+ STAT_PIGNORE = 1 << 9, /**< Ignore the program-specified position. */
+ STAT_SHAPE = 1 << 10, /**< This client uses the shape extension. */
+ STAT_SDESKTOP = 1 << 11, /**< This client was minimized to show desktop. */
+ STAT_FULLSCREEN = 1 << 12 /**< This client wants to be full screen. */
+} StatusFlags;
+
+/** Colormap window linked list. */
+typedef struct ColormapNode {
+ Window window; /**< A window containing a colormap. */
+ struct ColormapNode *next; /**< Next value in the linked list. */
+} ColormapNode;
+
+/** The aspect ratio of a window. */
+typedef struct AspectRatio {
+ int minx, miny; /**< The minimum aspect ratio. */
+ int maxx, maxy; /**< The maximum aspect ratio. */
+} AspectRatio;
+
+/** Struture to store information about a client window. */
+typedef struct ClientNode {
+
+ Window window; /**< The client window. */
+ Window parent; /**< The frame window. */
+
+ Window owner; /**< The owner window (for transients). */
+
+ int x, y; /**< The location of the window. */
+ int width, height; /**< The size of the window. */
+ int oldx, oldy; /**< The old location (for maximize). */
+ int oldWidth, oldHeight; /**< The old size (for maximize). */
+
+ long sizeFlags;
+ int baseWidth, baseHeight;
+ int minWidth, minHeight;
+ int maxWidth, maxHeight;
+ int xinc, yinc;
+ AspectRatio aspect;
+ int gravity;
+
+ Colormap cmap;
+ ColormapNode *colormaps;
+
+ char *name;
+ char *instanceName;
+ char *className;
+
+ ClientState state;
+
+ BorderActionType borderAction;
+
+ struct IconNode *icon;
+
+ void (*controller)(int wasDestroyed);
+
+ struct ClientNode *prev; /**< The previous client in this layer. */
+ struct ClientNode *next; /**< The next client in this layer. */
+
+} ClientNode;
+
+/** Client windows in linked lists for each layer. */
+extern ClientNode *nodes[LAYER_COUNT];
+
+/** Client windows in linked lists for each layer (pointer to the tail). */
+extern ClientNode *nodeTail[LAYER_COUNT];
+
+/** Find a client by window or parent window.
+ * @param w The window.
+ * @return The client (NULL if not found).
+ */
+ClientNode *FindClientByWindow(Window w);
+
+/** Find a client by its parent window.
+ * @param p The parent window.
+ * @return The client (NULL if not found).
+ */
+ClientNode *FindClientByParent(Window p);
+
+/** Get the active client.
+ * @return The active client (NULL if no client is active).
+ */
+ClientNode *GetActiveClient();
+
+void InitializeClients();
+void StartupClients();
+void ShutdownClients();
+void DestroyClients();
+
+/** Add a window to management.
+ * @param w The client window.
+ * @param alreadyMapped 1 if the window is mapped, 0 if not.
+ * @param notOwner 1 if JWM doesn't own this window, 0 if JWM is the owner.
+ * @return The client window data.
+ */
+ClientNode *AddClientWindow(Window w, int alreadyMapped, int notOwner);
+
+/** Remove a client from management.
+ * @param np The client to remove.
+ */
+void RemoveClient(ClientNode *np);
+
+/** Minimize a client.
+ * @param np The client to minimize.
+ */
+void MinimizeClient(ClientNode *np);
+
+/** Shade a client.
+ * @param np The client to shade.
+ */
+void ShadeClient(ClientNode *np);
+
+/** Unshade a client.
+ * @param np The client to unshade.
+ */
+void UnshadeClient(ClientNode *np);
+
+/** Set a client's status to withdrawn.
+ * A withdrawn client is a client that is not visible in any way to the
+ * user. This may be a window that an application keeps around so that
+ * it can be reused at a later time.
+ * @param np The client whose status to change.
+ */
+void SetClientWithdrawn(ClientNode *np);
+
+/** Restore a client from minimized state.
+ * @param np The client to restore.
+ * @param raise 1 to raise the client, 0 to leave stacking unchanged.
+ */
+void RestoreClient(ClientNode *np, int raise);
+
+/** Maximize a client.
+ * @param np The client to maximize.
+ */
+void MaximizeClient(ClientNode *np);
+
+/** Set the full screen status of a client.
+ * @param np The client.
+ * @param fullScreen 1 to make full screen, 0 to make not full screen.
+ */
+void SetClientFullScreen(ClientNode *np, int fullScreen);
+
+/** Set the keyboard focus to a client.
+ * @param np The client to focus.
+ */
+void FocusClient(ClientNode *np);
+
+/** Set the keyboard focus to the next client.
+ * This is used to focus the next client in the stacking order.
+ * @param np The client before the client to focus.
+ */
+void FocusNextStacked(ClientNode *np);
+
+/** Set the keyboard focus back to the active client. */
+void RefocusClient();
+
+/** Tell a client to exit.
+ * @param np The client to delete.
+ */
+void DeleteClient(ClientNode *np);
+
+/** Force a client to exit.
+ * @param np The client to kill.
+ */
+void KillClient(ClientNode *np);
+
+/** Raise a client to the top of its layer.
+ * @param np The client to raise.
+ */
+void RaiseClient(ClientNode *np);
+
+/** Lower a client to the bottom of its layer.
+ * @param np The client to lower.
+ */
+void LowerClient(ClientNode *np);
+
+/** Restack the clients.
+ * This is used when a client is mapped so that the stacking order
+ * remains consistent.
+ */
+void RestackClients();
+
+/** Set the layer of a client.
+ * @param np The client whose layer to set.
+ * @param layer the layer to assign to the client.
+ */
+void SetClientLayer(ClientNode *np, unsigned int layer);
+
+/** Set the desktop for a client.
+ * @param np The client.
+ * @parma desktop The desktop to be assigned to the client.
+ */
+void SetClientDesktop(ClientNode *np, unsigned int desktop);
+
+/** Set the sticky status of a client.
+ * A sticky client will appear on all desktops.
+ * @param np The client.
+ * @param isSticky 1 to make the client sticky, 0 to make it not sticky.
+ */
+void SetClientSticky(ClientNode *np, int isSticky);
+
+/** Hide a client.
+ * This is used for changing desktops.
+ * @param np The client to hide.
+ */
+void HideClient(ClientNode *np);
+
+/** Show a client.
+ * This is used for changing desktops.
+ * @param np The client to show.
+ */
+void ShowClient(ClientNode *np);
+
+/** Update a client's colormap.
+ * @param np The client.
+ */
+void UpdateClientColormap(ClientNode *np);
+
+/** Update the shape of a client using the shape extension.
+ * @param np The client to update.
+ */
+void SetShape(ClientNode *np);
+
+/** Send a configure event to a client.
+ * This will send updated location and size information to a client.
+ * @param np The client to get the event.
+ */
+void SendConfigureEvent(ClientNode *np);
+
+/** Send a message to a client.
+ * @param w The client window.
+ * @param type The type of message to send.
+ * @param message The message to send.
+ */
+void SendClientMessage(Window w, AtomType type, AtomType message);
+
+#endif
+
diff --git a/src/clock.c b/src/clock.c
new file mode 100644
index 0000000..147c1f6
--- /dev/null
+++ b/src/clock.c
@@ -0,0 +1,332 @@
+/**
+ * @file clock.c
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Clock tray component.
+ *
+ */
+
+#include "jwm.h"
+#include "clock.h"
+#include "tray.h"
+#include "color.h"
+#include "font.h"
+#include "timing.h"
+#include "main.h"
+#include "root.h"
+#include "cursor.h"
+#include "popup.h"
+#include "misc.h"
+
+/** Structure to respresent a clock tray component. */
+typedef struct ClockType {
+
+ TrayComponentType *cp; /**< Common component data. */
+
+ char *format; /**< The time format to use. */
+ char *command; /**< A command to run when clicked. */
+ char shortTime[80]; /**< Currently displayed time. */
+
+ /* The following are used to control popups. */
+ int mousex; /**< Last mouse x-coordinate. */
+ int mousey; /**< Last mouse y-coordinate. */
+ TimeType mouseTime; /**< Time of the last mouse motion. */
+
+ int userWidth; /**< User-specified clock width (or 0). */
+
+ struct ClockType *next; /**< Next clock in the list. */
+
+} ClockType;
+
+/** The default time format to use. */
+static const char *DEFAULT_FORMAT = "%I:%M %p";
+
+static ClockType *clocks;
+static TimeType lastUpdate = ZERO_TIME;
+
+static void Create(TrayComponentType *cp);
+static void Resize(TrayComponentType *cp);
+static void Destroy(TrayComponentType *cp);
+static void ProcessClockButtonEvent(TrayComponentType *cp,
+ int x, int y, int mask);
+static void ProcessClockMotionEvent(TrayComponentType *cp,
+ int x, int y, int mask);
+
+static void DrawClock(ClockType *clk, const TimeType *now, int x, int y);
+
+/** Initialize clocks. */
+void InitializeClock() {
+ clocks = NULL;
+}
+
+/** Start clock(s). */
+void StartupClock() {
+
+ ClockType *clk;
+
+ for(clk = clocks; clk; clk = clk->next) {
+ if(clk->cp->requestedWidth == 0) {
+ clk->cp->requestedWidth = GetStringWidth(FONT_CLOCK, clk->format) + 4;
+ }
+ if(clk->cp->requestedHeight == 0) {
+ clk->cp->requestedHeight = GetStringHeight(FONT_CLOCK) + 4;
+ }
+ }
+
+}
+
+/** Stop clock(s). */
+void ShutdownClock() {
+}
+
+/** Destroy clock(s). */
+void DestroyClock() {
+
+ ClockType *cp;
+
+ while(clocks) {
+ cp = clocks->next;
+
+ if(clocks->format) {
+ Release(clocks->format);
+ }
+ if(clocks->command) {
+ Release(clocks->command);
+ }
+
+ Release(clocks);
+ clocks = cp;
+ }
+
+}
+
+/** Create a clock tray component. */
+TrayComponentType *CreateClock(const char *format, const char *command,
+ int width, int height) {
+
+ TrayComponentType *cp;
+ ClockType *clk;
+
+ clk = Allocate(sizeof(ClockType));
+ clk->next = clocks;
+ clocks = clk;
+
+ clk->mousex = 0;
+ clk->mousey = 0;
+ clk->mouseTime.seconds = 0;
+ clk->mouseTime.ms = 0;
+ clk->userWidth = 0;
+
+ if(!format) {
+ format = DEFAULT_FORMAT;
+ }
+ clk->format = CopyString(format);
+
+ clk->command = CopyString(command);
+
+ clk->shortTime[0] = 0;
+
+ cp = CreateTrayComponent();
+ cp->object = clk;
+ clk->cp = cp;
+ if(width > 0) {
+ cp->requestedWidth = width;
+ clk->userWidth = 1;
+ } else {
+ cp->requestedWidth = 0;
+ clk->userWidth = 0;
+ }
+ cp->requestedHeight = height;
+
+ cp->Create = Create;
+ cp->Resize = Resize;
+ cp->Destroy = Destroy;
+ cp->ProcessButtonEvent = ProcessClockButtonEvent;
+ cp->ProcessMotionEvent = ProcessClockMotionEvent;
+
+ return cp;
+
+}
+
+/** Initialize a clock tray component. */
+void Create(TrayComponentType *cp) {
+
+ ClockType *clk;
+
+ Assert(cp);
+
+ clk = (ClockType*)cp->object;
+
+ Assert(clk);
+
+ cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height,
+ rootDepth);
+
+ JXSetForeground(display, rootGC, colors[COLOR_CLOCK_BG]);
+ JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, cp->width, cp->height);
+
+}
+
+/** Resize a clock tray component. */
+void Resize(TrayComponentType *cp) {
+
+ ClockType *clk;
+ TimeType now;
+ int x, y;
+
+ Assert(cp);
+
+ clk = (ClockType*)cp->object;
+
+ Assert(clk);
+
+ if(cp->pixmap != None) {
+ JXFreePixmap(display, cp->pixmap);
+ }
+
+ cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height,
+ rootDepth);
+
+ clk->shortTime[0] = 0;
+
+ GetCurrentTime(&now);
+ GetMousePosition(&x, &y);
+ DrawClock(clk, &now, x, y);
+
+}
+
+/** Destroy a clock tray component. */
+void Destroy(TrayComponentType *cp) {
+
+ ClockType *clk;
+
+ Assert(cp);
+
+ clk = (ClockType*)cp->object;
+
+ Assert(clk);
+
+ if(cp->pixmap != None) {
+ JXFreePixmap(display, cp->pixmap);
+ }
+}
+
+/** Process a click event on a clock tray component. */
+void ProcessClockButtonEvent(TrayComponentType *cp, int x, int y, int mask) {
+
+ ClockType *clk;
+
+ Assert(cp);
+
+ clk = (ClockType*)cp->object;
+
+ Assert(clk);
+
+ if(clk->command) {
+ RunCommand(clk->command);
+ }
+
+}
+
+/** Process a motion event on a clock tray component. */
+void ProcessClockMotionEvent(TrayComponentType *cp,
+ int x, int y, int mask) {
+
+ Assert(cp);
+
+ ClockType *clk = (ClockType*)cp->object;
+ clk->mousex = cp->screenx + x;
+ clk->mousey = cp->screeny + y;
+ GetCurrentTime(&clk->mouseTime);
+
+}
+
+/** Update a clock tray component. */
+void SignalClock(const TimeType *now, int x, int y) {
+
+ ClockType *cp;
+ int shouldDraw;
+ char *longTime;
+ time_t t;
+
+ Assert(now);
+
+ /* Determine if we should update the clock(s). */
+ if(GetTimeDifference(&lastUpdate, now) > 900) {
+ shouldDraw = 1;
+ lastUpdate = *now;
+ } else {
+ shouldDraw = 0;
+ }
+
+ /* Update each clock. */
+ for(cp = clocks; cp; cp = cp->next) {
+
+ if(shouldDraw) {
+ DrawClock(cp, now, x, y);
+ }
+
+ if(abs(cp->mousex - x) < POPUP_DELTA
+ && abs(cp->mousey - y) < POPUP_DELTA) {
+ if(GetTimeDifference(now, &cp->mouseTime) >= popupDelay) {
+ time(&t);
+ longTime = asctime(localtime(&t));
+ Trim(longTime);
+ ShowPopup(x, y, longTime);
+ }
+ }
+
+ }
+
+}
+
+/** Draw a clock tray component. */
+void DrawClock(ClockType *clk, const TimeType *now, int x, int y) {
+
+ TrayComponentType *cp;
+ const char *shortTime;
+ int width;
+ int rwidth;
+
+ Assert(clk);
+ Assert(now);
+
+ /* Only draw if the label changed. */
+ shortTime = GetTimeString(clk->format);
+ if(!strcmp(clk->shortTime, shortTime)) {
+ return;
+ }
+ strcpy(clk->shortTime, shortTime);
+
+ cp = clk->cp;
+
+ /* Clear the area. */
+ JXSetForeground(display, rootGC, colors[COLOR_CLOCK_BG]);
+ JXFillRectangle(display, cp->pixmap, rootGC, 0, 0,
+ cp->width, cp->height);
+
+ /* Determine if the clock is the right size. */
+ width = GetStringWidth(FONT_CLOCK, shortTime);
+ rwidth = width + 4;
+ if(rwidth == clk->cp->requestedWidth || clk->userWidth) {
+
+ /* Draw the clock. */
+ RenderString(cp->pixmap, FONT_CLOCK, COLOR_CLOCK_FG,
+ cp->width / 2 - width / 2,
+ cp->height / 2 - GetStringHeight(FONT_CLOCK) / 2,
+ cp->width, NULL, shortTime);
+
+ UpdateSpecificTray(clk->cp->tray, clk->cp);
+
+ } else {
+
+ /* Wrong size. Resize. */
+ clk->cp->requestedWidth = rwidth;
+ ResizeTray(clk->cp->tray);
+
+ }
+
+}
+
+
diff --git a/src/clock.h b/src/clock.h
new file mode 100644
index 0000000..25bd74d
--- /dev/null
+++ b/src/clock.h
@@ -0,0 +1,41 @@
+/**
+ * @file clock.h
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Clock tray component.
+ *
+ */
+
+#ifndef CLOCK_H
+#define CLOCK_H
+
+struct TrayComponentType;
+struct TimeType;
+
+/*@{*/
+void InitializeClock();
+void StartupClock();
+void ShutdownClock();
+void DestroyClock();
+/*@}*/
+
+/** Create a clock component for the tray.
+ * @param format The format of the clock.
+ * @param command The command to execute when the clock is clicked.
+ * @param width The width of the clock (0 for auto).
+ * @param height The height of the clock (0 for auto).
+ */
+struct TrayComponentType *CreateClock(const char *format,
+ const char *command, int width, int height);
+
+/** Update clocks.
+ * This is called on a regular basis to update the time.
+ * @param now The current time.
+ * @param x The x-coordinate of the mouse.
+ * @param y The y-coordinate of the mouse.
+ */
+void SignalClock(const struct TimeType *now, int x, int y);
+
+#endif
+
diff --git a/src/color.c b/src/color.c
new file mode 100644
index 0000000..edf5f7a
--- /dev/null
+++ b/src/color.c
@@ -0,0 +1,606 @@
+/****************************************************************************
+ * Functions to handle loading colors.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "main.h"
+#include "color.h"
+#include "error.h"
+#include "misc.h"
+
+typedef struct {
+ ColorType type;
+ const char *value;
+} DefaultColorNode;
+
+static const float COLOR_DELTA = 0.45;
+
+unsigned long colors[COLOR_COUNT];
+static unsigned long rgbColors[COLOR_COUNT];
+
+static unsigned long *map;
+
+#ifdef USE_XFT
+static XftColor *xftColors[COLOR_COUNT] = { NULL };
+#endif
+
+static DefaultColorNode DEFAULT_COLORS[] = {
+ { COLOR_BORDER_BG, "gray" },
+ { COLOR_BORDER_FG, "black" },
+ { COLOR_BORDER_ACTIVE_BG, "red" },
+ { COLOR_BORDER_ACTIVE_FG, "white" },
+ { COLOR_TRAY_BG, "gray" },
+ { COLOR_TRAY_FG, "black" },
+ { COLOR_TASK_BG, "gray" },
+ { COLOR_TASK_FG, "black" },
+ { COLOR_TASK_ACTIVE_BG, "red" },
+ { COLOR_TASK_ACTIVE_FG, "white" },
+ { COLOR_PAGER_BG, "black" },
+ { COLOR_PAGER_FG, "gray" },
+ { COLOR_PAGER_ACTIVE_BG, "red" },
+ { COLOR_PAGER_ACTIVE_FG, "red" },
+ { COLOR_PAGER_OUTLINE, "black" },
+ { COLOR_MENU_BG, "gray" },
+ { COLOR_MENU_FG, "black" },
+ { COLOR_MENU_ACTIVE_BG, "red" },
+ { COLOR_MENU_ACTIVE_FG, "white" },
+ { COLOR_POPUP_BG, "yellow" },
+ { COLOR_POPUP_FG, "black" },
+ { COLOR_POPUP_OUTLINE, "black" },
+ { COLOR_TRAYBUTTON_FG, "black" },
+ { COLOR_TRAYBUTTON_BG, "gray" },
+ { COLOR_CLOCK_FG, "black" },
+ { COLOR_CLOCK_BG, "gray" },
+ { COLOR_COUNT, NULL }
+};
+
+static char **names = NULL;
+
+static unsigned long redShift;
+static unsigned long greenShift;
+static unsigned long blueShift;
+static unsigned long redMask;
+static unsigned long greenMask;
+static unsigned long blueMask;
+
+static void ComputeShiftMask(unsigned long maskIn,
+ unsigned long *shiftOut, unsigned long *maskOut);
+
+static void GetDirectPixel(XColor *c);
+static void GetMappedPixel(XColor *c);
+
+static int ParseColor(ColorType type, const char *value);
+static void SetDefaultColor(ColorType type);
+
+static unsigned long ReadHex(const char *hex);
+
+static unsigned long GetRGBFromXColor(const XColor *c);
+static XColor GetXColorFromRGB(unsigned long rgb);
+
+static int GetColorByName(const char *str, XColor *c);
+static void InitializeNames();
+
+static void LightenColor(ColorType oldColor, ColorType newColor);
+static void DarkenColor(ColorType oldColor, ColorType newColor);
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeColors() {
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupColors() {
+
+ int x;
+ int red, green, blue;
+ XColor c;
+
+ /* Determine how to convert between RGB triples and pixels. */
+ Assert(rootVisual);
+ switch(rootVisual->class) {
+ case DirectColor:
+ case TrueColor:
+ ComputeShiftMask(rootVisual->red_mask, &redShift, &redMask);
+ ComputeShiftMask(rootVisual->green_mask, &greenShift, &greenMask);
+ ComputeShiftMask(rootVisual->blue_mask, &blueShift, &blueMask);
+ map = NULL;
+ break;
+ default:
+
+ /* Attempt to get 256 colors, pretend it worked. */
+ redMask = 0xE0;
+ greenMask = 0x1C;
+ blueMask = 0x03;
+ ComputeShiftMask(redMask, &redShift, &redMask);
+ ComputeShiftMask(greenMask, &greenShift, &greenMask);
+ ComputeShiftMask(blueMask, &blueShift, &blueMask);
+ map = Allocate(sizeof(unsigned long) * 256);
+
+ /* RGB: 3, 3, 2 */
+ x = 0;
+ for(red = 0; red < 8; red++) {
+ for(green = 0; green < 8; green++) {
+ for(blue = 0; blue < 4; blue++) {
+ c.red = 74898 * red / 8;
+ c.green = 74898 * green / 8;
+ c.blue = 87381 * blue / 4;
+ c.flags = DoRed | DoGreen | DoBlue;
+ JXAllocColor(display, rootColormap, &c);
+ map[x] = c.pixel;
+ ++x;
+ }
+ }
+ }
+
+ break;
+ }
+
+ /* Inherit unset colors from the tray for tray items. */
+ if(names) {
+ if(!names[COLOR_TASK_BG]) {
+ names[COLOR_TASK_BG] = CopyString(names[COLOR_TRAY_BG]);
+ }
+ if(!names[COLOR_TRAYBUTTON_BG]) {
+ names[COLOR_TRAYBUTTON_BG] = CopyString(names[COLOR_TRAY_BG]);
+ }
+ if(!names[COLOR_CLOCK_BG]) {
+ names[COLOR_CLOCK_BG] = CopyString(names[COLOR_TRAY_BG]);
+ }
+ if(!names[COLOR_TASK_FG]) {
+ names[COLOR_TASK_FG] = CopyString(names[COLOR_TRAY_FG]);
+ }
+ if(!names[COLOR_TRAYBUTTON_FG]) {
+ names[COLOR_TRAYBUTTON_FG] = CopyString(names[COLOR_TRAY_FG]);
+ }
+ if(!names[COLOR_CLOCK_FG]) {
+ names[COLOR_CLOCK_FG] = CopyString(names[COLOR_TRAY_FG]);
+ }
+ }
+
+ /* Get color information used for JWM stuff. */
+ for(x = 0; x < COLOR_COUNT; x++) {
+ if(names && names[x]) {
+ if(!ParseColor(x, names[x])) {
+ SetDefaultColor(x);
+ }
+ } else {
+ SetDefaultColor(x);
+ }
+ }
+
+ if(names) {
+ for(x = 0; x < COLOR_COUNT; x++) {
+ if(names[x]) {
+ Release(names[x]);
+ }
+ }
+ Release(names);
+ names = NULL;
+ }
+
+ LightenColor(COLOR_BORDER_BG, COLOR_BORDER_UP);
+ DarkenColor(COLOR_BORDER_BG, COLOR_BORDER_DOWN);
+
+ LightenColor(COLOR_BORDER_ACTIVE_BG, COLOR_BORDER_ACTIVE_UP);
+ DarkenColor(COLOR_BORDER_ACTIVE_BG, COLOR_BORDER_ACTIVE_DOWN);
+
+ LightenColor(COLOR_TRAY_BG, COLOR_TRAY_UP);
+ DarkenColor(COLOR_TRAY_BG, COLOR_TRAY_DOWN);
+
+ LightenColor(COLOR_TASK_BG, COLOR_TASK_UP);
+ DarkenColor(COLOR_TASK_BG, COLOR_TASK_DOWN);
+
+ LightenColor(COLOR_TASK_ACTIVE_BG, COLOR_TASK_ACTIVE_UP);
+ DarkenColor(COLOR_TASK_ACTIVE_BG, COLOR_TASK_ACTIVE_DOWN);
+
+ LightenColor(COLOR_MENU_BG, COLOR_MENU_UP);
+ DarkenColor(COLOR_MENU_BG, COLOR_MENU_DOWN);
+
+ LightenColor(COLOR_MENU_ACTIVE_BG, COLOR_MENU_ACTIVE_UP);
+ DarkenColor(COLOR_MENU_ACTIVE_BG, COLOR_MENU_ACTIVE_DOWN);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownColors() {
+
+#ifdef USE_XFT
+
+ int x;
+
+ for(x = 0; x < COLOR_COUNT; x++) {
+ if(xftColors[x]) {
+ JXftColorFree(display, rootVisual, rootColormap, xftColors[x]);
+ Release(xftColors[x]);
+ xftColors[x] = NULL;
+ }
+ }
+
+#endif
+
+ if(map != NULL) {
+ JXFreeColors(display, rootColormap, map, 256, 0);
+ Release(map);
+ map = NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyColors() {
+
+ int x;
+
+ if(names) {
+ for(x = 0; x < COLOR_COUNT; x++) {
+ if(names[x]) {
+ Release(names[x]);
+ }
+ }
+ Release(names);
+ names = NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ComputeShiftMask(unsigned long maskIn,
+ unsigned long *shiftOut, unsigned long *maskOut) {
+
+ int shift;
+
+ Assert(shiftOut);
+ Assert(maskOut);
+
+ /* Components are stored in 16 bits.
+ * When computing pixels, we'll first shift left 16 bits
+ * so to the shift will be an offset from that 32 bit entity.
+ * shift = 16 - <shift-to-ones> + <shift-to-zeros>
+ */
+
+ shift = 0;
+ *maskOut = maskIn;
+ while(maskIn && (maskIn & (1 << 31)) == 0) {
+ ++shift;
+ maskIn <<= 1;
+ }
+ *shiftOut = shift;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+unsigned long GetRGBFromXColor(const XColor *c) {
+
+ float red, green, blue;
+ unsigned long rgb;
+
+ Assert(c);
+
+ red = (float)c->red / 65535.0;
+ green = (float)c->green / 65535.0;
+ blue = (float)c->blue / 65535.0;
+
+ rgb = (unsigned long)(red * 255.0) << 16;
+ rgb |= (unsigned long)(green * 255.0) << 8;
+ rgb |= (unsigned long)(blue * 255.0);
+
+ return rgb;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+XColor GetXColorFromRGB(unsigned long rgb) {
+
+ XColor ret = { 0 };
+
+ ret.flags = DoRed | DoGreen | DoBlue;
+ ret.red = (unsigned short)(((rgb >> 16) & 0xFF) * 257);
+ ret.green = (unsigned short)(((rgb >> 8) & 0xFF) * 257);
+ ret.blue = (unsigned short)((rgb & 0xFF) * 257);
+
+ return ret;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetColor(ColorType c, const char *value) {
+
+ if(!value) {
+ Warning("empty color tag");
+ return;
+ }
+
+ InitializeNames();
+
+ if(names[c]) {
+ Release(names[c]);
+ }
+
+ names[c] = CopyString(value);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int ParseColor(ColorType type, const char *value) {
+
+ XColor temp;
+ unsigned long rgb;
+
+ if(!value) {
+ return 0;
+ }
+
+ if(value[0] == '#' && strlen(value) == 7) {
+ rgb = ReadHex(value + 1);
+ temp.red = ((rgb >> 16) & 0xFF) * 257;
+ temp.green = ((rgb >> 8) & 0xFF) * 257;
+ temp.blue = (rgb & 0xFF) * 257;
+ temp.flags = DoRed | DoGreen | DoBlue;
+ GetColor(&temp);
+ } else {
+ if(!GetColorByName(value, &temp)) {
+ Warning("bad color: \"%s\"", value);
+ return 0;
+ }
+ }
+ colors[type] = temp.pixel;
+ rgbColors[type] = GetRGBFromXColor(&temp);
+
+ return 1;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetDefaultColor(ColorType type) {
+
+ int x;
+
+ for(x = 0; DEFAULT_COLORS[x].value; x++) {
+ if(DEFAULT_COLORS[x].type == type) {
+ ParseColor(type, DEFAULT_COLORS[x].value);
+ return;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeNames() {
+
+ int x;
+
+ if(names == NULL) {
+ names = Allocate(sizeof(char*) * COLOR_COUNT);
+ for(x = 0; x < COLOR_COUNT; x++) {
+ names[x] = NULL;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+unsigned long ReadHex(const char *hex) {
+
+ unsigned long value = 0;
+ int x;
+
+ Assert(hex);
+
+ for(x = 0; hex[x]; x++) {
+ value *= 16;
+ if(hex[x] >= '0' && hex[x] <= '9') {
+ value += hex[x] - '0';
+ } else if(hex[x] >= 'A' && hex[x] <= 'F') {
+ value += hex[x] - 'A' + 10;
+ } else if(hex[x] >= 'a' && hex[x] <= 'f') {
+ value += hex[x] - 'a' + 10;
+ }
+ }
+
+ return value;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void LightenColor(ColorType oldColor, ColorType newColor) {
+
+ XColor temp;
+ float red, green, blue;
+ float delta = 1.0 + COLOR_DELTA;
+
+ temp = GetXColorFromRGB(rgbColors[oldColor]);
+
+ red = (float)temp.red / 65535.0;
+ green = (float)temp.green / 65535.0;
+ blue = (float)temp.blue / 65535.0;
+
+ red = Min(delta * red, 1.0);
+ green = Min(delta * green, 1.0);
+ blue = Min(delta * blue, 1.0);
+
+ temp.red = red * 65535.0;
+ temp.green = green * 65535.0;
+ temp.blue = blue * 65535.0;
+
+ GetColor(&temp);
+ colors[newColor] = temp.pixel;
+ rgbColors[newColor] = GetRGBFromXColor(&temp);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DarkenColor(ColorType oldColor, ColorType newColor) {
+
+ XColor temp;
+ float red, green, blue;
+ float delta = 1.0 - COLOR_DELTA;
+
+ temp = GetXColorFromRGB(rgbColors[oldColor]);
+
+ red = (float)temp.red / 65535.0;
+ green = (float)temp.green / 65535.0;
+ blue = (float)temp.blue / 65535.0;
+
+ red = delta * red;
+ green = delta * green;
+ blue = delta * blue;
+
+ temp.red = red * 65535.0;
+ temp.green = green * 65535.0;
+ temp.blue = blue * 65535.0;
+
+ GetColor(&temp);
+ colors[newColor] = temp.pixel;
+ rgbColors[newColor] = GetRGBFromXColor(&temp);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int GetColorByName(const char *str, XColor *c) {
+
+ Assert(str);
+ Assert(c);
+
+ if(!JXParseColor(display, rootColormap, str, c)) {
+ return 0;
+ }
+
+ GetColor(c);
+
+ return 1;
+
+}
+
+/***************************************************************************
+ * Compute the RGB components from an index into our RGB colormap.
+ ***************************************************************************/
+void GetColorFromIndex(XColor *c) {
+
+ unsigned long red;
+ unsigned long green;
+ unsigned long blue;
+
+ Assert(c);
+
+ red = (c->pixel & redMask) << redShift;
+ green = (c->pixel & greenMask) << greenShift;
+ blue = (c->pixel & blueMask) << blueShift;
+
+ c->red = red >> 16;
+ c->green = green >> 16;
+ c->blue = blue >> 16;
+
+}
+
+/***************************************************************************
+ * Compute the pixel value from RGB components.
+ ***************************************************************************/
+void GetDirectPixel(XColor *c) {
+
+ unsigned long red;
+ unsigned long green;
+ unsigned long blue;
+
+ Assert(c);
+
+ /* Normalize. */
+ red = c->red << 16;
+ green = c->green << 16;
+ blue = c->blue << 16;
+
+ /* Shift to the correct offsets and mask. */
+ red = (red >> redShift) & redMask;
+ green = (green >> greenShift) & greenMask;
+ blue = (blue >> blueShift) & blueMask;
+
+ /* Combine. */
+ c->pixel = red | green | blue;
+
+}
+
+/***************************************************************************
+ * Compute the pixel value from RGB components.
+ ***************************************************************************/
+void GetMappedPixel(XColor *c) {
+
+ Assert(c);
+
+ GetDirectPixel(c);
+ c->pixel = map[c->pixel];
+
+}
+
+/***************************************************************************
+ * Compute the pixel value from RGB components.
+ ***************************************************************************/
+void GetColor(XColor *c) {
+
+ Assert(c);
+ Assert(rootVisual);
+
+ switch(rootVisual->class) {
+ case DirectColor:
+ case TrueColor:
+ GetDirectPixel(c);
+ return;
+ default:
+ GetMappedPixel(c);
+ return;
+ }
+
+}
+
+/***************************************************************************
+ * When loading images from external sources, we need to know the color
+ * components even if running with a colormap. So here we pretend
+ * we have a linear RGB colormap even if we don't.
+ * This prevents calls to XQueryColor later.
+ ***************************************************************************/
+void GetColorIndex(XColor *c) {
+
+ Assert(c);
+
+ GetDirectPixel(c);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+#ifdef USE_XFT
+XftColor *GetXftColor(ColorType type) {
+
+ unsigned long rgb;
+ XRenderColor rcolor;
+
+ if(!xftColors[type]) {
+ rgb = rgbColors[type];
+ xftColors[type] = Allocate(sizeof(XftColor));
+ rcolor.alpha = 65535;
+ rcolor.red = ((rgb >> 16) & 0xFF) * 257;
+ rcolor.green = ((rgb >> 8) & 0xFF) * 257;
+ rcolor.blue = (rgb & 0xFF) * 257;
+ JXftColorAllocValue(display, rootVisual, rootColormap, &rcolor,
+ xftColors[type]);
+ }
+
+ return xftColors[type];
+
+}
+#endif
+
diff --git a/src/color.h b/src/color.h
new file mode 100644
index 0000000..6e9ba60
--- /dev/null
+++ b/src/color.h
@@ -0,0 +1,91 @@
+/**
+ * @file color.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the color functions.
+ *
+ */
+
+#ifndef COLOR_H
+#define COLOR_H
+
+typedef enum {
+
+ COLOR_BORDER_BG,
+ COLOR_BORDER_FG,
+ COLOR_BORDER_ACTIVE_BG,
+ COLOR_BORDER_ACTIVE_FG,
+
+ COLOR_TRAY_BG,
+ COLOR_TRAY_FG,
+
+ COLOR_TASK_BG,
+ COLOR_TASK_FG,
+ COLOR_TASK_ACTIVE_BG,
+ COLOR_TASK_ACTIVE_FG,
+
+ COLOR_PAGER_BG,
+ COLOR_PAGER_FG,
+ COLOR_PAGER_ACTIVE_BG,
+ COLOR_PAGER_ACTIVE_FG,
+ COLOR_PAGER_OUTLINE,
+
+ COLOR_MENU_BG,
+ COLOR_MENU_FG,
+ COLOR_MENU_ACTIVE_BG,
+ COLOR_MENU_ACTIVE_FG,
+
+ COLOR_BORDER_UP,
+ COLOR_BORDER_DOWN,
+ COLOR_BORDER_ACTIVE_UP,
+ COLOR_BORDER_ACTIVE_DOWN,
+
+ COLOR_TRAY_UP,
+ COLOR_TRAY_DOWN,
+
+ COLOR_TASK_UP,
+ COLOR_TASK_DOWN,
+ COLOR_TASK_ACTIVE_UP,
+ COLOR_TASK_ACTIVE_DOWN,
+
+ COLOR_MENU_UP,
+ COLOR_MENU_DOWN,
+ COLOR_MENU_ACTIVE_UP,
+ COLOR_MENU_ACTIVE_DOWN,
+
+ COLOR_POPUP_BG,
+ COLOR_POPUP_FG,
+ COLOR_POPUP_OUTLINE,
+
+ COLOR_TRAYBUTTON_BG,
+ COLOR_TRAYBUTTON_FG,
+
+ COLOR_CLOCK_BG,
+ COLOR_CLOCK_FG,
+
+ COLOR_COUNT
+
+} ColorType;
+
+extern unsigned long colors[COLOR_COUNT];
+
+/*@{*/
+void InitializeColors();
+void StartupColors();
+void ShutdownColors();
+void DestroyColors();
+/*@}*/
+
+void SetColor(ColorType c, const char *value);
+
+void GetColor(XColor *c);
+void GetColorIndex(XColor *c);
+void GetColorFromIndex(XColor *c);
+
+#ifdef USE_XFT
+XftColor *GetXftColor(ColorType type);
+#endif
+
+#endif
+
diff --git a/src/command.c b/src/command.c
new file mode 100644
index 0000000..b82498d
--- /dev/null
+++ b/src/command.c
@@ -0,0 +1,124 @@
+/**
+ * @file command.c
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Handle running startup, shutdown, and restart commands.
+ *
+ */
+
+#include "jwm.h"
+#include "command.h"
+#include "root.h"
+#include "misc.h"
+#include "main.h"
+
+/** Structure to represent a list of commands. */
+typedef struct CommandNode {
+ char *command; /**< The command. */
+ struct CommandNode *next; /**< The next command in the list. */
+} CommandNode;
+
+static CommandNode *startupCommands;
+static CommandNode *shutdownCommands;
+static CommandNode *restartCommands;
+
+static void RunCommands(CommandNode *commands);
+static void ReleaseCommands(CommandNode **commands);
+static void AddCommand(CommandNode **commands, const char *command);
+
+/** Initialize the command lists. */
+void InitializeCommands() {
+ startupCommands = NULL;
+ shutdownCommands = NULL;
+ restartCommands = NULL;
+}
+
+/** Process startup/restart commands. */
+void StartupCommands() {
+
+ if(isRestarting) {
+ RunCommands(restartCommands);
+ } else {
+ RunCommands(startupCommands);
+ }
+
+}
+
+/** Process shutdown commands. */
+void ShutdownCommands() {
+
+ if(!shouldRestart) {
+ RunCommands(shutdownCommands);
+ }
+
+}
+
+/** Destroy the command lists. */
+void DestroyCommands() {
+ ReleaseCommands(&startupCommands);
+ ReleaseCommands(&shutdownCommands);
+ ReleaseCommands(&restartCommands);
+}
+
+/** Run the commands in a command list. */
+void RunCommands(CommandNode *commands) {
+
+ CommandNode *cp;
+
+ for(cp = commands; cp; cp = cp->next) {
+ RunCommand(cp->command);
+ }
+
+}
+
+/** Release a command list. */
+void ReleaseCommands(CommandNode **commands) {
+
+ CommandNode *cp;
+
+ Assert(commands);
+
+ while(*commands) {
+ cp = (*commands)->next;
+ Release((*commands)->command);
+ Release(*commands);
+ *commands = cp;
+ }
+
+}
+
+/** Add a command to a command list. */
+void AddCommand(CommandNode **commands, const char *command) {
+
+ CommandNode *cp;
+
+ Assert(commands);
+
+ if(!command) {
+ return;
+ }
+
+ cp = Allocate(sizeof(CommandNode));
+ cp->next = *commands;
+ *commands = cp;
+
+ cp->command = CopyString(command);
+
+}
+
+/** Add a startup command. */
+void AddStartupCommand(const char *command) {
+ AddCommand(&startupCommands, command);
+}
+
+/** Add a shutdown command. */
+void AddShutdownCommand(const char *command) {
+ AddCommand(&shutdownCommands, command);
+}
+
+/** Add a restart command. */
+void AddRestartCommand(const char *command) {
+ AddCommand(&restartCommands, command);
+}
+
diff --git a/src/command.h b/src/command.h
new file mode 100644
index 0000000..7c05e05
--- /dev/null
+++ b/src/command.h
@@ -0,0 +1,36 @@
+/**
+ * @file command.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Handle running startup, shutdown, and restart commands.
+ *
+ */
+
+#ifndef COMMAND_H
+#define COMMAND_H
+
+/*@{*/
+void InitializeCommands();
+void StartupCommands();
+void ShutdownCommands();
+void DestroyCommands();
+/*@}*/
+
+/** Add a command to be executed at startup.
+ * @param command The command to execute.
+ */
+void AddStartupCommand(const char *command);
+
+/** Add a command to be executed at shutdown.
+ * @param command The command to execute.
+ */
+void AddShutdownCommand(const char *command);
+
+/** Add a command to be executed after a restart.
+ * @param command The command to execute.
+ */
+void AddRestartCommand(const char *command);
+
+#endif
+
diff --git a/src/confirm.c b/src/confirm.c
new file mode 100644
index 0000000..77aa48e
--- /dev/null
+++ b/src/confirm.c
@@ -0,0 +1,412 @@
+/**
+ * @file confirm.c
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Confirm dialog functions.
+ *
+ */
+
+#include "jwm.h"
+#include "confirm.h"
+#include "client.h"
+#include "main.h"
+#include "font.h"
+#include "button.h"
+#include "screen.h"
+#include "color.h"
+#include "misc.h"
+
+#ifndef DISABLE_CONFIRM
+
+typedef struct DialogType {
+
+ int x, y;
+ int width, height;
+ int lineHeight;
+
+ int okx;
+ int cancelx;
+ int buttony;
+ int buttonWidth, buttonHeight;
+
+ int lineCount;
+ char **message;
+
+ ClientNode *node;
+
+ void (*action)(ClientNode*);
+ ClientNode *client;
+
+ struct DialogType *prev;
+ struct DialogType *next;
+
+} DialogType;
+
+static const char *OK_STRING = "Ok";
+static const char *CANCEL_STRING = "Cancel";
+
+static DialogType *dialogList = NULL;
+
+static int minWidth = 0;
+
+static void DrawConfirmDialog(DialogType *d);
+static void DestroyConfirmDialog(DialogType *d);
+static void ComputeDimensions(DialogType *d);
+static void DrawMessage(DialogType *d);
+static void DrawButtons(DialogType *d);
+static DialogType *FindDialogByWindow(Window w);
+static int HandleDialogExpose(const XExposeEvent *event);
+static int HandleDialogButtonRelease(const XButtonEvent *event);
+
+/** Initialize the dialog processing data. */
+void InitializeDialogs() {
+}
+
+/** Startup dialog processing. */
+void StartupDialogs() {
+}
+
+/** Stop dialog processing. */
+void ShutdownDialogs() {
+
+ while(dialogList) {
+ DestroyConfirmDialog(dialogList);
+ }
+
+}
+
+/** Destroy dialog processing data. */
+void DestroyDialogs() {
+}
+
+/** Handle an event on a dialog window. */
+int ProcessDialogEvent(const XEvent *event) {
+
+ int handled = 0;
+
+ Assert(event);
+
+ switch(event->type) {
+ case Expose:
+ return HandleDialogExpose(&event->xexpose);
+ case ButtonRelease:
+ return HandleDialogButtonRelease(&event->xbutton);
+ default:
+ break;
+ }
+
+ return handled;
+
+}
+
+/** Handle an expose event. */
+int HandleDialogExpose(const XExposeEvent *event) {
+
+ DialogType *dp;
+
+ Assert(event);
+
+ dp = FindDialogByWindow(event->window);
+ if(dp) {
+ DrawConfirmDialog(dp);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/** Handle a mouse button release event. */
+int HandleDialogButtonRelease(const XButtonEvent *event) {
+
+ DialogType *dp;
+ int x, y;
+ int cancelPressed, okPressed;
+
+ Assert(event);
+
+ dp = FindDialogByWindow(event->window);
+ if(dp) {
+ cancelPressed = 0;
+ okPressed = 0;
+ y = event->y;
+ if(y >= dp->buttony && y < dp->buttony + dp->buttonHeight) {
+ x = event->x;
+ if(x >= dp->okx && x < dp->okx + dp->buttonWidth) {
+ okPressed = 1;
+ } else if(x >= dp->cancelx && x < dp->cancelx + dp->buttonWidth) {
+ cancelPressed = 1;
+ }
+ }
+
+ if(okPressed) {
+ (dp->action)(dp->client);
+ }
+
+ if(cancelPressed || okPressed) {
+ DestroyConfirmDialog(dp);
+ }
+
+ return 1;
+ } else {
+ return 0;
+ }
+
+}
+
+/** Find a dialog by window or frame. */
+DialogType *FindDialogByWindow(Window w) {
+
+ DialogType *dp;
+
+ for(dp = dialogList; dp; dp = dp->next) {
+ if(dp->node->window == w || dp->node->parent == w) {
+ return dp;
+ }
+ }
+
+ return NULL;
+
+}
+
+/** Show a confirm dialog. */
+void ShowConfirmDialog(ClientNode *np, void (*action)(ClientNode*), ...) {
+
+ va_list ap;
+ DialogType *dp;
+ XSetWindowAttributes attrs;
+ XSizeHints shints;
+ Window window;
+ char *str;
+ int x;
+
+ Assert(action);
+
+ dp = Allocate(sizeof(DialogType));
+ dp->client = np;
+ dp->action = action;
+
+ dp->prev = NULL;
+ dp->next = dialogList;
+ if(dialogList) {
+ dialogList->prev = dp;
+ }
+ dialogList = dp;
+
+ /* Get the number of lines. */
+ va_start(ap, action);
+ for(dp->lineCount = 0; va_arg(ap, char*); dp->lineCount++);
+ va_end(ap);
+
+ dp->message = Allocate(dp->lineCount * sizeof(char*));
+ va_start(ap, action);
+ for(x = 0; x < dp->lineCount; x++) {
+ str = va_arg(ap, char*);
+ dp->message[x] = CopyString(str);
+ }
+ va_end(ap);
+
+ ComputeDimensions(dp);
+
+ attrs.background_pixel = colors[COLOR_MENU_BG];
+ attrs.event_mask = ButtonReleaseMask | ExposureMask;
+
+ window = JXCreateWindow(display, rootWindow,
+ dp->x, dp->y, dp->width, dp->height, 0,
+ CopyFromParent, InputOutput, CopyFromParent,
+ CWBackPixel | CWEventMask, &attrs);
+
+ shints.x = dp->x;
+ shints.y = dp->y;
+ shints.flags = PPosition;
+ JXSetWMNormalHints(display, window, &shints);
+
+ JXStoreName(display, window, "Confirm");
+
+ dp->node = AddClientWindow(window, 0, 0);
+ Assert(dp->node);
+ if(np) {
+ dp->node->owner = np->window;
+ }
+ dp->node->state.status |= STAT_WMDIALOG;
+ FocusClient(dp->node);
+
+ DrawConfirmDialog(dp);
+
+ JXGrabButton(display, AnyButton, AnyModifier, window,
+ True, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None);
+
+}
+
+/** Draw a confirm dialog. */
+void DrawConfirmDialog(DialogType *dp) {
+
+ Assert(dp);
+
+ DrawMessage(dp);
+ DrawButtons(dp);
+
+}
+
+/** Destroy a confirm dialog. */
+void DestroyConfirmDialog(DialogType *dp) {
+
+ int x;
+
+ Assert(dp);
+
+ /* This will take care of destroying the dialog window since
+ * its parent will be destroyed. */
+ RemoveClient(dp->node);
+
+ for(x = 0; x < dp->lineCount; x++) {
+ Release(dp->message[x]);
+ }
+ Release(dp->message);
+
+ if(dp->next) {
+ dp->next->prev = dp->prev;
+ }
+ if(dp->prev) {
+ dp->prev->next = dp->next;
+ } else {
+ dialogList = dp->next;
+ }
+ Release(dp);
+
+}
+
+/** Compute the size of a dialog window. */
+void ComputeDimensions(DialogType *dp) {
+
+ const ScreenType *sp;
+ int width;
+ int x;
+
+ Assert(dp);
+
+ if(!minWidth) {
+ minWidth = GetStringWidth(FONT_MENU, CANCEL_STRING) * 3;
+ width = GetStringWidth(FONT_MENU, OK_STRING) * 3;
+ if(width > minWidth) {
+ minWidth = width;
+ }
+ minWidth += 30;
+ }
+ dp->width = minWidth;
+
+ for(x = 0; x < dp->lineCount; x++) {
+ width = GetStringWidth(FONT_MENU, dp->message[x]);
+ if(width > dp->width) {
+ dp->width = width;
+ }
+ }
+ dp->lineHeight = GetStringHeight(FONT_MENU);
+ dp->width += 8;
+ dp->height = (dp->lineCount + 2) * dp->lineHeight;
+
+ if(dp->client) {
+
+ dp->x = dp->client->x + dp->client->width / 2 - dp->width / 2;
+ dp->y = dp->client->y + dp->client->height / 2 - dp->height / 2;
+
+ if(dp->x < 0) {
+ dp->x = 0;
+ }
+ if(dp->y < 0) {
+ dp->y = 0;
+ }
+ if(dp->x + dp->width >= rootWidth) {
+ dp->x = rootWidth - dp->width - (borderWidth * 2);
+ }
+ if(dp->y + dp->height >= rootHeight) {
+ dp->y = rootHeight - dp->height - (borderWidth * 2 + titleHeight);
+ }
+
+ } else {
+
+ sp = GetMouseScreen();
+
+ dp->x = sp->width / 2 - dp->width / 2 + sp->x;
+ dp->y = sp->height / 2 - dp->height / 2 + sp->y;
+
+ }
+
+}
+
+/** Display the message on the dialog window. */
+void DrawMessage(DialogType *dp) {
+
+ int yoffset;
+ int x;
+
+ Assert(dp);
+
+ yoffset = 4;
+ for(x = 0; x < dp->lineCount; x++) {
+ RenderString(dp->node->window, FONT_MENU, COLOR_MENU_FG,
+ 4, yoffset, dp->width, NULL, dp->message[x]);
+ yoffset += dp->lineHeight;
+ }
+
+}
+
+/** Draw the buttons on the dialog window. */
+void DrawButtons(DialogType *dp) {
+
+ ButtonNode button;
+ int temp;
+
+ Assert(dp);
+
+ dp->buttonWidth = GetStringWidth(FONT_MENU, CANCEL_STRING);
+ temp = GetStringWidth(FONT_MENU, OK_STRING);
+ if(temp > dp->buttonWidth) {
+ dp->buttonWidth = temp;
+ }
+ dp->buttonWidth += 8;
+ dp->buttonHeight = dp->lineHeight + 4;
+
+ ResetButton(&button, dp->node->window, rootGC);
+ button.font = FONT_MENU;
+ button.width = dp->buttonWidth;
+ button.height = dp->buttonHeight;
+ button.alignment = ALIGN_CENTER;
+
+ dp->okx = dp->width / 3 - dp->buttonWidth / 2;
+ dp->cancelx = 2 * dp->width / 3 - dp->buttonWidth / 2;
+ dp->buttony = dp->height - dp->lineHeight - dp->lineHeight / 2;
+
+ button.type = BUTTON_MENU;
+ button.text = OK_STRING;
+ button.x = dp->okx;
+ button.y = dp->buttony;
+ DrawButton(&button);
+
+ button.text = CANCEL_STRING;
+ button.x = dp->cancelx;
+ button.y = dp->buttony;
+ DrawButton(&button);
+
+}
+
+#else /* DISABLE_CONFIRM */
+
+/** Process an event on a dialog window. */
+int ProcessDialogEvent(const XEvent *event) {
+ return 0;
+}
+
+/** Show a confirm dialog. */
+void ShowConfirmDialog(ClientNode *np, void (*action)(ClientNode*), ...) {
+
+ Assert(action);
+
+ (action)(np);
+
+}
+
+#endif /* DISABLE_CONFIRM */
+
+
+
diff --git a/src/confirm.h b/src/confirm.h
new file mode 100644
index 0000000..21161f2
--- /dev/null
+++ b/src/confirm.h
@@ -0,0 +1,36 @@
+/**
+ * @file confirm.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the confirm dialog functions.
+ *
+ */
+
+#ifndef CONFIRM_H
+#define CONFIRM_H
+
+struct ClientNode;
+
+/*@{*/
+void InitializeDialogs();
+void StartupDialogs();
+void ShutdownDialogs();
+void DestroyDialogs();
+/*@}*/
+
+/** Handle an event on a dialog window.
+ * @param event The event.
+ * @return 1 if handled, 0 if not handled.
+ */
+int ProcessDialogEvent(const XEvent *event);
+
+/** Show a confirm dialog.
+ * @param np A client window associated with the dialog.
+ * @param action A callback to run if "OK" is clicked.
+ */
+void ShowConfirmDialog(struct ClientNode *np,
+ void (*action)(struct ClientNode*), ...);
+
+#endif
+
diff --git a/src/cursor.c b/src/cursor.c
new file mode 100644
index 0000000..b4a6ca7
--- /dev/null
+++ b/src/cursor.c
@@ -0,0 +1,313 @@
+/****************************************************************************
+ * Functions to handle the mouse cursor.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "cursor.h"
+#include "main.h"
+#include "error.h"
+
+static Cursor defaultCursor;
+static Cursor moveCursor;
+static Cursor northCursor;
+static Cursor southCursor;
+static Cursor eastCursor;
+static Cursor westCursor;
+static Cursor northEastCursor;
+static Cursor northWestCursor;
+static Cursor southEastCursor;
+static Cursor southWestCursor;
+static Cursor chooseCursor;
+
+static Cursor GetResizeCursor(BorderActionType action);
+static Cursor CreateCursor(unsigned int shape);
+
+static int mousex;
+static int mousey;
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeCursors() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupCursors() {
+
+ Window win1, win2;
+ int winx, winy;
+ unsigned int mask;
+
+ defaultCursor = CreateCursor(XC_left_ptr);
+ moveCursor = CreateCursor(XC_fleur);
+ northCursor = CreateCursor(XC_top_side);
+ southCursor = CreateCursor(XC_bottom_side);
+ eastCursor = CreateCursor(XC_right_side);
+ westCursor = CreateCursor(XC_left_side);
+ northEastCursor = CreateCursor(XC_ur_angle);
+ northWestCursor = CreateCursor(XC_ul_angle);
+ southEastCursor = CreateCursor(XC_lr_angle);
+ southWestCursor = CreateCursor(XC_ll_angle);
+ chooseCursor = CreateCursor(XC_tcross);
+
+ JXQueryPointer(display, rootWindow, &win1, &win2,
+ &mousex, &mousey, &winx, &winy, &mask);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+Cursor CreateCursor(unsigned int shape) {
+ return JXCreateFontCursor(display, shape);
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownCursors() {
+
+ JXFreeCursor(display, defaultCursor);
+ JXFreeCursor(display, moveCursor);
+ JXFreeCursor(display, northCursor);
+ JXFreeCursor(display, southCursor);
+ JXFreeCursor(display, eastCursor);
+ JXFreeCursor(display, westCursor);
+ JXFreeCursor(display, northEastCursor);
+ JXFreeCursor(display, northWestCursor);
+ JXFreeCursor(display, southEastCursor);
+ JXFreeCursor(display, southWestCursor);
+ JXFreeCursor(display, chooseCursor);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyCursors() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+Cursor GetFrameCursor(BorderActionType action) {
+
+ switch(action & 0x0F) {
+ case BA_RESIZE:
+ return GetResizeCursor(action);
+ case BA_CLOSE:
+ break;
+ case BA_MAXIMIZE:
+ break;
+ case BA_MINIMIZE:
+ break;
+ case BA_MOVE:
+ break;
+ default:
+ break;
+ }
+ return defaultCursor;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+Cursor GetResizeCursor(BorderActionType action) {
+
+ if(action & BA_RESIZE_N) {
+ if(action & BA_RESIZE_E) {
+ return northEastCursor;
+ } else if(action & BA_RESIZE_W) {
+ return northWestCursor;
+ } else {
+ return northCursor;
+ }
+ } else if(action & BA_RESIZE_S) {
+ if(action & BA_RESIZE_E) {
+ return southEastCursor;
+ } else if(action & BA_RESIZE_W) {
+ return southWestCursor;
+ } else {
+ return southCursor;
+ }
+ } else {
+ if(action & BA_RESIZE_E) {
+ return eastCursor;
+ } else {
+ return westCursor;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GrabMouseForResize(BorderActionType action) {
+
+ Cursor cur;
+ int result;
+
+ cur = GetFrameCursor(action);
+
+ result = JXGrabPointer(display, rootWindow, False, ButtonPressMask
+ | ButtonReleaseMask | PointerMotionMask, GrabModeAsync,
+ GrabModeAsync, None, cur, CurrentTime);
+
+ if(result == GrabSuccess) {
+ return 1;
+ } else {
+ return 0;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GrabMouseForMove() {
+
+ int result;
+
+ result = JXGrabPointer(display, rootWindow, False,
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
+ GrabModeAsync, GrabModeAsync, None, moveCursor, CurrentTime);
+
+ if(result == GrabSuccess) {
+
+ return 1;
+
+ } else {
+
+ return 0;
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GrabMouseForMenu() {
+
+ int result;
+
+ result = JXGrabPointer(display, rootWindow, False,
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
+ GrabModeAsync, GrabModeAsync, None, defaultCursor, CurrentTime);
+
+ if(result == GrabSuccess) {
+ return 1;
+ } else {
+ return 0;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GrabMouseForChoose() {
+
+ int result;
+
+ result = JXGrabPointer(display, rootWindow, False,
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
+ GrabModeAsync, GrabModeAsync, None, chooseCursor, CurrentTime);
+
+ if(result == GrabSuccess) {
+ return 1;
+ } else {
+ return 0;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetDefaultCursor(Window w) {
+
+ JXDefineCursor(display, w, defaultCursor);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void MoveMouse(Window win, int x, int y) {
+
+ Window win1, win2;
+ int winx, winy;
+ unsigned int mask;
+
+ JXWarpPointer(display, None, win, 0, 0, 0, 0, x, y);
+
+ JXQueryPointer(display, rootWindow, &win1, &win2,
+ &mousex, &mousey, &winx, &winy, &mask);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetMousePosition(int x, int y) {
+
+ mousex = x;
+ mousey = y;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void GetMousePosition(int *x, int *y) {
+
+ Assert(x);
+ Assert(y);
+
+ *x = mousex;
+ *y = mousey;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+unsigned int GetMouseMask() {
+
+ Window win1, win2;
+ int winx, winy;
+ unsigned int mask;
+
+ JXQueryPointer(display, rootWindow, &win1, &win2,
+ &mousex, &mousey, &winx, &winy, &mask);
+
+ return mask;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetDoubleClickSpeed(const char *str) {
+
+ int speed;
+
+ if(str) {
+ speed = atoi(str);
+ if(speed < MIN_DOUBLE_CLICK_SPEED || speed > MAX_DOUBLE_CLICK_SPEED) {
+ Warning("invalid DoubleClickSpeed: %d", speed);
+ doubleClickSpeed = DEFAULT_DOUBLE_CLICK_SPEED;
+ } else {
+ doubleClickSpeed = speed;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetDoubleClickDelta(const char *str) {
+
+ int delta;
+
+ if(str) {
+ delta = atoi(str);
+ if(delta < MIN_DOUBLE_CLICK_DELTA || delta > MAX_DOUBLE_CLICK_DELTA) {
+ Warning("invalid DoubleClickDelta: %d", delta);
+ doubleClickDelta = DEFAULT_DOUBLE_CLICK_DELTA;
+ } else {
+ doubleClickDelta = delta;
+ }
+ }
+
+}
+
diff --git a/src/cursor.h b/src/cursor.h
new file mode 100644
index 0000000..b8b6868
--- /dev/null
+++ b/src/cursor.h
@@ -0,0 +1,44 @@
+/**
+ * @file confirm.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the cursor functions.
+ *
+ */
+
+#ifndef CURSOR_H
+#define CURSOR_H
+
+#include "border.h"
+
+/*@{*/
+void InitializeCursors();
+void StartupCursors();
+void ShutdownCursors();
+void DestroyCursors();
+/*@}*/
+
+int GrabMouseForResize(BorderActionType action);
+int GrabMouseForMove();
+
+int GrabMouseForMenu();
+int GrabMouseForChoose();
+
+Cursor GetFrameCursor(BorderActionType action);
+
+void MoveMouse(Window win, int x, int y);
+
+void SetMousePosition(int x, int y);
+void GetMousePosition(int *x, int *y);
+
+unsigned int GetMouseMask();
+
+void SetDefaultCursor(Window w);
+
+void SetDoubleClickSpeed(const char *str);
+void SetDoubleClickDelta(const char *str);
+
+#endif
+
+
diff --git a/src/debug.c b/src/debug.c
new file mode 100644
index 0000000..6527abd
--- /dev/null
+++ b/src/debug.c
@@ -0,0 +1,396 @@
+/***************************************************************************
+ * Debug functions.
+ * Copyright (C) 2003 Joe Wingbermuehle
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ***************************************************************************/
+
+#include "debug.h"
+
+/***************************************************************************
+ * Emit a message.
+ ***************************************************************************/
+void Debug(const char *str, ...) {
+#ifdef DEBUG
+ va_list ap;
+ va_start(ap, str);
+
+ Assert(str);
+
+ fprintf(stderr, "DEBUG: ");
+ vfprintf(stderr, str, ap);
+ fprintf(stderr, "\n");
+
+ va_end(ap);
+#endif
+}
+
+#ifdef DEBUG
+
+#define CHECKPOINT_LIST_SIZE 8
+
+typedef struct MemoryType {
+ const char *file;
+ unsigned int line;
+ size_t size;
+ void *pointer;
+ struct MemoryType *next;
+} MemoryType;
+
+static MemoryType *allocations = NULL;
+
+typedef struct ResourceType {
+ int resource;
+ const char *allocationFiles[CHECKPOINT_LIST_SIZE];
+ unsigned int allocationLines[CHECKPOINT_LIST_SIZE];
+ const char *releaseFiles[CHECKPOINT_LIST_SIZE];
+ unsigned int releaseLines[CHECKPOINT_LIST_SIZE];
+ unsigned int allocationOffset;
+ unsigned int releaseOffset;
+ size_t count;
+ struct ResourceType *next;
+} ResourceType;
+
+static ResourceType *resources = NULL;
+
+static const char *checkpointFile[CHECKPOINT_LIST_SIZE];
+static unsigned int checkpointLine[CHECKPOINT_LIST_SIZE];
+static int checkpointOffset;
+
+static void DEBUG_PrintResourceStack(ResourceType *rp);
+
+/***************************************************************************
+ * Start the debugger.
+ ***************************************************************************/
+void DEBUG_StartDebug(const char *file, unsigned int line) {
+ int x;
+
+ Debug("%s[%u]: debug mode started", file, line);
+
+ checkpointOffset = 0;
+ for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) {
+ checkpointFile[x] = NULL;
+ checkpointLine[x] = 0;
+ }
+
+}
+
+/***************************************************************************
+ * Stop the debugger.
+ ***************************************************************************/
+void DEBUG_StopDebug(const char *file, unsigned int line) {
+ MemoryType *mp;
+ ResourceType *rp;
+ unsigned int count = 0;
+
+ Debug("%s[%u]: debug mode stopped", file, line);
+
+ if(allocations) {
+ Debug("MEMORY: memory leaks follow");
+ for(mp = allocations; mp; mp = mp->next) {
+ Debug(" %u bytes in %s at line %u",
+ mp->size, mp->file, mp->line);
+ ++count;
+ }
+ if(count == 1) {
+ Debug("MEMORY: 1 memory leak");
+ } else {
+ Debug("MEMORY: %u memory leaks", count);
+ }
+ } else {
+ Debug("MEMORY: no memory leaks");
+ }
+
+ if(resources) {
+ for(rp = resources; rp; rp = rp->next) {
+ if(rp->count > 0) {
+ Debug("RESOURCE: resource %d has reference count %u",
+ rp->resource, rp->count);
+ DEBUG_PrintResourceStack(rp);
+ }
+ }
+ }
+
+}
+
+/***************************************************************************
+ * Print the resource allocation/release stacks for a resource.
+ ***************************************************************************/
+void DEBUG_PrintResourceStack(ResourceType *rp) {
+ unsigned int x, offset;
+
+ Debug(" Allocation stack: (oldest)");
+ offset = rp->allocationOffset;
+ for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) {
+ if(rp->allocationFiles[offset]) {
+ Debug(" %s line %u", rp->allocationFiles[offset],
+ rp->allocationLines[offset]);
+ }
+ offset = (offset + 1) % CHECKPOINT_LIST_SIZE;
+ }
+ Debug(" Release stack: (oldest)");
+ offset = rp->releaseOffset;
+ for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) {
+ if(rp->releaseFiles[offset]) {
+ Debug(" %s line %u", rp->releaseFiles[offset],
+ rp->releaseLines[offset]);
+ }
+ offset = (offset + 1) % CHECKPOINT_LIST_SIZE;
+ }
+}
+
+/***************************************************************************
+ * Set a checkpoint.
+ ***************************************************************************/
+void DEBUG_SetCheckpoint(const char *file, unsigned int line) {
+
+ checkpointFile[checkpointOffset] = file;
+ checkpointLine[checkpointOffset] = line;
+
+ checkpointOffset = (checkpointOffset + 1) % CHECKPOINT_LIST_SIZE;
+
+}
+
+/***************************************************************************
+ * Display the location of the last checkpoint.
+ ***************************************************************************/
+void DEBUG_ShowCheckpoint() {
+ int x, offset;
+
+ Debug("CHECKPOINT LIST (oldest)");
+ offset = checkpointOffset;
+ for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) {
+ if(checkpointFile[offset]) {
+ Debug(" %s[%u]", checkpointFile[offset], checkpointLine[offset]);
+ }
+ offset = (offset + 1) % CHECKPOINT_LIST_SIZE;
+ }
+ Debug("END OF CHECKPOINT LIST (most recent)");
+
+}
+
+/***************************************************************************
+ * Allocate memory and log.
+ ***************************************************************************/
+void *DEBUG_Allocate(size_t size, const char *file, unsigned int line) {
+ MemoryType *mp;
+
+ if(size <= 0) {
+ Debug("MEMORY: %s[%u]: Attempt to allocate %d bytes of memory",
+ file, line, size);
+ }
+
+ mp = (MemoryType*)malloc(sizeof(MemoryType));
+ Assert(mp);
+
+ mp->file = file;
+ mp->line = line;
+ mp->size = size;
+
+ mp->pointer = malloc(size + sizeof(char));
+ if(!mp->pointer) {
+ Debug("MEMORY: %s[%u]: Memory allocation failed (%d bytes)",
+ file, line, size);
+ Assert(0);
+ }
+
+ /* Make uninitialized accesses easy to find. */
+ memset(mp->pointer, 85, size);
+
+ /* Canary value for buffer overflow checking. */
+ ((char*)mp->pointer)[size] = 42;
+
+ mp->next = allocations;
+ allocations = mp;
+
+ return mp->pointer;
+}
+
+/***************************************************************************
+ * Reallocate memory and log.
+ ***************************************************************************/
+void *DEBUG_Reallocate(void *ptr, size_t size, const char *file,
+ unsigned int line) {
+
+ MemoryType *mp;
+
+ if(size <= 0) {
+ Debug("MEMORY: %s[%u]: Attempt to reallocate %d bytes of memory",
+ file, line, size);
+ }
+ if(!ptr) {
+ Debug("MEMORY: %s[%u]: Attempt to reallocate NULL pointer. "
+ "Calling Allocate...", file, line);
+ return DEBUG_Allocate(size, file, line);
+ } else {
+
+ for(mp = allocations; mp; mp = mp->next) {
+ if(mp->pointer == ptr) {
+
+ if(((char*)ptr)[mp->size] != 42) {
+ Debug("MEMORY: %s[%u]: The canary is dead.", file, line);
+ }
+
+ mp->file = file;
+ mp->line = line;
+ mp->size = size;
+ mp->pointer = realloc(ptr, size + sizeof(char));
+ if(!mp->pointer) {
+ Debug("MEMORY: %s[%u]: Failed to reallocate %d bytes.",
+ file, line, size);
+ Assert(0);
+ }
+ ((char*)mp->pointer)[size] = 42;
+ return mp->pointer;
+ }
+ }
+
+ Debug("MEMORY: %s[%u]: Attempt to reallocate unallocated pointer",
+ file, line);
+ mp = malloc(sizeof(MemoryType));
+ Assert(mp);
+ mp->file = file;
+ mp->line = line;
+ mp->size = size;
+ mp->pointer = malloc(size + sizeof(char));
+ if(!mp->pointer) {
+ Debug("MEMORY: %s[%u]: Failed to reallocate %d bytes.",
+ file, line, size);
+ Assert(0);
+ }
+ memset(mp->pointer, 85, size);
+ ((char*)mp->pointer)[size] = 42;
+
+ mp->next = allocations;
+ allocations = mp;
+
+ return mp->pointer;
+ }
+
+}
+
+/***************************************************************************
+ * Release memory and log.
+ ***************************************************************************/
+void DEBUG_Release(void **ptr, const char *file, unsigned int line) {
+ MemoryType *mp, *last;
+
+ if(!ptr) {
+ Debug("MEMORY: %s[%u]: Invalid attempt to release", file, line);
+ } else if(!*ptr) {
+ Debug("MEMORY: %s[%u]: Attempt to delete NULL pointer",
+ file, line);
+ } else {
+ last = NULL;
+ for(mp = allocations; mp; mp = mp->next) {
+ if(mp->pointer == *ptr) {
+ if(last) {
+ last->next = mp->next;
+ } else {
+ allocations = mp->next;
+ }
+
+ if(((char*)*ptr)[mp->size] != 42) {
+ Debug("MEMORY: %s[%u]: The canary is dead.", file, line);
+ }
+
+ memset(*ptr, 0xFF, mp->size);
+ free(mp);
+ free(*ptr);
+ *ptr = NULL;
+ return;
+ }
+ last = mp;
+ }
+ Debug("MEMORY: %s[%u]: Attempt to delete unallocated pointer",
+ file, line);
+ memset(*ptr, 0xFF, mp->size);
+ free(*ptr);
+
+ /* This address should cause a segfault or bus error. */
+ *ptr = (void*)1;
+
+ }
+}
+
+/***************************************************************************
+ * Add a resource.
+ ***************************************************************************/
+void DEBUG_AllocateResource(int resource, const char *file,
+ unsigned int line) {
+
+ ResourceType *rp;
+
+ for(rp = resources; rp; rp = rp->next) {
+ if(rp->resource == resource) {
+
+ rp->allocationFiles[rp->allocationOffset] = file;
+ rp->allocationLines[rp->allocationOffset] = line;
+ rp->allocationOffset
+ = (rp->allocationOffset + 1) % CHECKPOINT_LIST_SIZE;
+
+ ++rp->count;
+ return;
+ }
+ }
+
+ rp = malloc(sizeof(ResourceType));
+ memset(rp, 0, sizeof(ResourceType));
+ rp->resource = resource;
+ rp->allocationFiles[0] = file;
+ rp->allocationLines[0] = line;
+ rp->allocationOffset = 1;
+ rp->releaseOffset = 0;
+ rp->count = 1;
+
+ rp->next = resources;
+ resources = rp;
+
+}
+
+/***************************************************************************
+ * Remove a resource.
+ ***************************************************************************/
+void DEBUG_ReleaseResource(int resource, const char *file,
+ unsigned int line) {
+
+ ResourceType *rp;
+
+ for(rp = resources; rp; rp = rp->next) {
+ if(rp->resource == resource) {
+ rp->releaseFiles[rp->releaseOffset] = file;
+ rp->releaseLines[rp->releaseOffset] = line;
+ rp->releaseOffset = (rp->releaseOffset + 1) % CHECKPOINT_LIST_SIZE;
+ if(rp->count <= 0) {
+ Debug("RESOURCE: Multiple attempts to release resource %d",
+ resource);
+ DEBUG_PrintResourceStack(rp);
+ } else {
+ --rp->count;
+ }
+ return;
+ }
+ }
+
+ Debug("RESOURCE: Attempt to release unallocated resource %d",
+ resource);
+ Debug(" in %s at line %u", file, line);
+
+}
+
+#undef CHECKPOINT_LIST_SIZE
+
+#endif
+
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 0000000..c74f1a8
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,104 @@
+/**
+ * @file debug.h
+ * @author Joe Wingbermuehle
+ * @date 2003-2006
+ *
+ * @brief Header for the debug functions.
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+
+void Debug(const char *str, ...);
+
+#ifdef HAVE_ALLOCA_H
+
+# define AllocateStack( x ) alloca( x )
+# define ReleaseStack( x ) ((void)0)
+
+#else
+
+# define AllocateStack( x ) Allocate( x )
+# define ReleaseStack( x ) Release( x )
+
+#endif
+
+#ifdef DEBUG
+
+# define Assert( x ) \
+ if(!( x )) { \
+ Debug("ASSERT FAILED: %s[%u]", __FILE__, __LINE__ ); \
+ abort(); \
+ }
+
+# define SetCheckpoint() \
+ DEBUG_SetCheckpoint( __FILE__, __LINE__ )
+# define ShowCheckpoint() \
+ DEBUG_ShowCheckpoint()
+
+# define StartDebug() \
+ DEBUG_StartDebug( __FILE__, __LINE__ )
+# define StopDebug() \
+ DEBUG_StopDebug( __FILE__, __LINE__ )
+
+# define Allocate( x ) \
+ DEBUG_Allocate( (x), __FILE__, __LINE__ )
+# define Reallocate( x, y ) \
+ DEBUG_Reallocate( (x), (y), __FILE__, __LINE__ )
+# define Release( x ) \
+ DEBUG_Release( (void*)(& x), __FILE__, __LINE__ )
+
+ void DEBUG_SetCheckpoint(const char*, unsigned int);
+ void DEBUG_ShowCheckpoint();
+
+ void DEBUG_StartDebug(const char*, unsigned int);
+ void DEBUG_StopDebug(const char*, unsigned int);
+
+ void *DEBUG_Allocate(size_t, const char*, unsigned int);
+ void *DEBUG_Reallocate(void*, size_t, const char*, unsigned int);
+ void DEBUG_Release(void**, const char*, unsigned int);
+
+#else
+
+# define Assert( x ) ((void)0)
+
+# define SetCheckpoint() ((void)0)
+# define ShowCheckpoint() ((void)0)
+
+# define StartDebug() ((void)0)
+# define StopDebug() ((void)0)
+
+# define Allocate( x ) malloc( (x) )
+# define Reallocate( x, y ) realloc( (x), (y) )
+# define Release( x ) free( (x) )
+
+#endif
+
+#endif
+
diff --git a/src/desktop.c b/src/desktop.c
new file mode 100644
index 0000000..1d09050
--- /dev/null
+++ b/src/desktop.c
@@ -0,0 +1,239 @@
+/***************************************************************************
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "desktop.h"
+#include "main.h"
+#include "client.h"
+#include "hint.h"
+#include "pager.h"
+#include "taskbar.h"
+#include "error.h"
+#include "menu.h"
+#include "misc.h"
+
+char **desktopNames = NULL;
+
+static int showingDesktop;
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeDesktops() {
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void StartupDesktops() {
+
+ unsigned int x;
+
+ if(desktopNames == NULL) {
+ desktopNames = Allocate(desktopCount * sizeof(char*));
+ for(x = 0; x < desktopCount; x++) {
+ desktopNames[x] = NULL;
+ }
+ }
+ for(x = 0; x < desktopCount; x++) {
+ if(desktopNames[x] == NULL) {
+ desktopNames[x] = Allocate(4 * sizeof(char));
+ snprintf(desktopNames[x], 4, "%d", x + 1);
+ }
+ }
+
+ showingDesktop = 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShutdownDesktops() {
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyDesktops() {
+
+ unsigned int x;
+
+ if(desktopNames) {
+ for(x = 0; x < desktopCount; x++) {
+ Release(desktopNames[x]);
+ }
+ Release(desktopNames);
+ desktopNames = NULL;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void NextDesktop() {
+ ChangeDesktop((currentDesktop + 1) % desktopCount);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void PreviousDesktop() {
+ if(currentDesktop > 0) {
+ ChangeDesktop(currentDesktop - 1);
+ } else {
+ ChangeDesktop(desktopCount - 1);
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ChangeDesktop(unsigned int desktop) {
+
+ ClientNode *np;
+ unsigned int x;
+
+ if(desktop >= desktopCount) {
+ return;
+ }
+
+ if(currentDesktop == desktop && !initializing) {
+ return;
+ }
+
+ for(x = 0; x < LAYER_COUNT; x++) {
+ for(np = nodes[x]; np; np = np->next) {
+ if(np->state.status & STAT_STICKY) {
+ continue;
+ }
+ if(np->state.desktop == desktop) {
+ ShowClient(np);
+ } else if(np->state.desktop == currentDesktop) {
+ HideClient(np);
+ }
+ }
+ }
+
+ currentDesktop = desktop;
+
+ SetCardinalAtom(rootWindow, ATOM_NET_CURRENT_DESKTOP, currentDesktop);
+ SetCardinalAtom(rootWindow, ATOM_WIN_WORKSPACE, currentDesktop);
+
+ RestackClients();
+
+ UpdatePager();
+ UpdateTaskBar();
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+Menu *CreateDesktopMenu(unsigned int mask) {
+
+ Menu *menu;
+ MenuItem *item;
+ int x;
+
+ menu = Allocate(sizeof(Menu));
+ menu->itemHeight = 0;
+ menu->items = NULL;
+ menu->label = NULL;
+
+ for(x = desktopCount - 1; x >= 0; x--) {
+
+ item = Allocate(sizeof(MenuItem));
+ item->type = MENU_ITEM_NORMAL;
+ item->iconName = NULL;
+ item->submenu = NULL;
+ item->next = menu->items;
+ menu->items = item;
+
+ item->action.type = MA_DESKTOP;
+ item->action.data.i = x;
+
+ item->name = Allocate(strlen(desktopNames[x]) + 3);
+ if(mask & (1 << x)) {
+ strcpy(item->name, "[");
+ strcat(item->name, desktopNames[x]);
+ strcat(item->name, "]");
+ } else {
+ strcpy(item->name, " ");
+ strcat(item->name, desktopNames[x]);
+ strcat(item->name, " ");
+ }
+
+ }
+
+ return menu;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShowDesktop() {
+
+ ClientNode *np;
+ int layer;
+
+ for(layer = 0; layer < LAYER_COUNT; layer++) {
+ for(np = nodes[layer]; np; np = np->next) {
+ if(showingDesktop) {
+ if(np->state.status & STAT_SDESKTOP) {
+ RestoreClient(np, 0);
+ }
+ } else if(np->state.desktop == currentDesktop
+ || (np->state.status & STAT_STICKY)) {
+ if(np->state.status & (STAT_MAPPED | STAT_SHADED)) {
+ MinimizeClient(np);
+ np->state.status |= STAT_SDESKTOP;
+ }
+ }
+ }
+ }
+
+ showingDesktop = !showingDesktop;
+
+ RestackClients();
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetDesktopCount(const char *str) {
+
+ if(!str) {
+ Warning("invalid desktop count");
+ return;
+ }
+
+ desktopCount = atoi(str);
+ if(desktopCount <= 0 || desktopCount > MAX_DESKTOP_COUNT) {
+ Warning("invalid desktop count: \"%s\"", str);
+ desktopCount = DEFAULT_DESKTOP_COUNT;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetDesktopName(unsigned int desktop, const char *str) {
+
+ unsigned int x;
+
+ if(!str) {
+ Warning("empty Desktops Name tag");
+ return;
+ }
+
+ Assert(desktop >= 0);
+ Assert(desktop < desktopCount);
+
+ if(!desktopNames) {
+ desktopNames = Allocate(desktopCount * sizeof(char*));
+ for(x = 0; x < desktopCount; x++) {
+ desktopNames[x] = NULL;
+ }
+ }
+
+ Assert(desktopNames[desktop] == NULL);
+
+ desktopNames[desktop] = CopyString(str);
+
+}
+
+
diff --git a/src/desktop.h b/src/desktop.h
new file mode 100644
index 0000000..b8e3312
--- /dev/null
+++ b/src/desktop.h
@@ -0,0 +1,60 @@
+/**
+ * @file desktop.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the desktop management functions.
+ *
+ */
+
+#ifndef DESKTOP_H
+#define DESKTOP_H
+
+struct MenuType;
+
+extern char **desktopNames;
+
+/*@{*/
+void InitializeDesktops();
+void StartupDesktops();
+void ShutdownDesktops();
+void DestroyDesktops();
+/*@}*/
+
+/** Switch to the next desktop. */
+void NextDesktop();
+
+/** Switch to the previous desktop. */
+void PreviousDesktop();
+
+/** Switch to a specific desktop.
+ * @param desktop The desktop to show (0 based).
+ */
+void ChangeDesktop(unsigned int desktop);
+
+/** Toggle the "show desktop" state.
+ * This will either minimize or restore all items on the current desktop.
+ */
+void ShowDesktop();
+
+/** Create a menu containing a list of desktops.
+ * @param mask A bit mask of desktops to highlight.
+ * @return A menu containing all the desktops.
+ */
+struct Menu *CreateDesktopMenu(unsigned int mask);
+
+/** Set the number of desktops.
+ * This is called before startup.
+ * @param str ASCII representation of the number of desktops.
+ */
+void SetDesktopCount(const char *str);
+
+/** Set the name of a desktop.
+ * This is called before startup.
+ * @param desktop The desktop to name (0 based).
+ * @param str The name to assign.
+ */
+void SetDesktopName(unsigned int desktop, const char *str);
+
+#endif
+
diff --git a/src/dock.c b/src/dock.c
new file mode 100644
index 0000000..289e85f
--- /dev/null
+++ b/src/dock.c
@@ -0,0 +1,602 @@
+/**
+ * @file dock.c
+ * @author Joe Wingbermuehle
+ * @date 2006
+ *
+ * @brief Dock functions.
+ *
+ */
+
+#include "jwm.h"
+#include "dock.h"
+#include "tray.h"
+#include "main.h"
+#include "error.h"
+#include "color.h"
+
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+#define SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define SYSTEM_TRAY_ORIENTATION_VERT 1
+
+/** Structure to represent a docked window. */
+typedef struct DockNode {
+
+ Window window;
+ int needs_reparent;
+
+ struct DockNode *next;
+
+} DockNode;
+
+/** Structure to represent a dock tray component. */
+typedef struct DockType {
+
+ TrayComponentType *cp;
+
+ Window window;
+
+ DockNode *nodes;
+
+} DockType;
+
+static const char *BASE_SELECTION_NAME = "_NET_SYSTEM_TRAY_S%d";
+static const char *ORIENTATION_ATOM = "_NET_SYSTEM_TRAY_ORIENTATION";
+
+static DockType *dock = NULL;
+static int owner = 0;
+static Atom dockAtom;
+static unsigned long orientation;
+
+static void SetSize(TrayComponentType *cp, int width, int height);
+static void Create(TrayComponentType *cp);
+static void Resize(TrayComponentType *cp);
+
+static void DockWindow(Window win);
+static int UndockWindow(Window win);
+
+static void UpdateDock();
+
+/** Initialize dock data. */
+void InitializeDock() {
+}
+
+/** Startup the dock. */
+void StartupDock() {
+
+ char *selectionName;
+
+ if(!dock) {
+ /* No dock has been requested. */
+ return;
+ }
+
+ if(!dock->cp) {
+ /* The Dock item has been removed from the configuration. */
+ JXDestroyWindow(display, dock->window);
+ Release(dock);
+ dock = NULL;
+ return;
+ }
+
+ if(dock->window == None) {
+
+ /* No dock yet. */
+
+ /* Get the selection atom. */
+ selectionName = AllocateStack(strlen(BASE_SELECTION_NAME) + 1);
+ sprintf(selectionName, BASE_SELECTION_NAME, rootScreen);
+ dockAtom = JXInternAtom(display, selectionName, False);
+ ReleaseStack(selectionName);
+
+ /* The location and size of the window doesn't matter here. */
+ dock->window = JXCreateSimpleWindow(display, rootWindow,
+ /* x, y, width, height */ 0, 0, 1, 1,
+ /* border_size, border_color */ 0, 0,
+ /* background */ colors[COLOR_TRAY_BG]);
+ JXSelectInput(display, dock->window,
+ SubstructureNotifyMask
+ | SubstructureRedirectMask
+ | PointerMotionMask | PointerMotionHintMask);
+
+ }
+ dock->cp->window = dock->window;
+
+}
+
+/** Shutdown the dock. */
+void ShutdownDock() {
+
+ DockNode *np;
+
+ if(dock) {
+
+ if(shouldRestart) {
+
+ /* If restarting we just reparent the dock window to the root
+ * window. We need to keep the dock around and visible so that
+ * we don't cause problems with the docked windows.
+ * It seems every application handles docking differently...
+ */
+ JXReparentWindow(display, dock->window, rootWindow, 0, 0);
+
+ } else {
+
+ /* JWM is exiting. */
+
+ /* Release memory used by the dock list. */
+ while(dock->nodes) {
+ np = dock->nodes->next;
+ JXReparentWindow(display, dock->nodes->window, rootWindow, 0, 0);
+ Release(dock->nodes);
+ dock->nodes = np;
+ }
+
+ /* Release the selection. */
+ if(owner) {
+ JXSetSelectionOwner(display, dockAtom, None, CurrentTime);
+ }
+
+ /* Destroy the dock window. */
+ JXDestroyWindow(display, dock->window);
+
+ }
+
+ }
+
+}
+
+/** Destroy dock data. */
+void DestroyDock() {
+
+ if(dock) {
+ if(shouldRestart) {
+ dock->cp = NULL;
+ } else {
+ Release(dock);
+ dock = NULL;
+ }
+ }
+
+}
+
+/** Create a dock component. */
+TrayComponentType *CreateDock() {
+
+ TrayComponentType *cp;
+
+ if(dock != NULL && dock->cp != NULL) {
+ Warning("only one Dock allowed");
+ return NULL;
+ } else if(dock == NULL) {
+ dock = Allocate(sizeof(DockType));
+ dock->nodes = NULL;
+ dock->window = None;
+ }
+
+ cp = CreateTrayComponent();
+ cp->object = dock;
+ dock->cp = cp;
+ cp->requestedWidth = 1;
+ cp->requestedHeight = 1;
+
+ cp->SetSize = SetSize;
+ cp->Create = Create;
+ cp->Resize = Resize;
+
+ return cp;
+
+}
+
+/** Set the size of a dock component. */
+void SetSize(TrayComponentType *cp, int width, int height) {
+
+ int count;
+ DockNode *np;
+
+ Assert(cp);
+ Assert(dock);
+
+ count = 0;
+ for(np = dock->nodes; np; np = np->next) {
+ ++count;
+ }
+
+ if(width == 0) {
+ if(count > 0) {
+ cp->width = count * height;
+ cp->requestedWidth = cp->width;
+ } else {
+ cp->width = 1;
+ cp->requestedWidth = 1;
+ }
+ } else if(height == 0) {
+ if(count > 0) {
+ cp->height = count * width;
+ cp->requestedHeight = cp->height;
+ } else {
+ cp->height = 1;
+ cp->requestedHeight = 1;
+ }
+ }
+
+}
+
+/** Initialize a dock component. */
+void Create(TrayComponentType *cp) {
+
+ XEvent event;
+ Atom orientationAtom;
+
+ Assert(cp);
+
+ /* Map the dock window. */
+ if(cp->window != None) {
+ JXResizeWindow(display, cp->window, cp->width, cp->height);
+ JXMapRaised(display, cp->window);
+ }
+
+ /* Set the orientation. */
+ orientationAtom = JXInternAtom(display, ORIENTATION_ATOM, False);
+ if(cp->height == 1) {
+ orientation = SYSTEM_TRAY_ORIENTATION_VERT;
+ } else {
+ orientation = SYSTEM_TRAY_ORIENTATION_HORZ;
+ }
+ JXChangeProperty(display, dock->cp->window, orientationAtom,
+ XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&orientation, 1);
+
+ /* Get the selection if we don't already own it.
+ * If we did already own it, getting it again would cause problems
+ * with some clients due to the way restarts are handled.
+ */
+ if(!owner) {
+
+ owner = 1;
+ JXSetSelectionOwner(display, dockAtom, dock->cp->window, CurrentTime);
+ if(JXGetSelectionOwner(display, dockAtom) != dock->cp->window) {
+
+ owner = 0;
+ Warning("could not acquire system tray selection");
+
+ } else {
+
+ memset(&event, 0, sizeof(event));
+ event.xclient.type = ClientMessage;
+ event.xclient.window = rootWindow;
+ event.xclient.message_type = JXInternAtom(display, "MANAGER", False);
+ event.xclient.format = 32;
+ event.xclient.data.l[0] = CurrentTime;
+ event.xclient.data.l[1] = dockAtom;
+ event.xclient.data.l[2] = dock->cp->window;
+ event.xclient.data.l[3] = 0;
+ event.xclient.data.l[4] = 0;
+
+ JXSendEvent(display, rootWindow, False, StructureNotifyMask, &event);
+
+ }
+
+ }
+
+}
+
+/** Resize a dock component. */
+void Resize(TrayComponentType *cp) {
+
+ Assert(cp);
+
+ JXResizeWindow(display, cp->window, cp->width, cp->height);
+ UpdateDock();
+
+}
+
+/** Handle a dock event. */
+void HandleDockEvent(const XClientMessageEvent *event) {
+
+ Assert(event);
+
+ switch(event->data.l[1]) {
+ case SYSTEM_TRAY_REQUEST_DOCK:
+ DockWindow(event->data.l[2]);
+ break;
+ case SYSTEM_TRAY_BEGIN_MESSAGE:
+ break;
+ case SYSTEM_TRAY_CANCEL_MESSAGE:
+ break;
+ default:
+ Debug("invalid opcode in dock event");
+ break;
+ }
+
+}
+
+/** Handle a resize request event. */
+int HandleDockResizeRequest(const XResizeRequestEvent *event) {
+
+ DockNode *np;
+
+ Assert(event);
+
+ if(!dock) {
+ return 0;
+ }
+
+ for(np = dock->nodes; np; np = np->next) {
+ if(np->window == event->window) {
+
+ JXResizeWindow(display, np->window, event->width, event->height);
+ UpdateDock();
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/** Handle a configure request event. */
+int HandleDockConfigureRequest(const XConfigureRequestEvent *event) {
+
+ XWindowChanges wc;
+ DockNode *np;
+
+ Assert(event);
+
+ if(!dock) {
+ return 0;
+ }
+
+ for(np = dock->nodes; np; np = np->next) {
+ if(np->window == event->window) {
+ wc.stack_mode = event->detail;
+ wc.sibling = event->above;
+ wc.border_width = event->border_width;
+ wc.x = event->x;
+ wc.y = event->y;
+ wc.width = event->width;
+ wc.height = event->height;
+ JXConfigureWindow(display, np->window, event->value_mask, &wc);
+ UpdateDock();
+ return 1;
+ }
+ }
+
+ return 0;
+
+}
+
+/** Handle a reparent notify event. */
+int HandleDockReparentNotify(const XReparentEvent *event) {
+
+ DockNode *np;
+ int handled;
+
+ Assert(event);
+
+ /* Just return if there is no dock. */
+ if(!dock) {
+ return 0;
+ }
+
+ /* Check each docked window. */
+ handled = 0;
+ for(np = dock->nodes; np; np = np->next) {
+ if(np->window == event->window) {
+ if(event->parent != dock->cp->window) {
+ /* For some reason the application reparented the window.
+ * We make note of this condition and reparent every time
+ * the dock is updated. Unfortunately we can't do this for
+ * all applications because some won't deal with it.
+ */
+ np->needs_reparent = 1;
+ handled = 1;
+ }
+ }
+ }
+
+ /* Layout the stuff on the dock again if something happened. */
+ if(handled) {
+ UpdateDock();
+ }
+
+ return handled;
+
+}
+
+/** Handle a destroy event. */
+int HandleDockDestroy(Window win) {
+
+ if(dock) {
+ return UndockWindow(win);
+ } else {
+ return 0;
+ }
+
+}
+
+/** Handle a selection clear event. */
+int HandleDockSelectionClear(const XSelectionClearEvent *event) {
+
+ if(event->selection == dockAtom) {
+ Debug("lost _NET_SYSTEM_TRAY selection");
+ owner = 0;
+ }
+
+ return 0;
+
+}
+
+/** Add a window to the dock. */
+void DockWindow(Window win) {
+
+ DockNode *np;
+
+ Assert(dock);
+
+ /* Make sure we have a valid window to add. */
+ if(win == None) {
+ return;
+ }
+
+ /* If this window is already docked ignore it. */
+ for(np = dock->nodes; np; np = np->next) {
+ if(np->window == win) {
+ return;
+ }
+ }
+
+ /* Add the window to our list. */
+ np = Allocate(sizeof(DockNode));
+ np->window = win;
+ np->needs_reparent = 0;
+ np->next = dock->nodes;
+ dock->nodes = np;
+
+ /* Update the requested size. */
+ if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
+ if(dock->cp->requestedWidth > 1) {
+ dock->cp->requestedWidth += dock->cp->height;
+ } else {
+ dock->cp->requestedWidth = dock->cp->height;
+ }
+ } else {
+ if(dock->cp->requestedHeight > 1) {
+ dock->cp->requestedHeight += dock->cp->width;
+ } else {
+ dock->cp->requestedHeight = dock->cp->width;
+ }
+ }
+
+ /* It's safe to reparent at (0, 0) since we call
+ * ResizeTray which will invoke the Resize callback.
+ */
+ JXAddToSaveSet(display, win);
+ JXSelectInput(display, win,
+ StructureNotifyMask
+ | ResizeRedirectMask
+ | PointerMotionMask | PointerMotionHintMask);
+ JXReparentWindow(display, win, dock->cp->window, 0, 0);
+ JXMapRaised(display, win);
+
+ /* Resize the tray containing the dock. */
+ ResizeTray(dock->cp->tray);
+
+}
+
+/** Remove a window from the dock. */
+int UndockWindow(Window win) {
+
+ DockNode *np;
+ DockNode *last;
+
+ Assert(dock);
+
+ last = NULL;
+ for(np = dock->nodes; np; np = np->next) {
+ if(np->window == win) {
+
+ /* Remove the window from our list. */
+ if(last) {
+ last->next = np->next;
+ } else {
+ dock->nodes = np->next;
+ }
+ Release(np);
+
+ /* Update the requested size. */
+ if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
+ dock->cp->requestedWidth -= dock->cp->height;
+ if(dock->cp->requestedWidth <= 0) {
+ dock->cp->requestedWidth = 1;
+ }
+ } else {
+ dock->cp->requestedHeight -= dock->cp->width;
+ if(dock->cp->requestedHeight <= 0) {
+ dock->cp->requestedHeight = 1;
+ }
+ }
+
+ /* Resize the tray. */
+ ResizeTray(dock->cp->tray);
+
+ return 1;
+
+ }
+ last = np;
+ }
+
+ return 0;
+}
+
+/** Layout items on the dock. */
+void UpdateDock() {
+
+ XWindowAttributes attr;
+ DockNode *np;
+ int x, y;
+ int width, height;
+ int xoffset, yoffset;
+ int itemWidth, itemHeight;
+ double ratio;
+
+ Assert(dock);
+
+ /* Determine the size of items in the dock. */
+ if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
+ itemWidth = dock->cp->height;
+ itemHeight = dock->cp->height;
+ } else {
+ itemHeight = dock->cp->width;
+ itemWidth = dock->cp->width;
+ }
+
+ x = 0;
+ y = 0;
+ for(np = dock->nodes; np; np = np->next) {
+
+ xoffset = 0;
+ yoffset = 0;
+ width = itemWidth;
+ height = itemHeight;
+
+ if(JXGetWindowAttributes(display, np->window, &attr)) {
+
+ ratio = (double)attr.width / attr.height;
+
+ if(ratio > 1.0) {
+ if(width > attr.width) {
+ width = attr.width;
+ }
+ height = width / ratio;
+ } else {
+ if(height > attr.height) {
+ height = attr.height;
+ }
+ width = height * ratio;
+ }
+
+ xoffset = (itemWidth - width) / 2;
+ yoffset = (itemHeight - height) / 2;
+
+ }
+
+ JXMoveResizeWindow(display, np->window, x + xoffset, y + yoffset,
+ width, height);
+
+ /* Reparent if this window likes to go other places. */
+ if(np->needs_reparent) {
+ JXReparentWindow(display, np->window, dock->cp->window,
+ x + xoffset, y + yoffset);
+ }
+
+ if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
+ x += itemWidth;
+ } else {
+ y += itemHeight;
+ }
+ }
+
+}
+
diff --git a/src/dock.h b/src/dock.h
new file mode 100644
index 0000000..307f348
--- /dev/null
+++ b/src/dock.h
@@ -0,0 +1,43 @@
+/**
+ * @file dock.h
+ * @author Joe Wingbermuehle
+ * @date 2006
+ *
+ * @brief Header for the dock functions.
+ *
+ */
+
+#ifndef DOCK_H
+#define DOCK_H
+
+struct TrayComponentType;
+
+/*@{*/
+void InitializeDock();
+void StartupDock();
+void ShutdownDock();
+void DestroyDock();
+/*@}*/
+
+/** Create a dock to be used for notifications.
+ * Note that only one dock can be created.
+ */
+struct TrayComponentType *CreateDock();
+
+/** Handle a client message sent to the dock window.
+ * @param event The event.
+ */
+void HandleDockEvent(const XClientMessageEvent *event);
+
+int HandleDockDestroy(Window win);
+
+int HandleDockSelectionClear(const XSelectionClearEvent *event);
+
+int HandleDockResizeRequest(const XResizeRequestEvent *event);
+
+int HandleDockConfigureRequest(const XConfigureRequestEvent *event);
+
+int HandleDockReparentNotify(const XReparentEvent *event);
+
+#endif
+
diff --git a/src/error.c b/src/error.c
new file mode 100644
index 0000000..a4b1277
--- /dev/null
+++ b/src/error.c
@@ -0,0 +1,108 @@
+/**
+ * @file error.c
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Error handling functions.
+ *
+ */
+
+#include "jwm.h"
+#include "error.h"
+#include "main.h"
+
+/** Log a fatal error and exit. */
+void FatalError(const char *str, ...) {
+
+ va_list ap;
+ va_start(ap, str);
+
+ Assert(str);
+
+ fprintf(stderr, "JWM: error: ");
+ vfprintf(stderr, str, ap);
+ fprintf(stderr, "\n");
+
+ va_end(ap);
+
+ exit(1);
+
+}
+
+/** Log a warning. */
+void Warning(const char *str, ...) {
+
+ va_list ap;
+ va_start(ap, str);
+
+ Assert(str);
+
+ WarningVA(NULL, str, ap);
+
+ va_end(ap);
+
+}
+
+/** Log a warning. */
+void WarningVA(const char *part, const char *str, va_list ap) {
+
+ Assert(str);
+
+ fprintf(stderr, "JWM: warning: ");
+ if(part) {
+ fprintf(stderr, "%s: ", part);
+ }
+ vfprintf(stderr, str, ap);
+ fprintf(stderr, "\n");
+
+}
+
+/** Callback to handle errors from Xlib.
+ * Note that if debug output is directed to an X terminal, emitting too
+ * much output can cause a dead lock (this happens on HP-UX). Therefore
+ * ShowCheckpoint isn't used by default.
+ */
+int ErrorHandler(Display *d, XErrorEvent *e) {
+
+#ifdef DEBUG
+
+ char buffer[64];
+ char code[32];
+
+#endif
+
+ if(initializing) {
+ if(e->request_code == X_ChangeWindowAttributes
+ && e->error_code == BadAccess) {
+ FatalError("display is already managed");
+ }
+ }
+
+#ifdef DEBUG
+
+ if(!e) {
+ fprintf(stderr, "XError: [no information]\n");
+ return 0;
+ }
+
+ XGetErrorText(display, e->error_code, buffer, sizeof(buffer));
+ Debug("XError: %s", buffer);
+
+ snprintf(code, sizeof(code), "%d", e->request_code);
+ XGetErrorDatabaseText(display, "XRequest", code, "?",
+ buffer, sizeof(buffer));
+ Debug(" Request Code: %d (%s)", e->request_code, buffer);
+ Debug(" Minor Code: %d", e->minor_code);
+ Debug(" Resource ID: 0x%lx", (unsigned long)e->resourceid);
+ Debug(" Error Serial: %lu", (unsigned long)e->serial);
+
+#if 0
+ ShowCheckpoint();
+#endif
+
+#endif
+
+ return 0;
+
+}
+
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 0000000..5028db4
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,38 @@
+/**
+ * @file error.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the error functions.
+ *
+ */
+
+#ifndef ERROR_H
+#define ERROR_H
+
+/** Display and error message and terminate the program.
+ * @param str The format of the message to display.
+ */
+void FatalError(const char *str, ...);
+
+/** Display a warning message.
+ * @param str The format of the message to display.
+ */
+void Warning(const char *str, ...);
+
+/** Display a warning message.
+ * @param part A section identifier for the message.
+ * @param str The format string of the message to display.
+ * @param ap The argument list.
+ */
+void WarningVA(const char *part, const char *str, va_list ap);
+
+/** Handle an XError event.
+ * @param d The display on which the event occurred.
+ * @param e The error event.
+ * @return 0
+ */
+int ErrorHandler(Display *d, XErrorEvent *e);
+
+#endif
+
diff --git a/src/event.c b/src/event.c
new file mode 100644
index 0000000..3c4f0bb
--- /dev/null
+++ b/src/event.c
@@ -0,0 +1,1138 @@
+/****************************************************************************
+ * Functions to handle XServer events.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "event.h"
+#include "client.h"
+#include "main.h"
+#include "hint.h"
+#include "tray.h"
+#include "pager.h"
+#include "desktop.h"
+#include "cursor.h"
+#include "icon.h"
+#include "taskbar.h"
+#include "confirm.h"
+#include "swallow.h"
+#include "popup.h"
+#include "winmenu.h"
+#include "root.h"
+#include "move.h"
+#include "resize.h"
+#include "key.h"
+#include "clock.h"
+#include "place.h"
+#include "dock.h"
+#include "timing.h"
+#include "traybutton.h"
+
+#define MIN_TIME_DELTA 50
+
+static void Signal();
+static void DispatchBorderButtonEvent(const XButtonEvent *event,
+ ClientNode *np);
+
+static void HandleConfigureRequest(const XConfigureRequestEvent *event);
+static int HandleExpose(const XExposeEvent *event);
+static int HandlePropertyNotify(const XPropertyEvent *event);
+static void HandleClientMessage(const XClientMessageEvent *event);
+static void HandleColormapChange(const XColormapEvent *event);
+static int HandleDestroyNotify(const XDestroyWindowEvent *event);
+static void HandleMapRequest(const XMapEvent *event);
+static void HandleUnmapNotify(const XUnmapEvent *event);
+static void HandleButtonEvent(const XButtonEvent *event);
+static void HandleKeyPress(const XKeyEvent *event);
+static void HandleEnterNotify(const XCrossingEvent *event);
+static void HandleLeaveNotify(const XCrossingEvent *event);
+static void HandleMotionNotify(const XMotionEvent *event);
+static int HandleSelectionClear(const XSelectionClearEvent *event);
+
+static void HandleNetMoveResize(const XClientMessageEvent *event,
+ ClientNode *np);
+static void HandleNetWMState(const XClientMessageEvent *event,
+ ClientNode *np);
+
+#ifdef USE_SHAPE
+static void HandleShapeEvent(const XShapeEvent *event);
+#endif
+
+/****************************************************************************
+ ****************************************************************************/
+void WaitForEvent(XEvent *event) {
+
+ struct timeval timeout;
+ fd_set fds;
+ int fd;
+ int handled;
+
+ fd = JXConnectionNumber(display);
+
+ do {
+
+ while(JXPending(display) == 0) {
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ timeout.tv_usec = 0;
+ timeout.tv_sec = 1;
+ if(select(fd + 1, &fds, NULL, NULL, &timeout) <= 0) {
+ Signal();
+ }
+ }
+
+ Signal();
+
+ JXNextEvent(display, event);
+
+ switch(event->type) {
+ case ConfigureRequest:
+ HandleConfigureRequest(&event->xconfigurerequest);
+ handled = 1;
+ break;
+ case MapRequest:
+ HandleMapRequest(&event->xmap);
+ handled = 1;
+ break;
+ case PropertyNotify:
+ handled = HandlePropertyNotify(&event->xproperty);
+ break;
+ case ClientMessage:
+ HandleClientMessage(&event->xclient);
+ handled = 1;
+ break;
+ case UnmapNotify:
+ HandleUnmapNotify(&event->xunmap);
+ handled = 1;
+ break;
+ case Expose:
+ handled = HandleExpose(&event->xexpose);
+ break;
+ case ColormapNotify:
+ HandleColormapChange(&event->xcolormap);
+ handled = 1;
+ break;
+ case DestroyNotify:
+ handled = HandleDestroyNotify(&event->xdestroywindow);
+ break;
+ case SelectionClear:
+ handled = HandleSelectionClear(&event->xselectionclear);
+ break;
+ case ResizeRequest:
+ handled = HandleDockResizeRequest(&event->xresizerequest);
+ break;
+ case MotionNotify:
+ SetMousePosition(event->xmotion.x_root, event->xmotion.y_root);
+ handled = 0;
+ break;
+ case ReparentNotify:
+ HandleDockReparentNotify(&event->xreparent);
+ handled = 1;
+ break;
+ case ConfigureNotify:
+ handled = 0;
+ break;
+ case CreateNotify:
+ case MapNotify:
+ case GraphicsExpose:
+ case NoExpose:
+ handled = 1;
+ break;
+ default:
+#ifdef USE_SHAPE
+ if(haveShape && event->type == shapeEvent) {
+ HandleShapeEvent((XShapeEvent*)event);
+ handled = 1;
+ } else {
+ handled = 0;
+ }
+#else
+ handled = 0;
+#endif
+ break;
+ }
+
+ if(!handled) {
+ handled = ProcessTrayEvent(event);
+ }
+ if(!handled) {
+ handled = ProcessDialogEvent(event);
+ }
+ if(!handled) {
+ handled = ProcessSwallowEvent(event);
+ }
+ if(!handled) {
+ handled = ProcessPopupEvent(event);
+ }
+
+ } while(handled && !shouldExit);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void Signal() {
+
+ static TimeType last = ZERO_TIME;
+
+ TimeType now;
+ int x, y;
+
+ GetCurrentTime(&now);
+
+ if(GetTimeDifference(&now, &last) < MIN_TIME_DELTA) {
+ return;
+ }
+ last = now;
+
+ GetMousePosition(&x, &y);
+
+ SignalTaskbar(&now, x, y);
+ SignalTrayButton(&now, x, y);
+ SignalClock(&now, x, y);
+ SignalTray(&now, x, y);
+ SignalPopup(&now, x, y);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ProcessEvent(XEvent *event) {
+
+ switch(event->type) {
+ case ButtonPress:
+ case ButtonRelease:
+ HandleButtonEvent(&event->xbutton);
+ break;
+ case KeyPress:
+ HandleKeyPress(&event->xkey);
+ break;
+ case EnterNotify:
+ HandleEnterNotify(&event->xcrossing);
+ break;
+ case LeaveNotify:
+ HandleLeaveNotify(&event->xcrossing);
+ break;
+ case MotionNotify:
+ while(JXCheckTypedEvent(display, MotionNotify, event));
+ HandleMotionNotify(&event->xmotion);
+ break;
+ case DestroyNotify:
+ case Expose:
+ case KeyRelease:
+ case ConfigureNotify:
+ break;
+ default:
+ Debug("Unknown event type: %d", event->type);
+ break;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DiscardMotionEvents(XEvent *event, Window w) {
+
+ XEvent temp;
+
+ while(JXCheckTypedEvent(display, MotionNotify, &temp)) {
+ SetMousePosition(temp.xmotion.x_root, temp.xmotion.y_root);
+ if(temp.xmotion.window == w) {
+ *event = temp;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int HandleSelectionClear(const XSelectionClearEvent *event) {
+
+ return HandleDockSelectionClear(event);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleButtonEvent(const XButtonEvent *event) {
+
+ int x, y;
+ ClientNode *np;
+ int north, south, east, west;
+ int allowMode;
+
+ np = FindClientByParent(event->window);
+ if(np) {
+ RaiseClient(np);
+ if(focusModel == FOCUS_CLICK) {
+ FocusClient(np);
+ }
+ switch(event->button) {
+ case Button1:
+ DispatchBorderButtonEvent(event, np);
+ break;
+ case Button2:
+ MoveClient(np, event->x, event->y);
+ break;
+ case Button3:
+ GetBorderSize(np, &north, &south, &east, &west);
+ x = event->x + np->x - west;
+ y = event->y + np->y - north;
+ ShowWindowMenu(np, x, y);
+ break;
+ case Button4:
+ ShadeClient(np);
+ break;
+ case Button5:
+ UnshadeClient(np);
+ break;
+ default:
+ break;
+ }
+ } else if(event->window == rootWindow && event->type == ButtonPress) {
+ if(!ShowRootMenu(event->button, event->x, event->y)) {
+ if(event->button == 4) {
+ PreviousDesktop();
+ } else if(event->button == 5) {
+ NextDesktop();
+ }
+ }
+ } else {
+ np = FindClientByWindow(event->window);
+ if(np) {
+ allowMode = ReplayPointer;
+ switch(event->button) {
+ case Button1:
+ case Button2:
+ RaiseClient(np);
+ if(focusModel == FOCUS_CLICK) {
+ FocusClient(np);
+ }
+ if(event->state & Mod1Mask) {
+ GetBorderSize(np, &north, &south, &east, &west);
+ MoveClient(np, event->x + west, event->y + north);
+ }
+ break;
+ case Button3:
+ if(event->state & Mod1Mask) {
+ LowerClient(np);
+ allowMode = SyncPointer;
+ } else {
+ RaiseClient(np);
+ if(focusModel == FOCUS_CLICK) {
+ FocusClient(np);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ JXAllowEvents(display, allowMode, CurrentTime);
+ }
+ }
+
+ UpdatePager();
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleKeyPress(const XKeyEvent *event) {
+ ClientNode *np;
+ KeyType key;
+
+ key = GetKey(event);
+
+ np = GetActiveClient();
+
+ switch(key & 0xFF) {
+ case KEY_EXEC:
+ RunKeyCommand(event);
+ break;
+ case KEY_DESKTOP:
+ if(key >> 8) {
+ ChangeDesktop((key >> 8) - 1);
+ } else {
+ NextDesktop();
+ }
+ break;
+ case KEY_NEXT:
+ FocusNext();
+ break;
+ case KEY_NEXT_STACKED:
+ FocusNextStackedCircular();
+ break;
+ case KEY_CLOSE:
+ if(np) {
+ DeleteClient(np);
+ }
+ break;
+ case KEY_SHADE:
+ if(np) {
+ if(np->state.status & STAT_SHADED) {
+ UnshadeClient(np);
+ } else {
+ ShadeClient(np);
+ }
+ }
+ break;
+ case KEY_MOVE:
+ if(np) {
+ MoveClientKeyboard(np);
+ }
+ break;
+ case KEY_RESIZE:
+ if(np) {
+ ResizeClientKeyboard(np);
+ }
+ break;
+ case KEY_MIN:
+ if(np) {
+ MinimizeClient(np);
+ }
+ break;
+ case KEY_MAX:
+ if(np) {
+ MaximizeClient(np);
+ }
+ break;
+ case KEY_ROOT:
+ ShowKeyMenu(event);
+ break;
+ case KEY_WIN:
+ if(np) {
+ ShowWindowMenu(np, np->x, np->y);
+ }
+ break;
+ case KEY_RESTART:
+ Restart();
+ break;
+ case KEY_EXIT:
+ Exit();
+ break;
+ default:
+ break;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleConfigureRequest(const XConfigureRequestEvent *event) {
+
+ XWindowChanges wc;
+ ClientNode *np;
+ int north, south, east, west;
+ int changed;
+ int handled;
+
+ handled = HandleDockConfigureRequest(event);
+ if(handled) {
+ return;
+ }
+
+ np = FindClientByWindow(event->window);
+ if(np && np->window == event->window) {
+
+ changed = 0;
+ if((event->value_mask & CWWidth) && (event->width != np->width)) {
+ np->width = event->width;
+ changed = 1;
+ }
+ if((event->value_mask & CWHeight) && (event->height != np->height)) {
+ np->height = event->height;
+ changed = 1;
+ }
+ if((event->value_mask & CWX) && (event->x != np->x)) {
+ np->x = event->x;
+ changed = 1;
+ }
+ if((event->value_mask & CWY) && (event->y != np->y)) {
+ np->y = event->y;
+ changed = 1;
+ }
+
+ if(!changed) {
+ return;
+ }
+
+ if(np->controller) {
+ (np->controller)(0);
+ }
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ wc.stack_mode = Above;
+ wc.sibling = np->parent;
+ wc.border_width = 0;
+
+ ConstrainSize(np);
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ np->state.status &= ~STAT_MAXIMIZED;
+ }
+
+ wc.x = np->x;
+ wc.y = np->y;
+ wc.width = np->width + east + west;
+ wc.height = np->height + north + south;
+ JXConfigureWindow(display, np->parent, event->value_mask, &wc);
+
+ wc.x = west;
+ wc.y = north;
+ wc.width = np->width;
+ wc.height = np->height;
+ JXConfigureWindow(display, np->window, event->value_mask, &wc);
+
+ } else {
+
+ wc.stack_mode = event->detail;
+ wc.sibling = event->above;
+ wc.border_width = event->border_width;
+ wc.x = event->x;
+ wc.y = event->y;
+ wc.width = event->width > rootWidth ? rootWidth : event->width;
+ wc.height = event->height > rootHeight ? rootHeight : event->height;
+ JXConfigureWindow(display, event->window, event->value_mask, &wc);
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleEnterNotify(const XCrossingEvent *event) {
+
+ ClientNode *np;
+ Cursor cur;
+
+ SetMousePosition(event->x_root, event->y_root);
+
+ np = FindClientByWindow(event->window);
+ if(np) {
+ if(!(np->state.status & STAT_ACTIVE) && (focusModel == FOCUS_SLOPPY)) {
+ FocusClient(np);
+ }
+ if(np->parent == event->window) {
+ np->borderAction = GetBorderActionType(np, event->x, event->y);
+ cur = GetFrameCursor(np->borderAction);
+ JXDefineCursor(display, np->parent, cur);
+ } else if(np->borderAction != BA_NONE) {
+ SetDefaultCursor(np->parent);
+ np->borderAction = BA_NONE;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleLeaveNotify(const XCrossingEvent *event) {
+
+ ClientNode *np;
+
+ SetMousePosition(event->x_root, event->y_root);
+
+ np = FindClientByParent(event->window);
+ if(np) {
+ SetDefaultCursor(np->parent);
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int HandleExpose(const XExposeEvent *event) {
+
+ ClientNode *np;
+
+ np = FindClientByWindow(event->window);
+ if(np) {
+ if(event->window == np->parent) {
+ DrawBorder(np, event);
+ return 1;
+ } else if(event->window == np->window
+ && np->state.status & STAT_WMDIALOG) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ return event->count ? 1 : 0;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int HandlePropertyNotify(const XPropertyEvent *event) {
+
+ ClientNode *np;
+ int changed;
+
+ np = FindClientByWindow(event->window);
+ if(np) {
+ changed = 0;
+ switch(event->atom) {
+ case XA_WM_NAME:
+ ReadWMName(np);
+ changed = 1;
+ break;
+ case XA_WM_NORMAL_HINTS:
+ ReadWMNormalHints(np);
+ changed = 1;
+ break;
+ case XA_WM_HINTS:
+ case XA_WM_ICON_NAME:
+ case XA_WM_CLIENT_MACHINE:
+ break;
+ default:
+ if(event->atom == atoms[ATOM_WM_COLORMAP_WINDOWS]) {
+ ReadWMColormaps(np);
+ UpdateClientColormap(np);
+ } else if(event->atom == atoms[ATOM_NET_WM_ICON]) {
+ LoadIcon(np);
+ changed = 1;
+ } else if(event->atom == atoms[ATOM_NET_WM_NAME]) {
+ ReadWMName(np);
+ changed = 1;
+ } else if(event->atom == atoms[ATOM_NET_WM_STRUT_PARTIAL]) {
+ ReadClientStrut(np);
+ } else if(event->atom == atoms[ATOM_NET_WM_STRUT]) {
+ ReadClientStrut(np);
+ }
+ break;
+ }
+
+ if(changed) {
+ DrawBorder(np, NULL);
+ UpdateTaskBar();
+ UpdatePager();
+ }
+ if(np->state.status & STAT_WMDIALOG) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ return 1;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleClientMessage(const XClientMessageEvent *event) {
+
+ ClientNode *np;
+ long mask, flags;
+#ifdef DEBUG
+ char *atomName;
+#endif
+
+ np = FindClientByWindow(event->window);
+ if(np) {
+ if(event->message_type == atoms[ATOM_WIN_STATE]) {
+
+ mask = event->data.l[0];
+ flags = event->data.l[1];
+
+ if(mask & WIN_STATE_STICKY) {
+ if(flags & WIN_STATE_STICKY) {
+ SetClientSticky(np, 1);
+ } else {
+ SetClientSticky(np, 0);
+ }
+ }
+
+ if(mask & WIN_STATE_HIDDEN) {
+ if(flags & WIN_STATE_HIDDEN) {
+ np->state.status |= STAT_NOLIST;
+ } else {
+ np->state.status &= ~STAT_NOLIST;
+ }
+ UpdateTaskBar();
+ UpdatePager();
+ }
+
+ } else if(event->message_type == atoms[ATOM_WIN_LAYER]) {
+
+ SetClientLayer(np, event->data.l[0]);
+
+ } else if(event->message_type == atoms[ATOM_WM_CHANGE_STATE]) {
+
+ if(np->controller) {
+ (np->controller)(0);
+ }
+
+ switch(event->data.l[0]) {
+ case WithdrawnState:
+ SetClientWithdrawn(np);
+ break;
+ case IconicState:
+ MinimizeClient(np);
+ break;
+ case NormalState:
+ RestoreClient(np, 1);
+ break;
+ default:
+ break;
+ }
+
+ } else if(event->message_type == atoms[ATOM_NET_ACTIVE_WINDOW]) {
+
+ RestoreClient(np, 1);
+ FocusClient(np);
+
+ } else if(event->message_type == atoms[ATOM_NET_WM_DESKTOP]) {
+
+ if(event->data.l[0] == ~0L) {
+ SetClientSticky(np, 1);
+ } else {
+
+ if(np->controller) {
+ (np->controller)(0);
+ }
+
+ if(event->data.l[0] >= 0 && event->data.l[0] < (long)desktopCount) {
+ np->state.status &= ~STAT_STICKY;
+ SetClientDesktop(np, event->data.l[0]);
+ }
+ }
+
+ } else if(event->message_type == atoms[ATOM_NET_CLOSE_WINDOW]) {
+
+ DeleteClient(np);
+
+ } else if(event->message_type == atoms[ATOM_NET_MOVERESIZE_WINDOW]) {
+
+ HandleNetMoveResize(event, np);
+
+ } else if(event->message_type == atoms[ATOM_NET_WM_STATE]) {
+
+ HandleNetWMState(event, np);
+
+ } else {
+
+#ifdef DEBUG
+ atomName = JXGetAtomName(display, event->message_type);
+ Debug("Uknown ClientMessage to client: %s", atomName);
+ JXFree(atomName);
+#endif
+
+ }
+
+ } else if(event->window == rootWindow) {
+
+ if(event->message_type == atoms[ATOM_JWM_RESTART]) {
+ Restart();
+ } else if(event->message_type == atoms[ATOM_JWM_EXIT]) {
+ Exit();
+ } else if(event->message_type == atoms[ATOM_NET_CURRENT_DESKTOP]) {
+ ChangeDesktop(event->data.l[0]);
+ } else {
+#ifdef DEBUG
+ atomName = JXGetAtomName(display, event->message_type);
+ Debug("Uknown ClientMessage to root: %s", atomName);
+ JXFree(atomName);
+#endif
+ }
+
+ } else if(event->message_type == atoms[ATOM_NET_SYSTEM_TRAY_OPCODE]) {
+
+ HandleDockEvent(event);
+
+ }
+
+}
+
+/****************************************************************************
+ * Handle a _NET_MOVERESIZE_WINDOW request.
+ ****************************************************************************/
+void HandleNetMoveResize(const XClientMessageEvent *event, ClientNode *np) {
+
+ long flags, gravity;
+ long x, y;
+ long width, height;
+ int deltax, deltay;
+ int north, south, east, west;
+
+ Assert(event);
+ Assert(np);
+
+ gravity = event->data.l[0] & 0xFF;
+ flags = event->data.l[0] >> 8;
+
+ x = np->x;
+ y = np->y;
+ width = np->width;
+ height = np->height;
+
+ if(flags & (1 << 0)) {
+ x = event->data.l[1];
+ }
+ if(flags & (1 << 1)) {
+ y = event->data.l[2];
+ }
+ if(flags & (1 << 2)) {
+ width = event->data.l[3];
+ }
+ if(flags & (1 << 3)) {
+ height = event->data.l[4];
+ }
+
+ if(gravity == 0) {
+ gravity = np->gravity;
+ }
+
+ GetBorderSize(np, &north, &south, &east, &west);
+ GetGravityDelta(np, &deltax, &deltay);
+
+ x -= deltax;
+ y -= deltay;
+
+ np->x = x;
+ np->y = y;
+ np->width = width;
+ np->height = height;
+
+ JXMoveResizeWindow(display, np->parent,
+ np->x - west, np->y - north,
+ np->width + east + west,
+ np->height + north + south);
+ JXMoveResizeWindow(display, np->window, west, north,
+ np->width, np->height);
+
+ WriteState(np);
+ SendConfigureEvent(np);
+
+}
+
+/****************************************************************************
+ * Handle a _NET_WM_STATE request.
+ ****************************************************************************/
+void HandleNetWMState(const XClientMessageEvent *event, ClientNode *np) {
+
+ int actionMaximize;
+ int actionStick;
+ int actionShade;
+ int actionFullScreen;
+ int x;
+
+ /* Up to two actions to be applied together, figure it out. */
+ actionMaximize = 0;
+ actionStick = 0;
+ actionShade = 0;
+ actionFullScreen = 0;
+
+ for(x = 1; x <= 2; x++) {
+ if(event->data.l[x]
+ == (long)atoms[ATOM_NET_WM_STATE_STICKY]) {
+ actionStick = 1;
+ } else if(event->data.l[x]
+ == (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT]) {
+ actionMaximize = 1;
+ } else if(event->data.l[x]
+ == (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) {
+ actionMaximize = 1;
+ } else if(event->data.l[x]
+ == (long)atoms[ATOM_NET_WM_STATE_SHADED]) {
+ actionShade = 1;
+ } else if(event->data.l[x]
+ == (long)atoms[ATOM_NET_WM_STATE_FULLSCREEN]) {
+ actionFullScreen = 1;
+ }
+ }
+
+ switch(event->data.l[0]) {
+ case 0: /* Remove */
+ if(actionStick) {
+ SetClientSticky(np, 0);
+ }
+ if(actionMaximize && (np->state.status & STAT_MAXIMIZED)) {
+ MaximizeClient(np);
+ }
+ if(actionShade) {
+ UnshadeClient(np);
+ }
+ if(actionFullScreen) {
+ SetClientFullScreen(np, 0);
+ }
+ break;
+ case 1: /* Add */
+ if(actionStick) {
+ SetClientSticky(np, 1);
+ }
+ if(actionMaximize && !(np->state.status & STAT_MAXIMIZED)) {
+ MaximizeClient(np);
+ }
+ if(actionShade) {
+ ShadeClient(np);
+ }
+ if(actionFullScreen) {
+ SetClientFullScreen(np, 1);
+ }
+ break;
+ case 2: /* Toggle */
+ if(actionStick) {
+ if(np->state.status & STAT_STICKY) {
+ SetClientSticky(np, 0);
+ } else {
+ SetClientSticky(np, 1);
+ }
+ }
+ if(actionMaximize) {
+ MaximizeClient(np);
+ }
+ if(actionShade) {
+ if(np->state.status & STAT_SHADED) {
+ UnshadeClient(np);
+ } else {
+ ShadeClient(np);
+ }
+ }
+ if(actionFullScreen) {
+ if(np->state.status & STAT_FULLSCREEN) {
+ SetClientFullScreen(np, 0);
+ } else {
+ SetClientFullScreen(np, 1);
+ }
+ }
+ break;
+ default:
+ Debug("bad _NET_WM_STATE action: %ld", event->data.l[0]);
+ break;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleMotionNotify(const XMotionEvent *event) {
+
+ ClientNode *np;
+ Cursor cur;
+ BorderActionType action;
+
+ if(event->is_hint) {
+ return;
+ }
+
+ SetMousePosition(event->x_root, event->y_root);
+
+ np = FindClientByParent(event->window);
+ if(np && (np->state.border & BORDER_OUTLINE)) {
+ action = GetBorderActionType(np, event->x, event->y);
+ if(np->borderAction != action) {
+ np->borderAction = action;
+ cur = GetFrameCursor(action);
+ JXDefineCursor(display, np->parent, cur);
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+#ifdef USE_SHAPE
+void HandleShapeEvent(const XShapeEvent *event) {
+
+ ClientNode *np;
+
+ np = FindClientByWindow(event->window);
+ if(np) {
+ SetShape(np);
+ }
+
+}
+#endif /* USE_SHAPE */
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleColormapChange(const XColormapEvent *event) {
+ ClientNode *np;
+
+ if(event->new == True) {
+ np = FindClientByWindow(event->window);
+ if(np) {
+ np->cmap = event->colormap;
+ UpdateClientColormap(np);
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleMapRequest(const XMapEvent *event) {
+
+ ClientNode *np;
+
+ Assert(event);
+
+ if(CheckSwallowMap(event)) {
+ return;
+ }
+
+ np = FindClientByWindow(event->window);
+ if(!np) {
+ JXSync(display, False);
+ JXGrabServer(display);
+ np = AddClientWindow(event->window, 0, 1);
+ if(np) {
+ if(focusModel == FOCUS_CLICK) {
+ FocusClient(np);
+ }
+ } else {
+ JXMapWindow(display, event->window);
+ }
+ JXSync(display, False);
+ JXUngrabServer(display);
+ } else {
+ if(!(np->state.status & STAT_MAPPED)) {
+ np->state.status |= STAT_MAPPED;
+ np->state.status &= ~STAT_MINIMIZED;
+ np->state.status &= ~STAT_SDESKTOP;
+ JXMapWindow(display, np->window);
+ JXMapWindow(display, np->parent);
+ RaiseClient(np);
+ if(focusModel == FOCUS_CLICK) {
+ FocusClient(np);
+ }
+ UpdateTaskBar();
+ UpdatePager();
+ }
+ }
+ RestackClients();
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleUnmapNotify(const XUnmapEvent *event) {
+
+ ClientNode *np;
+ XEvent e;
+
+ Assert(event);
+
+ np = FindClientByWindow(event->window);
+ if(np && np->window == event->window) {
+
+ if(JXCheckTypedWindowEvent(display, np->window, DestroyNotify, &e)) {
+ HandleDestroyNotify(&e.xdestroywindow);
+ return;
+ }
+
+ if(np->controller) {
+ (np->controller)(1);
+ }
+
+ if(np->state.status & STAT_MAPPED) {
+
+ np->state.status &= ~STAT_MAPPED;
+ JXUnmapWindow(display, np->parent);
+
+ WriteState(np);
+ UpdateTaskBar();
+ UpdatePager();
+
+ }
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int HandleDestroyNotify(const XDestroyWindowEvent *event) {
+
+ ClientNode *np;
+
+ np = FindClientByWindow(event->window);
+ if(np && np->window == event->window) {
+
+ if(np->controller) {
+ (np->controller)(1);
+ }
+
+ RemoveClient(np);
+
+ return 1;
+
+ } else if(!np) {
+
+ return HandleDockDestroy(event->window);
+
+ }
+
+ return 0;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DispatchBorderButtonEvent(const XButtonEvent *event, ClientNode *np) {
+
+ static Time lastClickTime = 0;
+ static int lastX = 0, lastY = 0;
+ static int doubleClickActive = 0;
+ BorderActionType action;
+ int bsize;
+
+ action = GetBorderActionType(np, event->x, event->y);
+
+ switch(action & 0x0F) {
+ case BA_RESIZE:
+ if(event->type == ButtonPress) {
+ ResizeClient(np, action, event->x, event->y);
+ }
+ break;
+ case BA_MOVE:
+ if(event->type == ButtonPress) {
+ if(doubleClickActive
+ && abs(event->time - lastClickTime) > 0
+ && abs(event->time - lastClickTime) <= doubleClickSpeed
+ && abs(event->x - lastX) <= doubleClickDelta
+ && abs(event->y - lastY) <= doubleClickDelta) {
+ MaximizeClient(np);
+ doubleClickActive = 0;
+ } else {
+ if(MoveClient(np, event->x, event->y)) {
+ doubleClickActive = 0;
+ } else {
+ doubleClickActive = 1;
+ lastClickTime = event->time;
+ lastX = event->x;
+ lastY = event->y;
+ }
+ }
+ }
+ break;
+ case BA_MENU:
+ if(event->type == ButtonPress) {
+ if(np->state.border & BORDER_OUTLINE) {
+ bsize = borderWidth;
+ } else {
+ bsize = 0;
+ }
+ ShowWindowMenu(np, np->x + event->x - bsize,
+ np->y + event->y - titleHeight - bsize);
+ }
+ break;
+ case BA_CLOSE:
+ if(event->type == ButtonRelease) {
+ DeleteClient(np);
+ }
+ break;
+ case BA_MAXIMIZE:
+ if(event->type == ButtonRelease) {
+ MaximizeClient(np);
+ }
+ break;
+ case BA_MINIMIZE:
+ if(event->type == ButtonRelease) {
+ MinimizeClient(np);
+ }
+ break;
+ default:
+ break;
+ }
+
+}
+
diff --git a/src/event.h b/src/event.h
new file mode 100644
index 0000000..627442b
--- /dev/null
+++ b/src/event.h
@@ -0,0 +1,28 @@
+/**
+ * @file event.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the event functions.
+ *
+ */
+
+#ifndef EVENT_H
+#define EVENT_H
+
+/** Wait for an event and process it. */
+void WaitForEvent();
+
+/** Process an event.
+ * @param event The event to process.
+ */
+void ProcessEvent(XEvent *event);
+
+/** Discard excess motion events.
+ * @param event The event to return.
+ * @param w The window whose events to discard.
+ */
+void DiscardMotionEvents(XEvent *event, Window w);
+
+#endif
+
diff --git a/src/font.c b/src/font.c
new file mode 100644
index 0000000..8ebf19a
--- /dev/null
+++ b/src/font.c
@@ -0,0 +1,295 @@
+/****************************************************************************
+ * Functions to load fonts.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "font.h"
+#include "main.h"
+#include "error.h"
+#include "color.h"
+#include "misc.h"
+
+static const char *DEFAULT_FONT = "-*-courier-*-r-*-*-14-*-*-*-*-*-*-*";
+
+static char *fontNames[FONT_COUNT];
+
+#ifdef USE_XFT
+static XftFont *fonts[FONT_COUNT];
+#else
+static XFontStruct *fonts[FONT_COUNT];
+static GC fontGC;
+#endif
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeFonts() {
+
+ int x;
+
+ for(x = 0; x < FONT_COUNT; x++) {
+ fonts[x] = NULL;
+ fontNames[x] = NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupFonts() {
+
+#ifndef USE_XFT
+ XGCValues gcValues;
+ unsigned long gcMask;
+#endif
+ int x;
+
+ /* Inherit unset fonts from the tray for tray items. */
+ if(!fontNames[FONT_TASK]) {
+ fontNames[FONT_TASK] = CopyString(fontNames[FONT_TRAY]);
+ }
+ if(!fontNames[FONT_TRAYBUTTON]) {
+ fontNames[FONT_TRAYBUTTON] = CopyString(fontNames[FONT_TRAY]);
+ }
+ if(!fontNames[FONT_CLOCK]) {
+ fontNames[FONT_CLOCK] = CopyString(fontNames[FONT_TRAY]);
+ }
+
+#ifdef USE_XFT
+
+ for(x = 0; x < FONT_COUNT; x++) {
+ if(fontNames[x]) {
+ fonts[x] = JXftFontOpenName(display, rootScreen, fontNames[x]);
+ if(!fonts[x]) {
+ fonts[x] = JXftFontOpenXlfd(display, rootScreen, fontNames[x]);
+ }
+ if(!fonts[x]) {
+ Warning("could not load font: %s", fontNames[x]);
+ }
+ }
+ if(!fonts[x]) {
+ fonts[x] = JXftFontOpenXlfd(display, rootScreen, DEFAULT_FONT);
+ }
+ if(!fonts[x]) {
+ FatalError("could not load the default font: %s", DEFAULT_FONT);
+ }
+ }
+
+#else
+
+ for(x = 0; x < FONT_COUNT; x++) {
+ if(fontNames[x]) {
+ fonts[x] = JXLoadQueryFont(display, fontNames[x]);
+ if(!fonts[x] && fontNames[x]) {
+ Warning("could not load font: %s", fontNames[x]);
+ }
+ }
+ if(!fonts[x]) {
+ fonts[x] = JXLoadQueryFont(display, DEFAULT_FONT);
+ }
+ if(!fonts[x]) {
+ FatalError("could not load the default font: %s", DEFAULT_FONT);
+ }
+ }
+
+ gcMask = GCGraphicsExposures;
+ gcValues.graphics_exposures = False;
+ fontGC = JXCreateGC(display, rootWindow, gcMask, &gcValues);
+
+#endif
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownFonts() {
+
+ int x;
+
+ for(x = 0; x < FONT_COUNT; x++) {
+ if(fonts[x]) {
+#ifdef USE_XFT
+ JXftFontClose(display, fonts[x]);
+#else
+ JXFreeFont(display, fonts[x]);
+#endif
+ fonts[x] = NULL;
+ }
+ }
+
+#ifndef USE_XFT
+
+ JXFreeGC(display, fontGC);
+
+#endif
+
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyFonts() {
+
+ int x;
+
+ for(x = 0; x < FONT_COUNT; x++) {
+ if(fontNames[x]) {
+ Release(fontNames[x]);
+ fontNames[x] = NULL;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GetStringWidth(FontType type, const char *str) {
+#ifdef USE_XFT
+
+ XGlyphInfo extents;
+ unsigned int length;
+
+ Assert(str);
+ Assert(fonts[type]);
+
+ length = strlen(str);
+
+ JXftTextExtentsUtf8(display, fonts[type], (const unsigned char*)str,
+ length, &extents);
+
+ return extents.width;
+
+#else
+
+ Assert(str);
+ Assert(fonts[type]);
+
+ return XTextWidth(fonts[type], str, strlen(str));
+
+#endif
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GetStringHeight(FontType type) {
+
+ Assert(fonts[type]);
+
+ return fonts[type]->ascent + fonts[type]->descent;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetFont(FontType type, const char *value) {
+
+ if(!value) {
+ Warning("empty Font tag");
+ return;
+ }
+
+ if(fontNames[type]) {
+ Release(fontNames[type]);
+ }
+
+ fontNames[type] = CopyString(value);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void RenderString(Drawable d, FontType font, ColorType color,
+ int x, int y, int width, Region region, const char *str) {
+
+#ifdef USE_XFT
+ XftDraw *xd;
+#endif
+
+ XRectangle rect;
+ Region renderRegion;
+ int len;
+ char *output;
+
+#ifdef USE_FRIBIDI
+
+ FriBidiChar *temp;
+ FriBidiCharType type = FRIBIDI_TYPE_ON;
+ int unicodeLength;
+
+#endif
+
+ if(!str) {
+ return;
+ }
+
+ len = strlen(str);
+ if(len == 0) {
+ return;
+ }
+
+ /* Get the bounds for the string based on the specified width. */
+ rect.x = x;
+ rect.y = y;
+ rect.width = Min(GetStringWidth(font, str), width) + 2;
+ rect.height = GetStringHeight(font);
+
+ /* Create a region to use. */
+ renderRegion = XCreateRegion();
+
+ /* Combine the width bounds with the region to use. */
+ XUnionRectWithRegion(&rect, renderRegion, renderRegion);
+
+ /* Combine the provided region with the region to use. */
+ if(region) {
+ XIntersectRegion(region, renderRegion, renderRegion);
+ }
+
+ /* Apply the bidi algorithm if requested. */
+
+#ifdef USE_FRIBIDI
+
+ temp = AllocateStack((len + 1) * sizeof(FriBidiChar));
+ unicodeLength = fribidi_utf8_to_unicode((char*)str, len, temp);
+
+ fribidi_log2vis(temp, unicodeLength, &type, temp, NULL, NULL, NULL);
+
+ fribidi_unicode_to_utf8(temp, len, (char*)temp);
+ output = (char*)temp;
+
+#else
+
+ output = (char*)str;
+
+#endif
+
+ /* Display the string. */
+
+#ifdef USE_XFT
+
+ xd = XftDrawCreate(display, d, rootVisual, rootColormap);
+ XftDrawSetClip(xd, renderRegion);
+ JXftDrawStringUtf8(xd, GetXftColor(color), fonts[font],
+ x, y + fonts[font]->ascent, (const unsigned char*)output, len);
+ XftDrawDestroy(xd);
+
+#else
+
+ JXSetForeground(display, fontGC, colors[color]);
+ XSetRegion(display, fontGC, renderRegion);
+ JXSetFont(display, fontGC, fonts[font]->fid);
+ JXDrawString(display, d, fontGC, x, y + fonts[font]->ascent, output, len);
+
+#endif
+
+ /* Free any memory used for UTF conversion. */
+
+#ifdef USE_FRIBIDI
+
+ ReleaseStack(output);
+
+#endif
+
+ XDestroyRegion(renderRegion);
+
+}
+
diff --git a/src/font.h b/src/font.h
new file mode 100644
index 0000000..fe1c0c5
--- /dev/null
+++ b/src/font.h
@@ -0,0 +1,43 @@
+/**
+ * @file font.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the font functions.
+ *
+ */
+
+#ifndef FONT_H
+#define FONT_H
+
+#include "color.h"
+
+typedef enum {
+
+ FONT_BORDER,
+ FONT_MENU,
+ FONT_TASK,
+ FONT_POPUP,
+ FONT_CLOCK,
+ FONT_TRAY,
+ FONT_TRAYBUTTON,
+
+ FONT_COUNT
+
+} FontType;
+
+void InitializeFonts();
+void StartupFonts();
+void ShutdownFonts();
+void DestroyFonts();
+
+void SetFont(FontType type, const char *value);
+
+void RenderString(Drawable d, FontType font, ColorType color,
+ int x, int y, int width, Region region, const char *str);
+
+int GetStringWidth(FontType type, const char *str);
+int GetStringHeight(FontType type);
+
+#endif
+
diff --git a/src/group.c b/src/group.c
new file mode 100644
index 0000000..cd3aba4
--- /dev/null
+++ b/src/group.c
@@ -0,0 +1,299 @@
+/****************************************************************************
+ * Functions for handling window groups.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "group.h"
+#include "client.h"
+#include "icon.h"
+#include "error.h"
+#include "match.h"
+#include "desktop.h"
+#include "main.h"
+#include "misc.h"
+
+typedef enum {
+ MATCH_NAME,
+ MATCH_CLASS
+} MatchType;
+
+typedef struct PatternListType {
+ char *pattern;
+ MatchType match;
+ struct PatternListType *next;
+} PatternListType;
+
+typedef struct OptionListType {
+ OptionType option;
+ char *value;
+ struct OptionListType *next;
+} OptionListType;
+
+typedef struct GroupType {
+ PatternListType *patterns;
+ OptionListType *options;
+ struct GroupType *next;
+} GroupType;
+
+static GroupType *groups = NULL;
+
+static void ReleasePatternList(PatternListType *lp);
+static void ReleaseOptionList(OptionListType *lp);
+static void AddPattern(PatternListType **lp, const char *pattern,
+ MatchType match);
+static void ApplyGroup(const GroupType *gp, ClientNode *np);
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeGroups() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupGroups() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownGroups() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyGroups() {
+
+ GroupType *gp;
+
+ while(groups) {
+ gp = groups->next;
+ ReleasePatternList(groups->patterns);
+ ReleaseOptionList(groups->options);
+ Release(groups);
+ groups = gp;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReleasePatternList(PatternListType *lp) {
+
+ PatternListType *tp;
+
+ while(lp) {
+ tp = lp->next;
+ Release(lp->pattern);
+ Release(lp);
+ lp = tp;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReleaseOptionList(OptionListType *lp) {
+
+ OptionListType *tp;
+
+ while(lp) {
+ tp = lp->next;
+ if(lp->value) {
+ Release(lp->value);
+ }
+ Release(lp);
+ lp = tp;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+GroupType *CreateGroup() {
+ GroupType *tp;
+
+ tp = Allocate(sizeof(GroupType));
+ tp->patterns = NULL;
+ tp->options = NULL;
+ tp->next = groups;
+ groups = tp;
+
+ return tp;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddGroupClass(GroupType *gp, const char *pattern) {
+
+ Assert(gp);
+
+ if(pattern) {
+ AddPattern(&gp->patterns, pattern, MATCH_CLASS);
+ } else {
+ Warning("invalid group class");
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddGroupName(GroupType *gp, const char *pattern) {
+
+ Assert(gp);
+
+ if(pattern) {
+ AddPattern(&gp->patterns, pattern, MATCH_NAME);
+ } else {
+ Warning("invalid group name");
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddPattern(PatternListType **lp, const char *pattern, MatchType match) {
+
+ PatternListType *tp;
+
+ Assert(lp);
+ Assert(pattern);
+
+ tp = Allocate(sizeof(PatternListType));
+ tp->next = *lp;
+ *lp = tp;
+
+ tp->pattern = CopyString(pattern);
+ tp->match = match;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddGroupOption(GroupType *gp, OptionType option) {
+
+ OptionListType *lp;
+
+ lp = Allocate(sizeof(OptionListType));
+ lp->option = option;
+ lp->value = NULL;
+ lp->next = gp->options;
+ gp->options = lp;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddGroupOptionValue(GroupType *gp, OptionType option,
+ const char *value) {
+
+ OptionListType *lp;
+
+ Assert(value);
+
+ lp = Allocate(sizeof(OptionListType));
+ lp->option = option;
+ lp->value = CopyString(value);
+ lp->next = gp->options;
+ gp->options = lp;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ApplyGroups(ClientNode *np) {
+
+ PatternListType *lp;
+ GroupType *gp;
+
+ Assert(np);
+
+ for(gp = groups; gp; gp = gp->next) {
+ for(lp = gp->patterns; lp; lp = lp->next) {
+ if(lp->match == MATCH_CLASS) {
+ if(Match(lp->pattern, np->className)) {
+ ApplyGroup(gp, np);
+ break;
+ }
+ } else if(lp->match == MATCH_NAME) {
+ if(Match(lp->pattern, np->name)) {
+ ApplyGroup(gp, np);
+ break;
+ }
+ } else {
+ Debug("invalid match in ApplyGroups: %d", lp->match);
+ }
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ApplyGroup(const GroupType *gp, ClientNode *np) {
+
+ OptionListType *lp;
+ unsigned int temp;
+
+ Assert(gp);
+ Assert(np);
+
+ for(lp = gp->options; lp; lp = lp->next) {
+ switch(lp->option) {
+ case OPTION_STICKY:
+ np->state.status |= STAT_STICKY;
+ break;
+ case OPTION_NOLIST:
+ np->state.status |= STAT_NOLIST;
+ break;
+ case OPTION_BORDER:
+ np->state.border |= BORDER_OUTLINE;
+ break;
+ case OPTION_NOBORDER:
+ np->state.border &= ~BORDER_OUTLINE;
+ break;
+ case OPTION_TITLE:
+ np->state.border |= BORDER_TITLE;
+ break;
+ case OPTION_NOTITLE:
+ np->state.border &= ~BORDER_TITLE;
+ break;
+ case OPTION_LAYER:
+ temp = atoi(lp->value);
+ if(temp <= LAYER_COUNT) {
+ SetClientLayer(np, temp);
+ } else {
+ Warning("invalid group layer: %s", lp->value);
+ }
+ break;
+ case OPTION_DESKTOP:
+ temp = atoi(lp->value);
+ if(temp >= 1 && temp <= desktopCount) {
+ np->state.desktop = temp - 1;
+ } else {
+ Warning("invalid group desktop: %s", lp->value);
+ }
+ break;
+ case OPTION_ICON:
+ DestroyIcon(np->icon);
+ np->icon = LoadNamedIcon(lp->value);
+ break;
+ case OPTION_PIGNORE:
+ np->state.status |= STAT_PIGNORE;
+ break;
+ case OPTION_MAXIMIZED:
+ np->state.status |= STAT_MAXIMIZED;
+ break;
+ case OPTION_MINIMIZED:
+ np->state.status |= STAT_MINIMIZED;
+ break;
+ case OPTION_SHADED:
+ np->state.status |= STAT_SHADED;
+ break;
+ default:
+ Debug("invalid option: %d", lp->option);
+ break;
+ }
+ }
+
+}
+
diff --git a/src/group.h b/src/group.h
new file mode 100644
index 0000000..ccbf62f
--- /dev/null
+++ b/src/group.h
@@ -0,0 +1,48 @@
+/**
+ * @file font.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Functions for handling window groups.
+ *
+ */
+
+#ifndef GROUP_H
+#define GROUP_H
+
+struct ClientNode;
+struct GroupType;
+
+typedef enum {
+ OPTION_INVALID = 0,
+ OPTION_STICKY = 1,
+ OPTION_LAYER = 2,
+ OPTION_DESKTOP = 3,
+ OPTION_ICON = 4,
+ OPTION_NOLIST = 5,
+ OPTION_BORDER = 6,
+ OPTION_NOBORDER = 7,
+ OPTION_TITLE = 8,
+ OPTION_NOTITLE = 9,
+ OPTION_PIGNORE = 10,
+ OPTION_MAXIMIZED = 11,
+ OPTION_MINIMIZED = 12,
+ OPTION_SHADED = 13
+} OptionType;
+
+void InitializeGroups();
+void StartupGroups();
+void ShutdownGroups();
+void DestroyGroups();
+
+struct GroupType *CreateGroup();
+void AddGroupClass(struct GroupType *gp, const char *pattern);
+void AddGroupName(struct GroupType *gp, const char *pattern);
+void AddGroupOption(struct GroupType *gp, OptionType option);
+void AddGroupOptionValue(struct GroupType *gp, OptionType option,
+ const char *value);
+
+void ApplyGroups(struct ClientNode *np);
+
+#endif
+
diff --git a/src/help.c b/src/help.c
new file mode 100644
index 0000000..cdcd377
--- /dev/null
+++ b/src/help.c
@@ -0,0 +1,84 @@
+/**
+ * @file help.c
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Functions for displaying information about JWM.
+ *
+ */
+
+#include "jwm.h"
+#include "help.h"
+
+/** Display program name, version, and compiled options . */
+void DisplayAbout() {
+ printf("JWM v%s by Joe Wingbermuehle\n", PACKAGE_VERSION);
+ DisplayCompileOptions();
+}
+
+/** Display compiled options. */
+void DisplayCompileOptions() {
+
+ printf("compiled options: ");
+
+#ifndef DISABLE_CONFIRM
+ printf("confirm ");
+#endif
+
+#ifdef DEBUG
+ printf("debug ");
+#endif
+
+#ifdef USE_FRIBIDI
+ printf("fribidi ");
+#endif
+
+#ifdef USE_ICONS
+ printf("icons ");
+#endif
+
+#ifdef USE_PNG
+ printf("png ");
+#endif
+
+#ifdef USE_SHAPE
+ printf("shape ");
+#endif
+
+#ifdef USE_XFT
+ printf("xft ");
+#endif
+
+#ifdef USE_XINERAMA
+ printf("xinerama ");
+#endif
+
+#ifdef USE_XPM
+ printf("xpm ");
+#endif
+
+#ifdef USE_XRENDER
+ printf("xrender ");
+#endif
+
+ printf("\nsystem configuration: %s\n", SYSTEM_CONFIG);
+
+}
+
+/** Display all help. */
+void DisplayHelp() {
+ DisplayUsage();
+ printf(" -display X Set the X display to use\n");
+ printf(" -exit Exit JWM (send _JWM_EXIT to the root)\n");
+ printf(" -h Display this help message\n");
+ printf(" -p Parse the configuration file and exit\n");
+ printf(" -restart Restart JWM (send _JWM_RESTART to the root)\n");
+ printf(" -v Display version information\n");
+}
+
+/** Display program usage information. */
+void DisplayUsage() {
+ DisplayAbout();
+ printf("usage: jwm [ options ]\n");
+}
+
diff --git a/src/help.h b/src/help.h
new file mode 100644
index 0000000..dca2f75
--- /dev/null
+++ b/src/help.h
@@ -0,0 +1,26 @@
+/**
+ * @file help.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the help functions.
+ *
+ */
+
+#ifndef HELP_H
+#define HELP_H
+
+/** Display program name, version, and compiled options . */
+void DisplayAbout();
+
+/** Display compiled options. */
+void DisplayCompileOptions();
+
+/** Display all help. */
+void DisplayHelp();
+
+/** Display program usage information. */
+void DisplayUsage();
+
+#endif
+
diff --git a/src/hint.c b/src/hint.c
new file mode 100644
index 0000000..6ab096b
--- /dev/null
+++ b/src/hint.c
@@ -0,0 +1,970 @@
+/****************************************************************************
+ * Functions to handle hints.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "hint.h"
+#include "client.h"
+#include "main.h"
+#include "tray.h"
+#include "desktop.h"
+#include "misc.h"
+
+/* MWM Defines */
+#define MWM_HINTS_FUNCTIONS (1L << 0)
+#define MWM_HINTS_DECORATIONS (1L << 1)
+#define MWM_HINTS_INPUT_MODE (1L << 2)
+#define MWM_HINTS_STATUS (1L << 3)
+
+#define MWM_FUNC_ALL (1L << 0)
+#define MWM_FUNC_RESIZE (1L << 1)
+#define MWM_FUNC_MOVE (1L << 2)
+#define MWM_FUNC_MINIMIZE (1L << 3)
+#define MWM_FUNC_MAXIMIZE (1L << 4)
+#define MWM_FUNC_CLOSE (1L << 5)
+
+#define MWM_DECOR_ALL (1L << 0)
+#define MWM_DECOR_BORDER (1L << 1)
+#define MWM_DECOR_RESIZEH (1L << 2)
+#define MWM_DECOR_TITLE (1L << 3)
+#define MWM_DECOR_MENU (1L << 4)
+#define MWM_DECOR_MINIMIZE (1L << 5)
+#define MWM_DECOR_MAXIMIZE (1L << 6)
+
+#define MWM_INPUT_MODELESS 0
+#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1
+#define MWM_INPUT_SYSTEM_MODAL 2
+#define MWM_INPUT_FULL_APPLICATION_MODAL 3
+
+#define MWM_TEAROFF_WINDOW (1L << 0)
+
+typedef struct {
+
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long inputMode;
+ unsigned long status;
+
+} PropMwmHints;
+
+typedef struct {
+ Atom *atom;
+ const char *name;
+} ProtocolNode;
+
+typedef struct {
+ Atom *atom;
+ const char *name;
+} AtomNode;
+
+Atom atoms[ATOM_COUNT];
+
+static const AtomNode atomList[] = {
+
+ { &atoms[ATOM_COMPOUND_TEXT], "COMPOUND_TEXT" },
+ { &atoms[ATOM_UTF8_STRING], "UTF8_STRING" },
+
+ { &atoms[ATOM_WM_STATE], "WM_STATE" },
+ { &atoms[ATOM_WM_PROTOCOLS], "WM_PROTOCOLS" },
+ { &atoms[ATOM_WM_DELETE_WINDOW], "WM_DELETE_WINDOW" },
+ { &atoms[ATOM_WM_TAKE_FOCUS], "WM_TAKE_FOCUS" },
+ { &atoms[ATOM_WM_LOCALE_NAME], "WM_LOCALE_NAME" },
+ { &atoms[ATOM_WM_CHANGE_STATE], "WM_CHANGE_STATE" },
+ { &atoms[ATOM_WM_COLORMAP_WINDOWS], "WM_COLORMAP_WINDOWS" },
+
+ { &atoms[ATOM_NET_SUPPORTED], "_NET_SUPPORTED" },
+ { &atoms[ATOM_NET_NUMBER_OF_DESKTOPS], "_NET_NUMBER_OF_DESKTOPS" },
+ { &atoms[ATOM_NET_DESKTOP_NAMES], "_NET_DESKTOP_NAMES" },
+ { &atoms[ATOM_NET_DESKTOP_GEOMETRY], "_NET_DESKTOP_GEOMETRY" },
+ { &atoms[ATOM_NET_DESKTOP_VIEWPORT], "_NET_DESKTOP_VIEWPORT" },
+ { &atoms[ATOM_NET_CURRENT_DESKTOP], "_NET_CURRENT_DESKTOP" },
+ { &atoms[ATOM_NET_ACTIVE_WINDOW], "_NET_ACTIVE_WINDOW" },
+ { &atoms[ATOM_NET_WORKAREA], "_NET_WORKAREA" },
+ { &atoms[ATOM_NET_SUPPORTING_WM_CHECK], "_NET_SUPPORTING_WM_CHECK" },
+ { &atoms[ATOM_NET_FRAME_EXTENTS], "_NET_FRAME_EXTENTS" },
+ { &atoms[ATOM_NET_WM_DESKTOP], "_NET_WM_DESKTOP" },
+ { &atoms[ATOM_NET_WM_STATE], "_NET_WM_STATE" },
+ { &atoms[ATOM_NET_WM_STATE_STICKY], "_NET_WM_STATE_STICKY" },
+ { &atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT], "_NET_WM_STATE_MAXIMIZED_VERT"},
+ { &atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ], "_NET_WM_STATE_MAXIMIZED_HORZ"},
+ { &atoms[ATOM_NET_WM_STATE_SHADED], "_NET_WM_STATE_SHADED" },
+ { &atoms[ATOM_NET_WM_STATE_FULLSCREEN], "_NET_WM_STATE_FULLSCREEN" },
+ { &atoms[ATOM_NET_WM_ALLOWED_ACTIONS], "_NET_WM_ALLOWED_ACTIONS" },
+ { &atoms[ATOM_NET_WM_ACTION_MOVE], "_NET_WM_ACTION_MOVE" },
+ { &atoms[ATOM_NET_WM_ACTION_RESIZE], "_NET_WM_ACTION_RESIZE" },
+ { &atoms[ATOM_NET_WM_ACTION_MINIMIZE], "_NET_WM_ACTION_MINIMIZE" },
+ { &atoms[ATOM_NET_WM_ACTION_SHADE], "_NET_WM_ACTION_SHADE" },
+ { &atoms[ATOM_NET_WM_ACTION_STICK], "_NET_WM_ACTION_STICK" },
+ { &atoms[ATOM_NET_WM_ACTION_MAXIMIZE_HORZ], "_NET_WM_ACTION_MAXIMIZE_HORZ"},
+ { &atoms[ATOM_NET_WM_ACTION_MAXIMIZE_VERT], "_NET_WM_ACTION_MAXIMIZE_VERT"},
+ { &atoms[ATOM_NET_WM_ACTION_CHANGE_DESKTOP],
+ "_NET_WM_ACTION_CHANGE_DESKTOP"},
+ { &atoms[ATOM_NET_WM_ACTION_CLOSE], "_NET_WM_ACTION_CLOSE" },
+ { &atoms[ATOM_NET_CLOSE_WINDOW], "_NET_CLOSE_WINDOW" },
+ { &atoms[ATOM_NET_MOVERESIZE_WINDOW], "_NET_MOVERESIZE_WINDOW" },
+ { &atoms[ATOM_NET_WM_NAME], "_NET_WM_NAME" },
+ { &atoms[ATOM_NET_WM_ICON], "_NET_WM_ICON" },
+ { &atoms[ATOM_NET_WM_WINDOW_TYPE], "_NET_WM_WINDOW_TYPE" },
+ { &atoms[ATOM_NET_WM_WINDOW_TYPE_DESKTOP],"_NET_WM_WINDOW_TYPE_DESKTOP" },
+ { &atoms[ATOM_NET_WM_WINDOW_TYPE_DOCK], "_NET_WM_WINDOW_TYPE_DOCK" },
+ { &atoms[ATOM_NET_CLIENT_LIST], "_NET_CLIENT_LIST" },
+ { &atoms[ATOM_NET_CLIENT_LIST_STACKING], "_NET_CLIENT_LIST_STACKING" },
+ { &atoms[ATOM_NET_WM_STRUT_PARTIAL], "_NET_WM_STRUT_PARTIAL" },
+ { &atoms[ATOM_NET_WM_STRUT], "_NET_WM_STRUT" },
+ { &atoms[ATOM_NET_SYSTEM_TRAY_OPCODE], "_NET_SYSTEM_TRAY_OPCODE" },
+
+ { &atoms[ATOM_WIN_LAYER], "_WIN_LAYER" },
+ { &atoms[ATOM_WIN_STATE], "_WIN_STATE" },
+ { &atoms[ATOM_WIN_WORKSPACE], "_WIN_WORKSPACE" },
+ { &atoms[ATOM_WIN_WORKSPACE_COUNT], "_WIN_WORKSPACE_COUNT" },
+ { &atoms[ATOM_WIN_SUPPORTING_WM_CHECK], "_WIN_SUPPORTING_WM_CHECK" },
+ { &atoms[ATOM_WIN_PROTOCOLS], "_WIN_PROTOCOLS" },
+
+ { &atoms[ATOM_MOTIF_WM_HINTS], "_MOTIF_WM_HINTS" },
+
+ { &atoms[ATOM_JWM_RESTART], "_JWM_RESTART" },
+ { &atoms[ATOM_JWM_EXIT], "_JWM_EXIT" }
+
+};
+
+static void WriteNetState(ClientNode *np);
+static void WriteNetAllowed(ClientNode *np);
+static void WriteWinState(ClientNode *np);
+static void ReadWMHints(Window win, ClientState *state);
+static void ReadMotifHints(Window win, ClientState *state);
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeHints() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupHints() {
+
+ unsigned long array[128];
+ Atom supported[ATOM_COUNT];
+ Window win;
+ char *data;
+ unsigned int x;
+ unsigned int count;
+
+ /* Intern the atoms */
+ for(x = 0; x < ATOM_COUNT; x++) {
+ *atomList[x].atom = JXInternAtom(display, atomList[x].name, False);
+ }
+
+ /* _NET_SUPPORTED */
+ for(x = FIRST_NET_ATOM; x <= LAST_NET_ATOM; x++) {
+ supported[x - FIRST_NET_ATOM] = atoms[x];
+ }
+ JXChangeProperty(display, rootWindow, atoms[ATOM_NET_SUPPORTED],
+ XA_ATOM, 32, PropModeReplace, (unsigned char*)supported,
+ LAST_NET_ATOM - FIRST_NET_ATOM + 1);
+
+ /* _WIN_PROTOCOLS */
+ for(x = FIRST_WIN_ATOM; x <= LAST_WIN_ATOM; x++) {
+ supported[x - FIRST_WIN_ATOM] = atoms[x];
+ }
+ JXChangeProperty(display, rootWindow, atoms[ATOM_WIN_PROTOCOLS],
+ XA_ATOM, 32, PropModeReplace, (unsigned char*)supported,
+ LAST_WIN_ATOM - FIRST_WIN_ATOM + 1);
+
+ /* _NET_NUMBER_OF_DESKTOPS */
+ SetCardinalAtom(rootWindow, ATOM_NET_NUMBER_OF_DESKTOPS, desktopCount);
+
+ /* _NET_DESKTOP_NAMES */
+ count = 0;
+ for(x = 0; x < desktopCount; x++) {
+ count += strlen(desktopNames[x]) + 1;
+ }
+ data = AllocateStack(count);
+ count = 0;
+ for(x = 0; x < desktopCount; x++) {
+ strcpy(data + count, desktopNames[x]);
+ count += strlen(desktopNames[x]) + 1;
+ }
+ JXChangeProperty(display, rootWindow, atoms[ATOM_NET_DESKTOP_NAMES],
+ atoms[ATOM_UTF8_STRING], 8, PropModeReplace,
+ (unsigned char*)data, count);
+ ReleaseStack(data);
+
+ /* _NET_DESKTOP_GEOMETRY */
+ array[0] = rootWidth;
+ array[1] = rootHeight;
+ JXChangeProperty(display, rootWindow, atoms[ATOM_NET_DESKTOP_GEOMETRY],
+ XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char*)array, 2);
+
+ /* _NET_DESKTOP_VIEWPORT */
+ array[0] = 0;
+ array[1] = 0;
+ JXChangeProperty(display, rootWindow, atoms[ATOM_NET_DESKTOP_VIEWPORT],
+ XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char*)array, 2);
+
+ /* _NET_WORKAREA */
+ for(x = 0; x < desktopCount; x++) {
+ array[x * 4 + 0] = 0;
+ array[x * 4 + 1] = 0;
+ array[x * 4 + 2] = rootWidth;
+ array[x * 4 + 3] = rootHeight;
+ }
+ JXChangeProperty(display, rootWindow, atoms[ATOM_NET_WORKAREA],
+ XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char*)array, desktopCount * 4);
+
+ win = GetSupportingWindow();
+ JXChangeProperty(display, win, atoms[ATOM_NET_WM_NAME],
+ atoms[ATOM_UTF8_STRING], 8, PropModeReplace,
+ (unsigned char*)"JWM", 3);
+
+ SetWindowAtom(rootWindow, ATOM_NET_SUPPORTING_WM_CHECK, win);
+ SetWindowAtom(win, ATOM_NET_SUPPORTING_WM_CHECK, win);
+
+ SetWindowAtom(rootWindow, ATOM_WIN_SUPPORTING_WM_CHECK, win);
+ SetWindowAtom(win, ATOM_WIN_SUPPORTING_WM_CHECK, win);
+
+ SetCardinalAtom(rootWindow, ATOM_WIN_WORKSPACE_COUNT, desktopCount);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownHints() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyHints() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadCurrentDesktop() {
+
+ unsigned long temp;
+
+ currentDesktop = 0;
+
+ if(GetCardinalAtom(rootWindow, ATOM_NET_CURRENT_DESKTOP, &temp)) {
+ ChangeDesktop(temp);
+ } else if(GetCardinalAtom(rootWindow, ATOM_WIN_WORKSPACE, &temp)) {
+ ChangeDesktop(temp);
+ } else {
+ ChangeDesktop(0);
+ }
+
+}
+
+/****************************************************************************
+ * Read client protocls/hints.
+ * This is called while the client is being added to management.
+ ****************************************************************************/
+void ReadClientProtocols(ClientNode *np) {
+
+ Status status;
+ ClientNode *pp;
+
+ Assert(np);
+
+ ReadWMName(np);
+ ReadWMClass(np);
+ ReadWMNormalHints(np);
+ ReadWMColormaps(np);
+
+ status = JXGetTransientForHint(display, np->window, &np->owner);
+ if(!status) {
+ np->owner = None;
+ }
+
+ np->state = ReadWindowState(np->window);
+ if(np->minWidth == np->maxWidth && np->minHeight == np->maxHeight) {
+ np->state.border &= ~BORDER_RESIZE;
+ }
+
+ /* Set the client to the same layer as its owner. */
+ if(np->owner != None) {
+ pp = FindClientByWindow(np->owner);
+ if(pp) {
+ np->state.layer = pp->state.layer;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void WriteState(ClientNode *np) {
+
+ unsigned long data[2];
+
+ Assert(np);
+
+ if(np->state.status & STAT_MAPPED) {
+ data[0] = NormalState;
+ } else if(np->state.status & STAT_MINIMIZED) {
+ data[0] = IconicState;
+ } else {
+ data[0] = WithdrawnState;
+ }
+ data[1] = None;
+
+ JXChangeProperty(display, np->window, atoms[ATOM_WM_STATE],
+ atoms[ATOM_WM_STATE], 32, PropModeReplace,
+ (unsigned char*)data, 2);
+
+ WriteNetState(np);
+ WriteNetAllowed(np);
+ WriteWinState(np);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void WriteNetState(ClientNode *np) {
+
+ unsigned long values[5];
+ int north, south, east, west;
+ int index;
+
+ Assert(np);
+
+ index = 0;
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ values[index++] = atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT];
+ values[index++] = atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ];
+ }
+
+ if(np->state.status & STAT_SHADED) {
+ values[index++] = atoms[ATOM_NET_WM_STATE_SHADED];
+ }
+
+ if(np->state.status & STAT_STICKY) {
+ values[index++] = atoms[ATOM_NET_WM_STATE_STICKY];
+ }
+
+ if(np->state.status & STAT_FULLSCREEN) {
+ values[index++] = atoms[ATOM_NET_WM_STATE_FULLSCREEN];
+ }
+
+ JXChangeProperty(display, np->window, atoms[ATOM_NET_WM_STATE],
+ XA_ATOM, 32, PropModeReplace, (unsigned char*)values, index);
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ /* left, right, top, bottom */
+ values[0] = west;
+ values[1] = east;
+ values[2] = north;
+ values[3] = south;
+
+ JXChangeProperty(display, np->window, atoms[ATOM_NET_FRAME_EXTENTS],
+ XA_CARDINAL, 32, PropModeReplace, (unsigned char*)values, 4);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void WriteNetAllowed(ClientNode *np) {
+
+ unsigned long values[10];
+ int index;
+
+ Assert(np);
+
+ index = 0;
+
+ if(np->state.border & BORDER_TITLE) {
+ values[index++] = atoms[ATOM_NET_WM_ACTION_SHADE];
+ }
+
+ if(np->state.border & BORDER_MIN) {
+ values[index++] = atoms[ATOM_NET_WM_ACTION_MINIMIZE];
+ }
+
+ if(np->state.border & BORDER_MAX) {
+ values[index++] = atoms[ATOM_NET_WM_ACTION_MAXIMIZE_HORZ];
+ values[index++] = atoms[ATOM_NET_WM_ACTION_MAXIMIZE_VERT];
+ }
+
+ if(np->state.border & BORDER_CLOSE) {
+ values[index++] = atoms[ATOM_NET_WM_ACTION_CLOSE];
+ }
+
+ if(np->state.border & BORDER_RESIZE) {
+ values[index++] = atoms[ATOM_NET_WM_ACTION_RESIZE];
+ }
+
+ if(np->state.border & BORDER_MOVE) {
+ values[index++] = atoms[ATOM_NET_WM_ACTION_MOVE];
+ }
+
+ if(!(np->state.status & STAT_STICKY)) {
+ values[index++] = atoms[ATOM_NET_WM_ACTION_CHANGE_DESKTOP];
+ }
+
+ values[index++] = atoms[ATOM_NET_WM_ACTION_STICK];
+
+ JXChangeProperty(display, np->window, atoms[ATOM_NET_WM_ALLOWED_ACTIONS],
+ XA_ATOM, 32, PropModeReplace, (unsigned char*)values, index);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void WriteWinState(ClientNode *np) {
+
+ unsigned long flags;
+
+ Assert(np);
+
+ flags = 0;
+ if(np->state.status & STAT_STICKY) {
+ flags |= WIN_STATE_STICKY;
+ }
+ if(np->state.status & STAT_MINIMIZED) {
+ flags |= WIN_STATE_MINIMIZED;
+ }
+ if(np->state.status & STAT_MAXIMIZED) {
+ flags |= WIN_STATE_MAXIMIZED_VERT;
+ flags |= WIN_STATE_MAXIMIZED_HORIZ;
+ }
+ if(np->state.status & STAT_NOLIST) {
+ flags |= WIN_STATE_HIDDEN;
+ }
+ if(np->state.status & STAT_SHADED) {
+ flags |= WIN_STATE_SHADED;
+ }
+
+ SetCardinalAtom(np->window, ATOM_WIN_STATE, flags);
+
+}
+
+/****************************************************************************
+ * Read all hints needed to determine the current window state.
+ ****************************************************************************/
+ClientState ReadWindowState(Window win) {
+
+ ClientState result;
+ Status status;
+ unsigned long count, x;
+ unsigned long extra;
+ Atom realType;
+ int realFormat;
+ unsigned char *temp;
+ Atom *state;
+ unsigned long card;
+ int maxVert, maxHorz;
+ int fullScreen;
+
+ Assert(win != None);
+
+ result.status = STAT_NONE;
+ result.border = BORDER_DEFAULT;
+ result.layer = LAYER_NORMAL;
+ result.desktop = currentDesktop;
+
+ ReadWMHints(win, &result);
+ ReadMotifHints(win, &result);
+
+ /* _NET_WM_DESKTOP */
+ if(GetCardinalAtom(win, ATOM_NET_WM_DESKTOP, &card)) {
+ if(card == ~0UL) {
+ result.status |= STAT_STICKY;
+ } else if(card < desktopCount) {
+ result.desktop = card;
+ } else {
+ result.desktop = desktopCount - 1;
+ }
+ }
+
+ /* _NET_WM_STATE */
+ status = JXGetWindowProperty(display, win,
+ atoms[ATOM_NET_WM_STATE], 0, 32, False, XA_ATOM, &realType,
+ &realFormat, &count, &extra, &temp);
+ if(status == Success) {
+ if(count > 0) {
+ maxVert = 0;
+ maxHorz = 0;
+ fullScreen = 0;
+ state = (unsigned long*)temp;
+ for(x = 0; x < count; x++) {
+ if(state[x] == atoms[ATOM_NET_WM_STATE_STICKY]) {
+ result.status |= STAT_STICKY;
+ } else if(state[x] == atoms[ATOM_NET_WM_STATE_SHADED]) {
+ result.status |= STAT_SHADED;
+ } else if(state[x] == atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT]) {
+ maxVert = 1;
+ } else if(state[x] == atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) {
+ maxHorz = 1;
+ } else if(state[x] == atoms[ATOM_NET_WM_STATE_FULLSCREEN]) {
+ fullScreen = 1;
+ }
+ }
+ if(maxVert && maxHorz) {
+ result.status |= STAT_MAXIMIZED;
+ }
+ if(fullScreen) {
+ result.status |= STAT_FULLSCREEN;
+ }
+ }
+ if(temp) {
+ JXFree(temp);
+ }
+ }
+
+ /* _NET_WM_WINDOW_TYPE */
+ status = JXGetWindowProperty(display, win,
+ atoms[ATOM_NET_WM_WINDOW_TYPE], 0, 32, False, XA_ATOM, &realType,
+ &realFormat, &count, &extra, &temp);
+ if(status == Success) {
+ if(count > 0) {
+ state = (unsigned long*)temp;
+ for(x = 0; x < count; x++) {
+ if(state[x] == atoms[ATOM_NET_WM_WINDOW_TYPE_DESKTOP]) {
+ result.status |= STAT_STICKY | STAT_NOLIST;
+ result.layer = 0;
+ result.border = BORDER_NONE;
+ } else if(state[x] == atoms[ATOM_NET_WM_WINDOW_TYPE_DOCK]) {
+ result.status |= STAT_STICKY | STAT_NOLIST;
+ result.layer = 0;
+ result.border = BORDER_NONE;
+ }
+ }
+ }
+ if(temp) {
+ JXFree(temp);
+ }
+ }
+
+ /* _WIN_STATE */
+ if(GetCardinalAtom(win, ATOM_WIN_STATE, &card)) {
+ if(card & WIN_STATE_STICKY) {
+ result.status |= STAT_STICKY;
+ }
+ if(card & WIN_STATE_MINIMIZED) {
+ result.status |= STAT_MINIMIZED;
+ }
+ if(card & WIN_STATE_HIDDEN) {
+ result.status |= STAT_NOLIST;
+ }
+ if(card & WIN_STATE_SHADED) {
+ result.status |= STAT_SHADED;
+ }
+ if((card & WIN_STATE_MAXIMIZED_VERT)
+ && (card & WIN_STATE_MAXIMIZED_HORIZ)) {
+ result.status |= STAT_MAXIMIZED;
+ }
+ }
+
+ /* _WIN_LAYER */
+ if(GetCardinalAtom(win, ATOM_WIN_LAYER, &card)) {
+ result.layer = card;
+ }
+
+ return result;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadWMName(ClientNode *np) {
+
+ unsigned long count;
+ int status;
+ unsigned long extra;
+ Atom realType;
+ int realFormat;
+ unsigned char *name;
+
+ Assert(np);
+
+ if(np->name) {
+ JXFree(np->name);
+ }
+
+ status = JXGetWindowProperty(display, np->window,
+ atoms[ATOM_NET_WM_NAME], 0, 1024, False,
+ atoms[ATOM_UTF8_STRING], &realType, &realFormat, &count, &extra, &name);
+ if(status != Success) {
+ np->name = NULL;
+ } else {
+ np->name = (char*)name;
+ }
+
+ if(!np->name) {
+ if(JXFetchName(display, np->window, &np->name) == 0) {
+ np->name = NULL;
+ }
+ }
+
+ if(!np->name) {
+ status = JXGetWindowProperty(display, np->window, XA_WM_NAME,
+ 0, 1024, False, atoms[ATOM_COMPOUND_TEXT], &realType,
+ &realFormat, &count, &extra, &name);
+ if(status != Success) {
+ np->name = NULL;
+ } else {
+ np->name = (char*)name;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadWMClass(ClientNode *np) {
+
+ XClassHint hint;
+
+ Assert(np);
+
+ if(JXGetClassHint(display, np->window, &hint)) {
+ np->instanceName = hint.res_name;
+ np->className = hint.res_class;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+ClientProtocolType ReadWMProtocols(Window w) {
+
+ ClientProtocolType result;
+ unsigned long count, x;
+ int status;
+ unsigned long extra;
+ Atom realType;
+ int realFormat;
+ unsigned char *temp;
+ Atom *p;
+
+ Assert(w != None);
+
+ result = PROT_NONE;
+ status = JXGetWindowProperty(display, w, atoms[ATOM_WM_PROTOCOLS],
+ 0, 32, False, XA_ATOM, &realType, &realFormat, &count, &extra, &temp);
+ p = (Atom*)temp;
+
+ if(status != Success || !p) {
+ return result;
+ }
+
+ for(x = 0; x < count; x++) {
+ if(p[x] == atoms[ATOM_WM_DELETE_WINDOW]) {
+ result |= PROT_DELETE;
+ } else if(p[x] == atoms[ATOM_WM_TAKE_FOCUS]) {
+ result |= PROT_TAKE_FOCUS;
+ }
+ }
+
+ JXFree(p);
+
+ return result;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadWMNormalHints(ClientNode *np) {
+
+ XSizeHints hints;
+ long temp;
+
+ Assert(np);
+
+ if(!JXGetWMNormalHints(display, np->window, &hints, &temp)) {
+ np->sizeFlags = 0;
+ } else {
+ np->sizeFlags = hints.flags;
+ }
+
+ if(np->sizeFlags & PResizeInc) {
+ np->xinc = Max(1, hints.width_inc);
+ np->yinc = Max(1, hints.height_inc);
+ } else {
+ np->xinc = 1;
+ np->yinc = 1;
+ }
+
+ if(np->sizeFlags & PMinSize) {
+ np->minWidth = Max(0, hints.min_width);
+ np->minHeight = Max(0, hints.min_height);
+ } else {
+ np->minWidth = 1;
+ np->minHeight = 1;
+ }
+
+ if(np->sizeFlags & PMaxSize) {
+ np->maxWidth = hints.max_width;
+ np->maxHeight = hints.max_height;
+ if(np->maxWidth <= 0) {
+ np->maxWidth = rootWidth;
+ }
+ if(np->maxHeight <= 0) {
+ np->maxHeight = rootHeight;
+ }
+ } else {
+ np->maxWidth = rootWidth;
+ np->maxHeight = rootHeight;
+ }
+
+ if(np->sizeFlags & PBaseSize) {
+ np->baseWidth = hints.base_width;
+ np->baseHeight = hints.base_height;
+ } else if(np->sizeFlags & PMinSize) {
+ np->baseWidth = np->minWidth;
+ np->baseHeight = np->minHeight;
+ } else {
+ np->baseWidth = 0;
+ np->baseHeight = 0;
+ }
+
+ if(np->sizeFlags & PAspect) {
+ np->aspect.minx = hints.min_aspect.x;
+ np->aspect.miny = hints.min_aspect.y;
+ np->aspect.maxx = hints.max_aspect.x;
+ np->aspect.maxy = hints.max_aspect.y;
+ if(np->aspect.minx < 1) {
+ np->aspect.minx = 1;
+ }
+ if(np->aspect.miny < 1) {
+ np->aspect.miny = 1;
+ }
+ if(np->aspect.maxx < 1) {
+ np->aspect.maxx = 1;
+ }
+ if(np->aspect.maxy < 1) {
+ np->aspect.maxy = 1;
+ }
+ }
+
+ if(np->sizeFlags & PWinGravity) {
+ np->gravity = hints.win_gravity;
+ } else {
+ np->gravity = 1;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadWMColormaps(ClientNode *np) {
+
+ Window *windows;
+ ColormapNode *cp;
+ int count;
+ int x;
+
+ Assert(np);
+
+ if(JXGetWMColormapWindows(display, np->window, &windows, &count)) {
+ if(count > 0) {
+
+ /* Free old colormaps. */
+ while(np->colormaps) {
+ cp = np->colormaps->next;
+ Release(np->colormaps);
+ np->colormaps = cp;
+ }
+
+ /* Put the maps in the list in order so they will come out in
+ * reverse order. This way they will be installed with the
+ * most important last.
+ * Keep track of at most colormapCount colormaps for each
+ * window to avoid doing extra work. */
+ count = Min(colormapCount, count);
+ for(x = 0; x < count; x++) {
+ cp = Allocate(sizeof(ColormapNode));
+ cp->window = windows[x];
+ cp->next = np->colormaps;
+ np->colormaps = cp;
+ }
+
+ JXFree(windows);
+
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadWMHints(Window win, ClientState *state) {
+
+ XWMHints *wmhints;
+
+ Assert(win != None);
+ Assert(state);
+
+ wmhints = JXGetWMHints(display, win);
+ if(wmhints) {
+ switch(wmhints->flags & StateHint) {
+ case IconicState:
+ state->status |= STAT_MINIMIZED;
+ break;
+ default:
+ if(!(state->status & STAT_MINIMIZED)) {
+ state->status |= STAT_MAPPED;
+ }
+ break;
+ }
+ JXFree(wmhints);
+ } else {
+ state->status |= STAT_MAPPED;
+ }
+
+}
+
+/****************************************************************************
+ * Read _MOTIF_WM_HINTS
+ ****************************************************************************/
+void ReadMotifHints(Window win, ClientState *state) {
+
+ PropMwmHints *mhints;
+ Atom type;
+ unsigned long itemCount, bytesLeft;
+ unsigned char *data;
+ int format;
+
+ Assert(win != None);
+ Assert(state);
+
+ if(JXGetWindowProperty(display, win, atoms[ATOM_MOTIF_WM_HINTS],
+ 0L, 20L, False, atoms[ATOM_MOTIF_WM_HINTS], &type, &format,
+ &itemCount, &bytesLeft, &data) != Success) {
+ return;
+ }
+
+ mhints = (PropMwmHints*)data;
+ if(mhints) {
+
+ if((mhints->flags & MWM_HINTS_FUNCTIONS)
+ && !(mhints->functions & MWM_FUNC_ALL)) {
+
+ if(!(mhints->functions & MWM_FUNC_RESIZE)) {
+ state->border &= ~BORDER_RESIZE;
+ }
+ if(!(mhints->functions & MWM_FUNC_MOVE)) {
+ state->border &= ~BORDER_MOVE;
+ }
+ if(!(mhints->functions & MWM_FUNC_MINIMIZE)) {
+ state->border &= ~BORDER_MIN;
+ }
+ if(!(mhints->functions & MWM_FUNC_MAXIMIZE)) {
+ state->border &= ~BORDER_MAX;
+ }
+ if(!(mhints->functions & MWM_FUNC_CLOSE)) {
+ state->border &= ~BORDER_CLOSE;
+ }
+ }
+
+ if((mhints->flags & MWM_HINTS_DECORATIONS)
+ && !(mhints->decorations & MWM_DECOR_ALL)) {
+
+ if(!(mhints->decorations & MWM_DECOR_BORDER)) {
+ state->border &= ~BORDER_OUTLINE;
+ }
+ if(!(mhints->decorations & MWM_DECOR_TITLE)) {
+ state->border &= ~BORDER_TITLE;
+ }
+ if(!(mhints->decorations & MWM_DECOR_MINIMIZE)) {
+ state->border &= ~BORDER_MIN;
+ }
+ if(!(mhints->decorations & MWM_DECOR_MAXIMIZE)) {
+ state->border &= ~BORDER_MAX;
+ }
+ }
+
+ JXFree(mhints);
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GetCardinalAtom(Window window, AtomType atom, unsigned long *value) {
+
+ unsigned long count;
+ int status;
+ unsigned long extra;
+ Atom realType;
+ int realFormat;
+ unsigned char *data;
+ int ret;
+
+ Assert(window != None);
+ Assert(value);
+
+ status = JXGetWindowProperty(display, window, atoms[atom], 0, 1, False,
+ XA_CARDINAL, &realType, &realFormat, &count, &extra, &data);
+
+ ret = 0;
+ if(status == Success && data) {
+ if(count == 1) {
+ *value = *(unsigned long*)data;
+ ret = 1;
+ }
+ JXFree(data);
+ }
+
+ return ret;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GetWindowAtom(Window window, AtomType atom, Window *value) {
+ unsigned long count;
+ int status;
+ unsigned long extra;
+ Atom realType;
+ int realFormat;
+ unsigned char *data;
+ int ret;
+
+ Assert(window != None);
+ Assert(value);
+
+ status = JXGetWindowProperty(display, window, atoms[atom], 0, 1, False,
+ XA_WINDOW, &realType, &realFormat, &count, &extra, &data);
+
+ ret = 0;
+ if(status == Success && data) {
+ if(count == 1) {
+ *value = *(Window*)data;
+ ret = 1;
+ }
+ JXFree(data);
+ }
+
+ return ret;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetCardinalAtom(Window window, AtomType atom, unsigned long value) {
+
+ Assert(window != None);
+
+ JXChangeProperty(display, window, atoms[atom], XA_CARDINAL, 32,
+ PropModeReplace, (unsigned char*)&value, 1);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetWindowAtom(Window window, AtomType atom, unsigned long value) {
+
+ Assert(window != None);
+
+ JXChangeProperty(display, window, atoms[atom], XA_WINDOW, 32,
+ PropModeReplace, (unsigned char*)&value, 1);
+
+}
+
+
diff --git a/src/hint.h b/src/hint.h
new file mode 100644
index 0000000..7eae815
--- /dev/null
+++ b/src/hint.h
@@ -0,0 +1,173 @@
+/**
+ * @file hint.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for reading and writing X properties.
+ *
+ */
+
+#ifndef HINT_H
+#define HINT_H
+
+struct ClientNode;
+
+typedef enum {
+
+ /* Misc */
+ ATOM_COMPOUND_TEXT,
+ ATOM_UTF8_STRING,
+
+ /* Standard atoms */
+ ATOM_WM_STATE,
+ ATOM_WM_PROTOCOLS,
+ ATOM_WM_DELETE_WINDOW,
+ ATOM_WM_TAKE_FOCUS,
+ ATOM_WM_LOCALE_NAME,
+ ATOM_WM_CHANGE_STATE,
+ ATOM_WM_COLORMAP_WINDOWS,
+
+ /* WM Spec atoms */
+ ATOM_NET_SUPPORTED,
+ ATOM_NET_NUMBER_OF_DESKTOPS,
+ ATOM_NET_DESKTOP_NAMES,
+ ATOM_NET_DESKTOP_GEOMETRY,
+ ATOM_NET_DESKTOP_VIEWPORT,
+ ATOM_NET_CURRENT_DESKTOP,
+ ATOM_NET_ACTIVE_WINDOW,
+ ATOM_NET_WORKAREA,
+ ATOM_NET_SUPPORTING_WM_CHECK,
+ ATOM_NET_FRAME_EXTENTS,
+ ATOM_NET_WM_DESKTOP,
+
+ ATOM_NET_WM_STATE,
+ ATOM_NET_WM_STATE_STICKY,
+ ATOM_NET_WM_STATE_MAXIMIZED_VERT,
+ ATOM_NET_WM_STATE_MAXIMIZED_HORZ,
+ ATOM_NET_WM_STATE_SHADED,
+ ATOM_NET_WM_STATE_FULLSCREEN,
+
+ ATOM_NET_WM_ALLOWED_ACTIONS,
+ ATOM_NET_WM_ACTION_MOVE,
+ ATOM_NET_WM_ACTION_RESIZE,
+ ATOM_NET_WM_ACTION_MINIMIZE,
+ ATOM_NET_WM_ACTION_SHADE,
+ ATOM_NET_WM_ACTION_STICK,
+ ATOM_NET_WM_ACTION_MAXIMIZE_HORZ,
+ ATOM_NET_WM_ACTION_MAXIMIZE_VERT,
+ ATOM_NET_WM_ACTION_CHANGE_DESKTOP,
+ ATOM_NET_WM_ACTION_CLOSE,
+
+ ATOM_NET_CLOSE_WINDOW,
+ ATOM_NET_MOVERESIZE_WINDOW,
+
+ ATOM_NET_WM_NAME,
+ ATOM_NET_WM_ICON,
+ ATOM_NET_WM_WINDOW_TYPE,
+ ATOM_NET_WM_WINDOW_TYPE_DESKTOP,
+ ATOM_NET_WM_WINDOW_TYPE_DOCK,
+
+ ATOM_NET_CLIENT_LIST,
+ ATOM_NET_CLIENT_LIST_STACKING,
+
+ ATOM_NET_WM_STRUT_PARTIAL,
+ ATOM_NET_WM_STRUT,
+
+ ATOM_NET_SYSTEM_TRAY_OPCODE,
+
+ /* GNOME atoms */
+ ATOM_WIN_LAYER,
+ ATOM_WIN_STATE,
+ ATOM_WIN_WORKSPACE_COUNT,
+ ATOM_WIN_WORKSPACE,
+ ATOM_WIN_SUPPORTING_WM_CHECK,
+ ATOM_WIN_PROTOCOLS,
+
+ /* MWM atoms */
+ ATOM_MOTIF_WM_HINTS,
+
+ /* JWM-specific atoms. */
+ ATOM_JWM_RESTART,
+ ATOM_JWM_EXIT,
+
+ ATOM_COUNT
+} AtomType;
+
+#define FIRST_NET_ATOM ATOM_NET_SUPPORTED
+#define LAST_NET_ATOM ATOM_NET_SYSTEM_TRAY_OPCODE
+
+#define FIRST_WIN_ATOM ATOM_WIN_LAYER
+#define LAST_WIN_ATOM ATOM_WIN_PROTOCOLS
+
+#define FIRST_MWM_ATOM ATOM_MOTIF_WM_HINTS
+#define LAST_MWM_ATOM ATOM_MOTIF_WM_HINTS
+
+#define WIN_STATE_STICKY (1UL << 0)
+#define WIN_STATE_MINIMIZED (1UL << 1)
+#define WIN_STATE_MAXIMIZED_VERT (1UL << 2)
+#define WIN_STATE_MAXIMIZED_HORIZ (1UL << 3)
+#define WIN_STATE_HIDDEN (1UL << 4)
+#define WIN_STATE_SHADED (1UL << 5)
+#define WIN_STATE_HIDE_WORKSPACE (1UL << 6)
+#define WIN_STATE_HIDE_TRANSIENT (1UL << 7)
+#define WIN_STATE_FIXED_POSITION (1UL << 8)
+#define WIN_STATE_ARRANGE_IGNORE (1UL << 9)
+
+#define WIN_HINT_SKIP_FOCUS (1UL << 0)
+#define WIN_HINT_SKIP_WINLIST (1UL << 1)
+#define WIN_HINT_SKIP_TASKBAR (1UL << 2)
+#define WIN_HINT_GROUP_TRANSIENT (1UL << 3)
+#define WIN_HINT_FOCUS_ON_CLICK (1UL << 4)
+
+typedef enum {
+ LAYER_BOTTOM = 0,
+ LAYER_NORMAL = 4,
+ DEFAULT_TRAY_LAYER = 8,
+ LAYER_TOP = 12,
+ LAYER_COUNT = 13
+} WinLayerType;
+
+typedef struct ClientState {
+ unsigned int status;
+ unsigned int border;
+ unsigned int layer;
+ unsigned int desktop;
+} ClientState;
+
+typedef enum {
+ PROT_NONE = 0,
+ PROT_DELETE = 1,
+ PROT_TAKE_FOCUS = 2
+} ClientProtocolType;
+
+extern Atom atoms[ATOM_COUNT];
+
+void InitializeHints();
+void StartupHints();
+void ShutdownHints();
+void DestroyHints();
+
+void ReadCurrentDesktop();
+
+void ReadClientProtocols(struct ClientNode *np);
+
+void ReadWMName(struct ClientNode *np);
+void ReadWMClass(struct ClientNode *np);
+void ReadWMNormalHints(struct ClientNode *np);
+ClientProtocolType ReadWMProtocols(Window w);
+void ReadWMColormaps(struct ClientNode *np);
+
+void ReadWinLayer(struct ClientNode *np);
+
+ClientState ReadWindowState(Window win);
+
+void WriteState(struct ClientNode *np);
+
+int GetCardinalAtom(Window window, AtomType atom, unsigned long *value);
+int GetWindowAtom(Window window, AtomType atom, Window *value);
+
+void SetCardinalAtom(Window window, AtomType atom, unsigned long value);
+void SetWindowAtom(Window window, AtomType atom, unsigned long value);
+
+#endif
+
diff --git a/src/icon.c b/src/icon.c
new file mode 100644
index 0000000..b6f5ca6
--- /dev/null
+++ b/src/icon.c
@@ -0,0 +1,726 @@
+/****************************************************************************
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "icon.h"
+#include "client.h"
+#include "render.h"
+#include "main.h"
+#include "image.h"
+#include "misc.h"
+#include "hint.h"
+#include "color.h"
+
+static int iconSize = 0;
+
+#ifdef USE_ICONS
+
+#include "x.xpm"
+
+#define HASH_SIZE 64
+
+typedef struct IconPathNode {
+ char *path;
+ struct IconPathNode *next;
+} IconPathNode;
+
+static IconNode **iconHash;
+
+static IconPathNode *iconPaths;
+static IconPathNode *iconPathsTail;
+
+static GC iconGC;
+
+static void SetIconSize();
+
+static void DoDestroyIcon(int index, IconNode *icon);
+static void ReadNetWMIcon(ClientNode *np);
+static IconNode *GetDefaultIcon();
+static IconNode *CreateIconFromData(const char *name, char **data);
+static IconNode *CreateIconFromFile(const char *fileName);
+static IconNode *CreateIconFromBinary(const unsigned long *data,
+ unsigned int length);
+static IconNode *LoadNamedIconHelper(const char *name, const char *path);
+
+static IconNode *LoadSuffixedIcon(const char *path, const char *name,
+ const char *suffix);
+
+static ScaledIconNode *GetScaledIcon(IconNode *icon, int width, int height);
+
+static void InsertIcon(IconNode *icon);
+static IconNode *FindIcon(const char *name);
+static int GetHash(const char *str);
+
+/****************************************************************************
+ * Must be initialized before parsing the configuration.
+ ****************************************************************************/
+void InitializeIcons() {
+
+ int x;
+
+ iconPaths = NULL;
+ iconPathsTail = NULL;
+
+ iconHash = Allocate(sizeof(IconNode*) * HASH_SIZE);
+ for(x = 0; x < HASH_SIZE; x++) {
+ iconHash[x] = NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupIcons() {
+
+ XGCValues gcValues;
+ unsigned long gcMask;
+
+ gcMask = GCGraphicsExposures;
+ gcValues.graphics_exposures = False;
+ iconGC = JXCreateGC(display, rootWindow, gcMask, &gcValues);
+
+ QueryRenderExtension();
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownIcons() {
+
+ int x;
+
+ for(x = 0; x < HASH_SIZE; x++) {
+ while(iconHash[x]) {
+ DoDestroyIcon(x, iconHash[x]);
+ }
+ }
+
+ JXFreeGC(display, iconGC);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyIcons() {
+
+ IconPathNode *pn;
+
+ while(iconPaths) {
+ pn = iconPaths->next;
+ Release(iconPaths->path);
+ Release(iconPaths);
+ iconPaths = pn;
+ }
+ iconPathsTail = NULL;
+
+ if(iconHash) {
+ Release(iconHash);
+ iconHash = NULL;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetIconSize() {
+
+ XIconSize size;
+
+ if(!iconSize) {
+
+ /* FIXME: compute values based on the sizes we can actually use. */
+ iconSize = 32;
+
+ size.min_width = iconSize;
+ size.min_height = iconSize;
+ size.max_width = iconSize;
+ size.max_height = iconSize;
+ size.width_inc = iconSize;
+ size.height_inc = iconSize;
+
+ JXSetIconSizes(display, rootWindow, &size, 1);
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddIconPath(char *path) {
+
+ IconPathNode *ip;
+ int length;
+ int addSep;
+
+ if(!path) {
+ return;
+ }
+
+ Trim(path);
+
+ length = strlen(path);
+ if(path[length - 1] != '/') {
+ addSep = 1;
+ } else {
+ addSep = 0;
+ }
+
+ ip = Allocate(sizeof(IconPathNode));
+ ip->path = Allocate(length + addSep + 1);
+ strcpy(ip->path, path);
+ if(addSep) {
+ ip->path[length] = '/';
+ ip->path[length + 1] = 0;
+ }
+ ExpandPath(&ip->path);
+ ip->next = NULL;
+
+ if(iconPathsTail) {
+ iconPathsTail->next = ip;
+ } else {
+ iconPaths = ip;
+ }
+ iconPathsTail = ip;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void PutIcon(IconNode *icon, Drawable d, int x, int y,
+ int width, int height) {
+
+ ScaledIconNode *node;
+
+ Assert(icon);
+
+ node = GetScaledIcon(icon, width, height);
+
+ if(node) {
+
+ if(PutScaledRenderIcon(icon, node, d, x, y)) {
+ return;
+ }
+
+ if(node->image != None) {
+
+ if(node->mask != None) {
+ JXSetClipOrigin(display, iconGC, x, y);
+ JXSetClipMask(display, iconGC, node->mask);
+ }
+
+ JXCopyArea(display, node->image, d, iconGC, 0, 0,
+ node->width, node->height, x, y);
+
+ if(node->mask != None) {
+ JXSetClipMask(display, iconGC, None);
+ JXSetClipOrigin(display, iconGC, 0, 0);
+ }
+
+ }
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void LoadIcon(ClientNode *np) {
+
+ IconPathNode *ip;
+
+ Assert(np);
+
+ SetIconSize();
+
+ DestroyIcon(np->icon);
+ np->icon = NULL;
+
+ /* Attempt to read _NET_WM_ICON for an icon */
+ ReadNetWMIcon(np);
+ if(np->icon) {
+ return;
+ }
+
+ /* Attempt to find an icon for this program in the icon directory */
+ if(np->instanceName) {
+ for(ip = iconPaths; ip; ip = ip->next) {
+
+#ifdef USE_PNG
+ np->icon = LoadSuffixedIcon(ip->path, np->instanceName, ".png");
+ if(np->icon) {
+ return;
+ }
+#endif
+
+#ifdef USE_XPM
+ np->icon = LoadSuffixedIcon(ip->path, np->instanceName, ".xpm");
+ if(np->icon) {
+ return;
+ }
+#endif
+
+ }
+ }
+
+ /* Load the default icon */
+ np->icon = GetDefaultIcon();
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *LoadSuffixedIcon(const char *path, const char *name,
+ const char *suffix) {
+
+ IconNode *result;
+ ImageNode *image;
+ char *iconName;
+
+ Assert(path);
+ Assert(name);
+ Assert(suffix);
+
+ iconName = Allocate(strlen(name)
+ + strlen(path) + strlen(suffix) + 1);
+ strcpy(iconName, path);
+ strcat(iconName, name);
+ strcat(iconName, suffix);
+
+ result = FindIcon(iconName);
+ if(result) {
+ Release(iconName);
+ return result;
+ }
+
+ image = LoadImage(iconName);
+ if(image) {
+ result = CreateIcon();
+ result->name = iconName;
+ result->image = image;
+ InsertIcon(result);
+ return result;
+ } else {
+ Release(iconName);
+ return NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *LoadNamedIcon(const char *name) {
+
+ IconPathNode *ip;
+ IconNode *icon;
+
+ Assert(name);
+
+ SetIconSize();
+
+ if(name[0] == '/') {
+ return CreateIconFromFile(name);
+ } else {
+ for(ip = iconPaths; ip; ip = ip->next) {
+ icon = LoadNamedIconHelper(name, ip->path);
+ if(icon) {
+ return icon;
+ }
+ }
+ return NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *LoadNamedIconHelper(const char *name, const char *path) {
+
+ IconNode *result;
+ char *temp;
+
+ temp = AllocateStack(strlen(name) + strlen(path) + 1);
+ strcpy(temp, path);
+ strcat(temp, name);
+ result = CreateIconFromFile(temp);
+ ReleaseStack(temp);
+
+ return result;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadNetWMIcon(ClientNode *np) {
+
+ unsigned long count;
+ int status;
+ unsigned long extra;
+ Atom realType;
+ int realFormat;
+ unsigned char *data;
+
+ status = JXGetWindowProperty(display, np->window, atoms[ATOM_NET_WM_ICON],
+ 0, 256 * 256 * 4, False, XA_CARDINAL, &realType, &realFormat, &count,
+ &extra, &data);
+
+ if(status == Success && data) {
+ np->icon = CreateIconFromBinary((unsigned long*)data, count);
+ JXFree(data);
+ }
+
+}
+
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *GetDefaultIcon() {
+ return CreateIconFromData("default", x_xpm);
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *CreateIconFromData(const char *name, char **data) {
+
+ ImageNode *image;
+ IconNode *result;
+
+ Assert(name);
+ Assert(data);
+
+ /* Check if this icon has already been loaded */
+ result = FindIcon(name);
+ if(result) {
+ return result;
+ }
+
+ image = LoadImageFromData(data);
+ if(image) {
+ result = CreateIcon();
+ result->name = CopyString(name);
+ result->image = image;
+ InsertIcon(result);
+ return result;
+ } else {
+ return NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *CreateIconFromFile(const char *fileName) {
+
+ ImageNode *image;
+ IconNode *result;
+
+ if(!fileName) {
+ return NULL;
+ }
+
+ /* Check if this icon has already been loaded */
+ result = FindIcon(fileName);
+ if(result) {
+ return result;
+ }
+
+ image = LoadImage(fileName);
+ if(image) {
+ result = CreateIcon();
+ result->name = CopyString(fileName);
+ result->image = image;
+ InsertIcon(result);
+ return result;
+ } else {
+ return NULL;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+ScaledIconNode *GetScaledIcon(IconNode *icon, int rwidth, int rheight) {
+
+ XColor color;
+ ScaledIconNode *np;
+ GC maskGC;
+ int x, y;
+ int index;
+ int alpha;
+ double scalex, scaley;
+ double srcx, srcy;
+ double ratio;
+ int nwidth, nheight;
+
+ Assert(icon);
+ Assert(icon->image);
+
+ if(rwidth == 0) {
+ rwidth = icon->image->width;
+ }
+ if(rheight == 0) {
+ rheight = icon->image->height;
+ }
+
+ ratio = (double)icon->image->width / icon->image->height;
+ nwidth = Min(rwidth, rheight * ratio);
+ nheight = Min(rheight, nwidth / ratio);
+ nwidth = nheight * ratio;
+ if(nwidth < 1) {
+ nwidth = 1;
+ }
+ if(nheight < 1) {
+ nheight = 1;
+ }
+
+ /* Check if this size already exists. */
+ for(np = icon->nodes; np; np = np->next) {
+ if(np->width == nwidth && np->height == nheight) {
+ return np;
+ }
+ }
+
+ /* See if we can use XRender to create the icon. */
+ np = CreateScaledRenderIcon(icon, nwidth, nheight);
+ if(np) {
+ return np;
+ }
+
+ /* Create a new ScaledIconNode the old-fashioned way. */
+ np = Allocate(sizeof(ScaledIconNode));
+ np->width = nwidth;
+ np->height = nheight;
+ np->next = icon->nodes;
+#ifdef USE_XRENDER
+ np->imagePicture = None;
+ np->maskPicture = None;
+#endif
+ icon->nodes = np;
+
+ np->mask = JXCreatePixmap(display, rootWindow, nwidth, nheight, 1);
+ maskGC = JXCreateGC(display, np->mask, 0, NULL);
+ np->image = JXCreatePixmap(display, rootWindow, nwidth, nheight, rootDepth);
+
+ scalex = (double)icon->image->width / nwidth;
+ scaley = (double)icon->image->height / nheight;
+
+ srcy = 0.0;
+ for(y = 0; y < nheight; y++) {
+ srcx = 0.0;
+ for(x = 0; x < nwidth; x++) {
+
+ index = (int)srcy * icon->image->width + (int)srcx;
+
+ alpha = (icon->image->data[index] >> 24) & 0xFFUL;
+ if(alpha >= 128) {
+
+ color.red = ((icon->image->data[index] >> 16) & 0xFFUL) * 257;
+ color.green = ((icon->image->data[index] >> 8) & 0xFFUL) * 257;
+ color.blue = (icon->image->data[index] & 0xFFUL) * 257;
+ GetColor(&color);
+
+ JXSetForeground(display, rootGC, color.pixel);
+ JXDrawPoint(display, np->image, rootGC, x, y);
+
+ JXSetForeground(display, maskGC, 1);
+
+ } else {
+ JXSetForeground(display, maskGC, 0);
+ }
+ JXDrawPoint(display, np->mask, maskGC, x, y);
+
+ srcx += scalex;
+
+ }
+
+ srcy += scaley;
+ }
+
+ JXFreeGC(display, maskGC);
+
+ return np;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *CreateIconFromBinary(const unsigned long *input,
+ unsigned int length) {
+
+ unsigned long height, width;
+ IconNode *result;
+ unsigned long *data;
+ unsigned int x;
+
+ if(!input) {
+ return NULL;
+ }
+
+ width = input[0];
+ height = input[1];
+
+ if(width * height + 2 > length) {
+ Debug("invalid image size: %d x %d + 2 > %d", width, height, length);
+ return NULL;
+ } else if(width == 0 || height == 0) {
+ Debug("invalid image size: %d x %d", width, height);
+ return NULL;
+ }
+
+ result = CreateIcon();
+
+ result->image = Allocate(sizeof(ImageNode));
+ result->image->width = width;
+ result->image->height = height;
+
+ result->image->data = Allocate(width * height * sizeof(unsigned long));
+ data = (unsigned long*)result->image->data;
+
+ /* Note: the data types here might be of different sizes. */
+ for(x = 0; x < width * height; x++) {
+ data[x] = input[x + 2];
+ }
+
+ /* Don't insert this icon since it is transient. */
+
+ return result;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *CreateIcon() {
+
+ IconNode *icon;
+
+ icon = Allocate(sizeof(IconNode));
+ icon->name = NULL;
+ icon->image = NULL;
+ icon->nodes = NULL;
+ icon->next = NULL;
+ icon->prev = NULL;
+
+ return icon;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DoDestroyIcon(int index, IconNode *icon) {
+
+ ScaledIconNode *np;
+
+ if(icon) {
+ while(icon->nodes) {
+ np = icon->nodes->next;
+
+#ifdef USE_XRENDER
+ if(icon->nodes->imagePicture != None) {
+ JXRenderFreePicture(display, icon->nodes->imagePicture);
+ }
+ if(icon->nodes->maskPicture != None) {
+ JXRenderFreePicture(display, icon->nodes->maskPicture);
+ }
+#endif
+
+ if(icon->nodes->image != None) {
+ JXFreePixmap(display, icon->nodes->image);
+ }
+ if(icon->nodes->mask != None) {
+ JXFreePixmap(display, icon->nodes->mask);
+ }
+
+ Release(icon->nodes);
+ icon->nodes = np;
+ }
+
+ if(icon->name) {
+ Release(icon->name);
+ }
+ DestroyImage(icon->image);
+
+ if(icon->prev) {
+ icon->prev->next = icon->next;
+ } else {
+ iconHash[index] = icon->next;
+ }
+ if(icon->next) {
+ icon->next->prev = icon->prev;
+ }
+ Release(icon);
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyIcon(IconNode *icon) {
+
+ int index;
+
+ if(icon && !icon->name) {
+ index = GetHash(icon->name);
+ DoDestroyIcon(index, icon);
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void InsertIcon(IconNode *icon) {
+
+ int index;
+
+ Assert(icon);
+ Assert(icon->name);
+
+ index = GetHash(icon->name);
+
+ icon->prev = NULL;
+ if(iconHash[index]) {
+ iconHash[index]->prev = icon;
+ }
+ icon->next = iconHash[index];
+ iconHash[index] = icon;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+IconNode *FindIcon(const char *name) {
+
+ IconNode *icon;
+ int index;
+
+ index = GetHash(name);
+
+ icon = iconHash[index];
+ while(icon) {
+ if(!strcmp(icon->name, name)) {
+ return icon;
+ }
+ icon = icon->next;
+ }
+
+ return NULL;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GetHash(const char *str) {
+
+ int x;
+ int hash = 0;
+
+ if(!str || !str[0]) {
+ return hash % HASH_SIZE;
+ }
+
+ for(x = 0; str[x]; x++) {
+ hash = (hash + str[x]) % HASH_SIZE;
+ }
+
+ return hash;
+
+}
+
+#endif /* USE_ICONS */
+
diff --git a/src/icon.h b/src/icon.h
new file mode 100644
index 0000000..ad9645a
--- /dev/null
+++ b/src/icon.h
@@ -0,0 +1,110 @@
+/**
+ * @file icon.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header file for icon functions.
+ *
+ */
+
+#ifndef ICON_H
+#define ICON_H
+
+struct ClientNode;
+
+/** Structure to hold a scaled icon. */
+typedef struct ScaledIconNode {
+
+ int width; /**< The scaled width of the icon. */
+ int height; /**< The scaled height of the icon. */
+
+ Pixmap image;
+ Pixmap mask;
+#ifdef USE_XRENDER
+ Picture imagePicture;
+ Picture maskPicture;
+#endif
+
+ struct ScaledIconNode *next;
+
+} ScaledIconNode;
+
+/** Structure to hold an icon. */
+typedef struct IconNode {
+
+ char *name; /**< The name of the icon. */
+ struct ImageNode *image; /**< The image data. */
+ struct ScaledIconNode *nodes; /**< Scaled versions of the icon. */
+
+ struct IconNode *next; /**< The next icon in the list. */
+ struct IconNode *prev; /**< The previous icon in the list. */
+
+} IconNode;
+
+#ifdef USE_ICONS
+
+/*@{*/
+void InitializeIcons();
+void StartupIcons();
+void ShutdownIcons();
+void DestroyIcons();
+/*@}*/
+
+/** Add an icon path.
+ * This adds a path to the list of icon search paths.
+ * @param path The icon path to add.
+ */
+void AddIconPath(char *path);
+
+/** Render an icon.
+ * This will scale an icon if necessary to fit the requested size. The
+ * aspect ratio of the icon is preserved.
+ * @param icon The icon to render.
+ * @param d The drawable on which to place the icon.
+ * @param x The x offset on the drawable to render the icon.
+ * @param y The y offset on the drawable to render the icon.
+ * @param width The width of the icon to display.
+ * @param height The height of the icon to display.
+ */
+void PutIcon(IconNode *icon, Drawable d, int x, int y,
+ int width, int height);
+
+/** Load an icon for a client.
+ * @param np The client.
+ */
+void LoadIcon(struct ClientNode *np);
+
+/** Load an icon.
+ * @param name The name of the icon to load.
+ * @return A pointer to the icon (NULL if not found).
+ */
+IconNode *LoadNamedIcon(const char *name);
+
+/** Destroy an icon.
+ * @param icon The icon to destroy.
+ */
+void DestroyIcon(IconNode *icon);
+
+/** Create and initialize a new icon structure.
+ * @return The new icon structure.
+ */
+IconNode *CreateIcon();
+
+#else
+
+#define ICON_DUMMY_FUNCTION 0
+
+#define InitializeIcons() ICON_DUMMY_FUNCTION
+#define StartupIcons() ICON_DUMMY_FUNCTION
+#define ShutdownIcons() ICON_DUMMY_FUNCTION
+#define DestroyIcons() ICON_DUMMY_FUNCTION
+#define AddIconPath( a ) ICON_DUMMY_FUNCTION
+#define PutIcon( a, b, c, d, e, f ) ICON_DUMMY_FUNCTION
+#define LoadIcon( a ) ICON_DUMMY_FUNCTION
+#define LoadNamedIcon( a ) ICON_DUMMY_FUNCTION
+#define DestroyIcon( a ) ICON_DUMMY_FUNCTION
+
+#endif /* USE_ICONS */
+
+#endif /* ICON_H */
+
diff --git a/src/image.c b/src/image.c
new file mode 100644
index 0000000..af52ae7
--- /dev/null
+++ b/src/image.c
@@ -0,0 +1,362 @@
+/****************************************************************************
+ * Functions to load images.
+ * Copyright (C) 2005 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "image.h"
+#include "main.h"
+#include "error.h"
+#include "color.h"
+
+static ImageNode *LoadPNGImage(const char *fileName);
+static ImageNode *LoadXPMImage(const char *fileName);
+static ImageNode *CreateImageFromXImages(XImage *image, XImage *shape);
+
+#ifdef USE_XPM
+static int AllocateColor(Display *d, Colormap cmap, char *name,
+ XColor *c, void *closure);
+static int FreeColors(Display *d, Colormap cmap, Pixel *pixels, int n,
+ void *closure);
+#endif
+
+/****************************************************************************
+ ****************************************************************************/
+ImageNode *LoadImage(const char *fileName) {
+
+ ImageNode *result;
+
+ if(!fileName) {
+ return NULL;
+ }
+
+ /* Attempt to load the file as a PNG image. */
+ result = LoadPNGImage(fileName);
+ if(result) {
+ return result;
+ }
+
+ /* Attempt to load the file as an XPM image. */
+ result = LoadXPMImage(fileName);
+ if(result) {
+ return result;
+ }
+
+ return NULL;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+ImageNode *LoadImageFromData(char **data) {
+
+ ImageNode *result = NULL;
+
+#ifdef USE_XPM
+
+ XpmAttributes attr;
+ XImage *image;
+ XImage *shape;
+ int rc;
+
+ Assert(data);
+
+ attr.valuemask = XpmAllocColor | XpmFreeColors | XpmColorClosure;
+ attr.alloc_color = AllocateColor;
+ attr.free_colors = FreeColors;
+ attr.color_closure = NULL;
+ rc = XpmCreateImageFromData(display, data, &image, &shape, &attr);
+ if(rc == XpmSuccess) {
+ result = CreateImageFromXImages(image, shape);
+ JXDestroyImage(image);
+ if(shape) {
+ JXDestroyImage(shape);
+ }
+ }
+
+#endif
+
+ return result;
+
+}
+
+/****************************************************************************
+ * Load a PNG image from the given file name. Returns NULL on error.
+ * Since libpng uses longjmp, this function is not reentrant to simplify
+ * the issues surrounding longjmp and local variables.
+ ****************************************************************************/
+ImageNode *LoadPNGImage(const char *fileName) {
+
+#ifdef USE_PNG
+
+ static ImageNode *result;
+ static FILE *fd;
+ static unsigned char **rows;
+ static png_structp pngData;
+ static png_infop pngInfo;
+ static png_infop pngEndInfo;
+ static unsigned char *data;
+
+ unsigned char header[8];
+ unsigned long rowBytes;
+ int bitDepth, colorType;
+ unsigned int x, y;
+ unsigned long temp;
+
+ Assert(fileName);
+
+ result = NULL;
+ fd = NULL;
+ rows = NULL;
+ pngData = NULL;
+ pngInfo = NULL;
+ pngEndInfo = NULL;
+ data = NULL;
+
+ fd = fopen(fileName, "rb");
+ if(!fd) {
+ return NULL;
+ }
+
+ x = fread(header, 1, sizeof(header), fd);
+ if(x != sizeof(header) || png_sig_cmp(header, 0, sizeof(header))) {
+ fclose(fd);
+ return NULL;
+ }
+
+ pngData = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if(!pngData) {
+ fclose(fd);
+ Warning("could not create read struct for PNG image: %s", fileName);
+ return NULL;
+ }
+
+ if(setjmp(png_jmpbuf(pngData))) {
+ png_destroy_read_struct(&pngData, &pngInfo, &pngEndInfo);
+ if(fd) {
+ fclose(fd);
+ }
+ if(rows) {
+ Release(rows);
+ }
+ if(result) {
+ if(result->data) {
+ Release(result->data);
+ }
+ Release(result);
+ }
+ Warning("error reading PNG image: %s", fileName);
+ }
+
+ pngInfo = png_create_info_struct(pngData);
+ if(!pngInfo) {
+ png_destroy_read_struct(&pngData, NULL, NULL);
+ fclose(fd);
+ Warning("could not create info struct for PNG image: %s", fileName);
+ return NULL;
+ }
+
+ pngEndInfo = png_create_info_struct(pngData);
+ if(!pngEndInfo) {
+ png_destroy_read_struct(&pngData, &pngInfo, NULL);
+ fclose(fd);
+ Warning("could not create end info struct for PNG image: %s", fileName);
+ return NULL;
+ }
+
+ png_init_io(pngData, fd);
+ png_set_sig_bytes(pngData, sizeof(header));
+
+ png_read_info(pngData, pngInfo);
+
+ result = Allocate(sizeof(ImageNode));
+
+ png_get_IHDR(pngData, pngInfo, &result->width, &result->height,
+ &bitDepth, &colorType, NULL, NULL, NULL);
+
+ png_set_expand(pngData);
+
+ if(bitDepth == 16) {
+ png_set_strip_16(pngData);
+ } else if(bitDepth < 8) {
+ png_set_packing(pngData);
+ }
+
+ png_set_swap_alpha(pngData);
+ png_set_filler(pngData, 0xFF, PNG_FILLER_BEFORE);
+
+ if(colorType == PNG_COLOR_TYPE_GRAY
+ || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(pngData);
+ }
+
+ png_read_update_info(pngData, pngInfo);
+
+ rowBytes = png_get_rowbytes(pngData, pngInfo);
+ data = Allocate(rowBytes * result->height);
+
+ rows = AllocateStack(result->height * sizeof(result->data));
+
+ y = 0;
+ for(x = 0; x < result->height; x++) {
+ rows[x] = &data[y];
+ y += result->width * 4;
+ }
+
+ png_read_image(pngData, rows);
+
+ png_read_end(pngData, pngInfo);
+ png_destroy_read_struct(&pngData, &pngInfo, &pngEndInfo);
+
+ fclose(fd);
+
+ /* Convert the row data to ARGB format. */
+ /* Source is stored ARGB bytes. */
+ /* Destination is stored in unsigned longs with A most significant. */
+ result->data = Allocate(sizeof(unsigned long)
+ * result->width * result->height);
+ for(y = 0; y < result->height; y++) {
+ for(x = 0; x < result->width; x++) {
+ temp = (unsigned long)rows[y][4 * x + 0] << 24;
+ temp |= (unsigned long)rows[y][4 * x + 1] << 16;
+ temp |= (unsigned long)rows[y][4 * x + 2] << 8;
+ temp |= (unsigned long)rows[y][4 * x + 3] << 0;
+ result->data[y * result->width + x] = temp;
+ }
+ }
+
+ ReleaseStack(rows);
+ Release(data);
+
+ return result;
+
+#else
+
+ return NULL;
+
+#endif
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+ImageNode *LoadXPMImage(const char *fileName) {
+
+ ImageNode *result = NULL;
+
+#ifdef USE_XPM
+
+ XpmAttributes attr;
+ XImage *image;
+ XImage *shape;
+ int rc;
+
+ Assert(fileName);
+
+ attr.valuemask = XpmAllocColor | XpmFreeColors | XpmColorClosure;
+ attr.alloc_color = AllocateColor;
+ attr.free_colors = FreeColors;
+ attr.color_closure = NULL;
+ rc = XpmReadFileToImage(display, (char*)fileName, &image, &shape, &attr);
+ if(rc == XpmSuccess) {
+ result = CreateImageFromXImages(image, shape);
+
+ JXDestroyImage(image);
+ if(shape) {
+ JXDestroyImage(shape);
+ }
+ }
+
+#endif
+
+ return result;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+ImageNode *CreateImageFromXImages(XImage *image, XImage *shape) {
+
+ ImageNode *result;
+ XColor color;
+ unsigned long red, green, blue, alpha;
+ int index;
+ int x, y;
+
+ result = Allocate(sizeof(ImageNode));
+ result->data = Allocate(sizeof(unsigned long)
+ * image->width * image->height);
+ result->width = image->width;
+ result->height = image->height;
+
+ index = 0;
+ for(y = 0; y < image->height; y++) {
+ for(x = 0; x < image->width; x++) {
+
+ color.pixel = XGetPixel(image, x, y);
+ GetColorFromIndex(&color);
+
+ red = color.red >> 8;
+ green = color.green >> 8;
+ blue = color.blue >> 8;
+
+ alpha = 0;
+ if(!shape || XGetPixel(shape, x, y)) {
+ alpha = 255;
+ }
+
+ result->data[index] = alpha << 24;
+ result->data[index] |= red << 16;
+ result->data[index] |= green << 8;
+ result->data[index] |= blue;
+ ++index;
+
+ }
+ }
+
+ return result;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyImage(ImageNode *image) {
+ if(image) {
+ Release(image->data);
+ Release(image);
+ }
+}
+
+/****************************************************************************
+ * Function to allocate a color for libxpm.
+ ****************************************************************************/
+#ifdef USE_XPM
+int AllocateColor(Display *d, Colormap cmap, char *name,
+ XColor *c, void *closure)
+{
+
+ if(name) {
+ if(!JXParseColor(d, cmap, name, c)) {
+ return -1;
+ }
+ }
+
+ GetColorIndex(c);
+ return 1;
+
+}
+#endif
+
+/****************************************************************************
+ * Function to free colors allocated by libxpm.
+ * We don't need to do anything here as color.c takes care of this.
+ ****************************************************************************/
+#ifdef USE_XPM
+int FreeColors(Display *d, Colormap cmap, Pixel *pixels, int n,
+ void *closure) {
+
+ return 1;
+
+}
+#endif
+
diff --git a/src/image.h b/src/image.h
new file mode 100644
index 0000000..1291e1b
--- /dev/null
+++ b/src/image.h
@@ -0,0 +1,47 @@
+/**
+ * @file image.h
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Functions to load images.
+ *
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+/** Structure to represent an image. */
+typedef struct ImageNode {
+
+#ifdef USE_PNG
+ png_uint_32 width; /**< Width of the image. */
+ png_uint_32 height; /**< Height of the image. */
+#else
+ int width; /**< Width of the image. */
+ int height; /**< Height of the image. */
+#endif
+
+ unsigned long *data; /**< Image data. */
+
+} ImageNode;
+
+/** Load an image from a file.
+ * @param fileName The file containing the image.
+ * @return A new image node (NULL if the image could not be loaded).
+ */
+ImageNode *LoadImage(const char *fileName);
+
+/** Load an image from data.
+ * The data must be in the format from the EWMH spec.
+ * @param data The image data.
+ * @return A new image node (NULL if there were errors.
+ */
+ImageNode *LoadImageFromData(char **data);
+
+/** Destroy an image node.
+ * @param image The image to destroy.
+ */
+void DestroyImage(ImageNode *image);
+
+#endif
+
diff --git a/src/jwm.h b/src/jwm.h
new file mode 100644
index 0000000..131528a
--- /dev/null
+++ b/src/jwm.h
@@ -0,0 +1,128 @@
+/**
+ * @file jwm.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief The main JWM header file.
+ *
+ */
+
+#ifndef JWM_H
+#define JWM_H
+
+#include "../config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+
+#ifdef HAVE_STDARG_H
+# include <stdarg.h>
+#endif
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef HAVE_TIME_H
+# include <time.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include <X11/Xlib.h>
+#ifdef HAVE_X11_XUTIL_H
+# include <X11/Xutil.h>
+#endif
+#ifdef HAVE_X11_XRESOURCE_H
+# include <X11/Xresource.h>
+#endif
+#ifdef HAVE_X11_CURSORFONT_H
+# include <X11/cursorfont.h>
+#endif
+#ifdef HAVE_X11_XPROTO_H
+# include <X11/Xproto.h>
+#endif
+#ifdef HAVE_X11_XATOM_H
+# include <X11/Xatom.h>
+#endif
+#ifdef HAVE_X11_KEYSYM_H
+# include <X11/keysym.h>
+#endif
+
+#ifdef USE_XPM
+# include <X11/xpm.h>
+#endif
+#ifdef USE_PNG
+# include <png.h>
+#endif
+#ifdef USE_SHAPE
+# include <X11/extensions/shape.h>
+#endif
+#ifdef USE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif
+#ifdef USE_XFT
+# ifdef HAVE_FT2BUILD_H
+# include <ft2build.h>
+# endif
+# include <X11/Xft/Xft.h>
+#endif
+#ifdef USE_XRENDER
+# include <X11/extensions/Xrender.h>
+#endif
+#ifdef USE_FRIBIDI
+# include <fribidi/fribidi.h>
+# include <fribidi/fribidi_char_sets_utf8.h>
+#endif
+
+#define MAX_DESKTOP_COUNT 8
+
+#define MAX_INCLUDE_DEPTH 16
+
+#define MAX_BORDER_WIDTH 32
+#define MIN_BORDER_WIDTH 3
+#define DEFAULT_BORDER_WIDTH 5
+
+#define MAX_TITLE_HEIGHT 64
+#define MIN_TITLE_HEIGHT 2
+#define DEFAULT_TITLE_HEIGHT 21
+
+#define MAX_DOUBLE_CLICK_DELTA 32
+#define MIN_DOUBLE_CLICK_DELTA 0
+#define DEFAULT_DOUBLE_CLICK_DELTA 2
+
+#define MAX_DOUBLE_CLICK_SPEED 2000
+#define MIN_DOUBLE_CLICK_SPEED 1
+#define DEFAULT_DOUBLE_CLICK_SPEED 400
+
+#define MAX_SNAP_DISTANCE 32
+#define MIN_SNAP_DISTANCE 1
+#define DEFAULT_SNAP_DISTANCE 5
+
+#define MAX_TRAY_BORDER 32
+#define MIN_TRAY_BORDER 0
+#define DEFAULT_TRAY_BORDER 1
+
+#define MOVE_DELTA 3
+
+#define SHELL_NAME "/bin/sh"
+
+#define DEFAULT_MENU_TITLE "JWM"
+
+#define DEFAULT_DESKTOP_COUNT 4
+
+#include "debug.h"
+#include "jxlib.h"
+
+#endif
+
diff --git a/src/jxlib.h b/src/jxlib.h
new file mode 100644
index 0000000..e306c2d
--- /dev/null
+++ b/src/jxlib.h
@@ -0,0 +1,420 @@
+/**
+ * @file jxlib.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Macros to wrap X calls for debugging.
+ *
+ */
+
+#ifndef JXLIB_H
+#define JXLIB_H
+
+#define JXAddToSaveSet( a, b ) \
+ ( SetCheckpoint(), XAddToSaveSet( a, b ) )
+
+#define JXAllocColor( a, b, c ) \
+ ( SetCheckpoint(), XAllocColor( a, b, c ) )
+
+#define JXGetRGBColormaps( a, b, c, d, e ) \
+ ( SetCheckpoint(), XGetRGBColormaps( a, b, c, d, e ) )
+
+#define JXQueryColor( a, b, c ) \
+ ( SetCheckpoint(), XQueryColor( a, b, c ) )
+
+#define JXAllowEvents( a, b, c ) \
+ ( SetCheckpoint(), XAllowEvents( a, b, c ) )
+
+#define JXChangeProperty( a, b, c, d, e, f, g, h ) \
+ ( SetCheckpoint(), XChangeProperty( a, b, c, d, e, f, g, h ) )
+
+#define JXChangeWindowAttributes( a, b, c, d ) \
+ ( SetCheckpoint(), XChangeWindowAttributes( a, b, c, d ) )
+
+#define JXCheckTypedEvent( a, b, c ) \
+ ( SetCheckpoint(), XCheckTypedEvent( a, b, c ) )
+
+#define JXCheckTypedWindowEvent( a, b, c, d ) \
+ ( SetCheckpoint(), XCheckTypedWindowEvent( a, b, c, d ) )
+
+#define JXClearWindow( a, b ) \
+ ( SetCheckpoint(), XClearWindow( a, b ) )
+
+#define JXCloseDisplay( a ) \
+ ( SetCheckpoint(), XCloseDisplay( a ) )
+
+#define JXConfigureWindow( a, b, c, d ) \
+ ( SetCheckpoint(), XConfigureWindow( a, b, c, d ) )
+
+#define JXConnectionNumber( a ) \
+ ( SetCheckpoint(), XConnectionNumber( a ) )
+
+#define JXCopyArea( a, b, c, d, e, f, g, h, i, j ) \
+ ( SetCheckpoint(), XCopyArea( a, b, c, d, e, f, g, h, i, j ) )
+
+#define JXCopyPlane( a, b, c, d, e, f, g, h, i, j, k ) \
+ ( SetCheckpoint(), XCopyPlane( a, b, c, d, e, f, g, h, i, j, k ) )
+
+#define JXCreateFontCursor( a, b ) \
+ ( SetCheckpoint(), XCreateFontCursor( a, b ) )
+
+#define JXCreateGC( a, b, c, d ) \
+ ( SetCheckpoint(), XCreateGC( a, b, c, d ) )
+
+#define JXCreateImage( a, b, c, d, e, f, g, h, i, j ) \
+ ( \
+ SetCheckpoint(), \
+ XCreateImage( a, b, c, d, e, f, g, h, i, j ) \
+ )
+
+#define JXCreatePixmap( a, b, c, d, e ) \
+ ( SetCheckpoint(), XCreatePixmap( a, b, c, d, e ) )
+
+#define JXCreatePixmapFromBitmapData( a, b, c, d, e, f, g, h ) \
+ ( \
+ SetCheckpoint(), \
+ XCreatePixmapFromBitmapData( a, b, c, d, e, f, g, h ) \
+ )
+
+#define JXCreateSimpleWindow( a, b, c, d, e, f, g, h, i ) \
+ ( \
+ SetCheckpoint(), \
+ XCreateSimpleWindow( a, b, c, d, e, f, g, h, i ) \
+ )
+
+#define JXCreateWindow( a, b, c, d, e, f, g, h, i, j, k, l ) \
+ ( \
+ SetCheckpoint(), \
+ XCreateWindow( a, b, c, d, e, f, g, h, i, j, k, l ) \
+ )
+
+#define JXDefineCursor( a, b, c ) \
+ ( SetCheckpoint(), XDefineCursor( a, b, c ) )
+
+#define JXDestroyImage( a ) \
+ ( SetCheckpoint(), XDestroyImage( a ) )
+
+#define JXDestroyWindow( a, b ) \
+ ( SetCheckpoint(), XDestroyWindow( a, b ) )
+
+#define JXDrawPoint( a, b, c, d, e ) \
+ ( SetCheckpoint(), XDrawPoint( a, b, c, d, e ) )
+
+#define JXDrawLine( a, b, c, d, e, f, g ) \
+ ( SetCheckpoint(), XDrawLine( a, b, c, d, e, f, g ) )
+
+#define JXDrawRectangle( a, b, c, d, e, f, g ) \
+ ( SetCheckpoint(), XDrawRectangle( a, b, c, d, e, f, g ) )
+
+#define JXDrawString( a, b, c, d, e, f, g ) \
+ ( SetCheckpoint(), XDrawString( a, b, c, d, e, f, g ) )
+
+#define JXFetchName( a, b, c ) \
+ ( SetCheckpoint(), XFetchName( a, b, c ) )
+
+#define JXFillRectangle( a, b, c, d, e, f, g ) \
+ ( SetCheckpoint(), XFillRectangle( a, b, c, d, e, f, g ) )
+
+#define JXFlush( a ) \
+ ( SetCheckpoint(), XFlush( a ) )
+
+#define JXFree( a ) \
+ ( SetCheckpoint(), XFree( a ) )
+
+#define JXFreeColors( a, b, c, d, e ) \
+ ( SetCheckpoint(), XFreeColors( a, b, c, d, e ) )
+
+#define JXFreeCursor( a, b ) \
+ ( SetCheckpoint(), XFreeCursor( a, b ) )
+
+#define JXFreeFont( a, b ) \
+ ( SetCheckpoint(), XFreeFont( a, b ) )
+
+#define JXFreeGC( a, b ) \
+ ( SetCheckpoint(), XFreeGC( a, b ) )
+
+#define JXFreeModifiermap( a ) \
+ ( SetCheckpoint(), XFreeModifiermap( a ) )
+
+#define JXFreePixmap( a, b ) \
+ ( SetCheckpoint(), XFreePixmap( a, b ) )
+
+#define JXGetAtomName( a, b ) \
+ ( SetCheckpoint(), XGetAtomName( a, b ) )
+
+#define JXGetModifierMapping( a ) \
+ ( SetCheckpoint(), XGetModifierMapping( a ) )
+
+#define JXGetSubImage( a, b, c, d, e, f, g, h, i, j, k ) \
+ ( SetCheckpoint(), XGetSubImage( a, b, c, d, e, f, g, h, i, j, k ) )
+
+#define JXGetTransientForHint( a, b, c ) \
+ ( SetCheckpoint(), XGetTransientForHint( a, b, c ) )
+
+#define JXGetClassHint( a, b, c ) \
+ ( SetCheckpoint(), XGetClassHint( a, b, c ) )
+
+#define JXGetWindowAttributes( a, b, c ) \
+ ( SetCheckpoint(), XGetWindowAttributes( a, b, c ) )
+
+#define JXGetWindowProperty( a, b, c, d, e, f, g, h, i, j, k, l ) \
+ ( SetCheckpoint(), \
+ XGetWindowProperty( a, b, c, d, e, f, g, h, i, j, k, l ) )
+
+#define JXGetWMColormapWindows( a, b, c, d ) \
+ ( SetCheckpoint(), XGetWMColormapWindows( a, b, c, d ) )
+
+#define JXGetWMNormalHints( a, b, c, d ) \
+ ( SetCheckpoint(), XGetWMNormalHints( a, b, c, d ) )
+
+#define JXSetIconSizes( a, b, c, d ) \
+ ( SetCheckpoint(), XSetIconSizes( a, b, c, d ) )
+
+#define JXSetWindowBorder( a, b, c ) \
+ ( SetCheckpoint(), XSetWindowBorder( a, b, c ) )
+
+#define JXGetWMHints( a, b ) \
+ ( SetCheckpoint(), XGetWMHints( a, b ) )
+
+#define JXGrabButton( a, b, c, d, e, f, g, h, i, j ) \
+ ( SetCheckpoint(), XGrabButton( a, b, c, d, e, f, g, h, i, j ) )
+
+#define JXKeycodeToKeysym( a, b, c ) \
+ ( SetCheckpoint(), XKeycodeToKeysym( a, b, c ) )
+
+#define JXGrabKey( a, b, c, d, e, f, g ) \
+ ( SetCheckpoint(), XGrabKey( a, b, c, d, e, f, g ) )
+
+#define JXUngrabKey( a, b, c, d ) \
+ ( SetCheckpoint(), XUngrabKey( a, b, c, d ) )
+
+#define JXGrabKeyboard( a, b, c, d, e, f ) \
+ ( SetCheckpoint(), XGrabKeyboard( a, b, c, d, e, f ) )
+
+#define JXGrabPointer( a, b, c, d, e, f, g, h, i ) \
+ ( SetCheckpoint(), XGrabPointer( a, b, c, d, e, f, g, h, i ) )
+
+#define JXGrabServer( a ) \
+ ( SetCheckpoint(), XGrabServer( a ) )
+
+#define JXInstallColormap( a, b ) \
+ ( SetCheckpoint(), XInstallColormap( a, b ) )
+
+#define JXInternAtom( a, b, c ) \
+ ( SetCheckpoint(), XInternAtom( a, b, c ) )
+
+#define JXKeysymToKeycode( a, b ) \
+ ( SetCheckpoint(), XKeysymToKeycode( a, b ) )
+
+#define JXKillClient( a, b ) \
+ ( SetCheckpoint(), XKillClient( a, b ) )
+
+#define JXLoadQueryFont( a, b ) \
+ ( SetCheckpoint(), XLoadQueryFont( a, b ) )
+
+#define JXMapRaised( a, b ) \
+ ( SetCheckpoint(), XMapRaised( a, b ) )
+
+#define JXMapWindow( a, b ) \
+ ( SetCheckpoint(), XMapWindow( a, b ) )
+
+#define JXMoveResizeWindow( a, b, c, d, e, f ) \
+ ( SetCheckpoint(), XMoveResizeWindow( a, b, c, d, e, f ) )
+
+#define JXMoveWindow( a, b, c, d ) \
+ ( SetCheckpoint(), XMoveWindow( a, b, c, d ) )
+
+#define JXNextEvent( a, b ) \
+ ( SetCheckpoint(), XNextEvent( a, b ) )
+
+#define JXMaskEvent( a, b, c ) \
+ ( SetCheckpoint(), XMaskEvent( a, b, c ) )
+
+#define JXCheckMaskEvent( a, b, c ) \
+ ( SetCheckpoint(), XCheckMaskEvent( a, b, c ) )
+
+#define JXOpenDisplay( a ) \
+ ( SetCheckpoint(), XOpenDisplay( a ) )
+
+#define JXParseColor( a, b, c, d ) \
+ ( SetCheckpoint(), XParseColor( a, b, c, d ) )
+
+#define JXPending( a ) \
+ ( SetCheckpoint(), XPending( a ) )
+
+#define JXPutBackEvent( a, b ) \
+ ( SetCheckpoint(), XPutBackEvent( a, b ) )
+
+#define JXGetImage( a, b, c, d, e, f, g, h ) \
+ ( SetCheckpoint(), XGetImage( a, b, c, d, e, f, g, h ) )
+
+#define JXPutImage( a, b, c, d, e, f, g, h, i, j ) \
+ ( SetCheckpoint(), XPutImage( a, b, c, d, e, f, g, h, i, j ) )
+
+#define JXQueryPointer( a, b, c, d, e, f, g, h, i ) \
+ ( SetCheckpoint(), XQueryPointer( a, b, c, d, e, f, g, h, i ) )
+
+#define JXQueryTree( a, b, c, d, e, f ) \
+ ( SetCheckpoint(), XQueryTree( a, b, c, d, e, f ) )
+
+#define JXReparentWindow( a, b, c, d, e ) \
+ ( SetCheckpoint(), XReparentWindow( a, b, c, d, e ) )
+
+#define JXRemoveFromSaveSet( a, b ) \
+ ( SetCheckpoint(), XRemoveFromSaveSet( a, b ) )
+
+#define JXResizeWindow( a, b, c, d ) \
+ ( SetCheckpoint(), XResizeWindow( a, b, c, d ) )
+
+#define JXRestackWindows( a, b, c ) \
+ ( SetCheckpoint(), XRestackWindows( a, b, c ) )
+
+#define JXSelectInput( a, b, c ) \
+ ( SetCheckpoint(), XSelectInput( a, b, c ) )
+
+#define JXSendEvent( a, b, c, d, e ) \
+ ( SetCheckpoint(), XSendEvent( a, b, c, d, e ) )
+
+#define JXSetBackground( a, b, c ) \
+ ( SetCheckpoint(), XSetBackground( a, b, c ) )
+
+#define JXSetClipMask( a, b, c ) \
+ ( SetCheckpoint(), XSetClipMask( a, b, c ) )
+
+#define JXSetClipOrigin( a, b, c, d ) \
+ ( SetCheckpoint(), XSetClipOrigin( a, b, c, d) )
+
+#define JXSetClipRectangles( a, b, c, d, e, f, g ) \
+ ( SetCheckpoint(), XSetClipRectangles( a, b, c, d, e, f, g ) )
+
+#define JXSetErrorHandler( a ) \
+ ( SetCheckpoint(), XSetErrorHandler( a ) )
+
+#define JXSetFont( a, b, c ) \
+ ( SetCheckpoint(), XSetFont( a, b, c ) )
+
+#define JXSetForeground( a, b, c ) \
+ ( SetCheckpoint(), XSetForeground( a, b, c ) )
+
+#define JXSetInputFocus( a, b, c, d ) \
+ ( SetCheckpoint(), XSetInputFocus( a, b, c, d ) )
+
+#define JXSetWindowBackground( a, b, c ) \
+ ( SetCheckpoint(), XSetWindowBackground( a, b, c ) )
+
+#define JXSetWindowBorderWidth( a, b, c ) \
+ ( SetCheckpoint(), XSetWindowBorderWidth( a, b, c ) )
+
+#define JXSetWMNormalHints( a, b, c ) \
+ ( SetCheckpoint(), XSetWMNormalHints( a, b, c ) )
+
+#define JXShapeCombineRectangles( a, b, c, d, e, f, g, h, i ) \
+ ( SetCheckpoint(), XShapeCombineRectangles( a, b, c, d, e, f, g, h, i ) )
+
+#define JXShapeCombineShape( a, b, c, d, e, f, g, h ) \
+ ( SetCheckpoint(), XShapeCombineShape( a, b, c, d, e, f, g, h ) )
+
+#define JXShapeQueryExtension( a, b, c ) \
+ ( SetCheckpoint(), XShapeQueryExtension( a, b, c ) )
+
+#define JXShapeQueryExtents( a, b, c, d, e, f, g, h, i, j, k, l ) \
+ ( SetCheckpoint(), \
+ XShapeQueryExtents( a, b, c, d, e, f, g, h, i, j, k, l ) )
+
+#define JXShapeSelectInput( a, b, c ) \
+ ( SetCheckpoint(), XShapeSelectInput( a, b, c ) )
+
+#define JXStoreName( a, b, c ) \
+ ( SetCheckpoint(), XStoreName( a, b, c ) )
+
+#define JXStringToKeysym( a ) \
+ ( SetCheckpoint(), XStringToKeysym( a ) )
+
+#define JXSync( a, b ) \
+ ( SetCheckpoint(), XSync( a, b ) )
+
+#define JXTextWidth( a, b, c ) \
+ ( SetCheckpoint(), XTextWidth( a, b, c ) )
+
+#define JXUngrabButton( a, b, c, d ) \
+ ( SetCheckpoint(), XUngrabButton( a, b, c, d ) )
+
+#define JXUngrabKeyboard( a, b ) \
+ ( SetCheckpoint(), XUngrabKeyboard( a, b ) )
+
+#define JXUngrabPointer( a, b ) \
+ ( SetCheckpoint(), XUngrabPointer( a, b ) )
+
+#define JXUngrabServer( a ) \
+ ( SetCheckpoint(), XUngrabServer( a ) )
+
+#define JXUnmapWindow( a, b ) \
+ ( SetCheckpoint(), XUnmapWindow( a, b ) )
+
+#define JXWarpPointer( a, b, c, d, e, f, g, h, i ) \
+ ( SetCheckpoint(), XWarpPointer( a, b, c, d, e, f, g, h, i ) )
+
+#define JXSetSelectionOwner( a, b, c, d ) \
+ ( SetCheckpoint(), XSetSelectionOwner( a, b, c, d ) )
+
+#define JXGetSelectionOwner( a, b ) \
+ ( SetCheckpoint(), XGetSelectionOwner( a, b ) )
+
+/* XFT */
+
+#define JXftFontOpenName( a, b, c ) \
+ ( SetCheckpoint(), XftFontOpenName( a, b, c ) )
+
+#define JXftFontOpenXlfd( a, b, c ) \
+ ( SetCheckpoint(), XftFontOpenXlfd( a, b, c ) )
+
+#define JXftDrawCreate( a, b, c, d ) \
+ ( SetCheckpoint(), XftDrawCreate( a, b, c, d ) )
+
+#define JXftDrawDestroy( a ) \
+ ( SetCheckpoint(), XftDrawDestroy( a ) )
+
+#define JXftTextExtentsUtf8( a, b, c, d, e ) \
+ ( SetCheckpoint(), XftTextExtentsUtf8( a, b, c, d, e ) )
+
+#define JXftDrawChange( a, b ) \
+ ( SetCheckpoint(), XftDrawChange( a, b ) )
+
+#define JXftDrawSetClipRectangles( a, b, c, d, e ) \
+ ( SetCheckpoint(), XftDrawSetClipRectangles( a, b, c, d, e ) )
+
+#define JXftDrawStringUtf8( a, b, c, d, e, f, g ) \
+ ( SetCheckpoint(), XftDrawStringUtf8( a, b, c, d, e, f, g ) )
+
+#define JXftColorFree( a, b, c, d ) \
+ ( SetCheckpoint(), XftColorFree( a, b, c, d ) )
+
+#define JXftColorAllocValue( a, b, c, d, e ) \
+ ( SetCheckpoint(), XftColorAllocValue( a, b, c, d, e ) )
+
+#define JXftFontClose( a, b ) \
+ ( SetCheckpoint(), XftFontClose( a, b ) )
+
+/* Xrender */
+
+#define JXRenderQueryExtension( a, b, c ) \
+ ( SetCheckpoint(), XRenderQueryExtension( a, b, c ) )
+
+#define JXRenderFindVisualFormat( a, b ) \
+ ( SetCheckpoint(), XRenderFindVisualFormat( a, b ) )
+
+#define JXRenderFindFormat( a, b, c, d ) \
+ ( SetCheckpoint(), XRenderFindFormat( a, b, c, d ) )
+
+#define JXRenderCreatePicture( a, b, c, d, e ) \
+ ( SetCheckpoint(), XRenderCreatePicture( a, b, c, d, e ) )
+
+#define JXRenderFreePicture( a, b ) \
+ ( SetCheckpoint(), XRenderFreePicture( a, b ) )
+
+#define JXRenderComposite( a, b, c, d, e, f, g, h, i, j, k, l, m ) \
+ ( SetCheckpoint(), \
+ XRenderComposite( a, b, c, d, e, f, g, h, i, j, k, l, m) )
+
+#endif /* JXLIB_H */
+
diff --git a/src/key.c b/src/key.c
new file mode 100644
index 0000000..b6e9ee6
--- /dev/null
+++ b/src/key.c
@@ -0,0 +1,452 @@
+/***************************************************************************
+ * Key input functions.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "key.h"
+#include "main.h"
+#include "client.h"
+#include "root.h"
+#include "error.h"
+#include "tray.h"
+#include "misc.h"
+
+typedef enum {
+ MASK_NONE = 0,
+ MASK_ALT = 1,
+ MASK_CTRL = 2,
+ MASK_SHIFT = 4,
+ MASK_HYPER = 8,
+ MASK_META = 16,
+ MASK_SUPER = 32
+} MaskType;
+
+typedef struct KeyNode {
+
+ /* These are filled in when the configuration file is parsed */
+ int key;
+ unsigned int mask;
+ KeySym symbol;
+ char *command;
+ struct KeyNode *next;
+
+ /* This is filled in by StartupKeys if it isn't already set. */
+ KeyCode code;
+
+ /* This is filled in by StartupKeys. */
+ unsigned int state;
+
+} KeyNode;
+
+typedef struct LockNode {
+ KeySym symbol;
+ unsigned int mask;
+} LockNode;
+
+static XModifierKeymap *modmap;
+
+static LockNode mods[] = {
+ { XK_Caps_Lock, 0 },
+ { XK_Num_Lock, 0 }
+};
+
+static KeyNode *bindings;
+static unsigned int modifierMask;
+
+static unsigned int GetModifierMask(KeySym key);
+static unsigned int ParseModifierString(const char *str);
+static KeySym ParseKeyString(const char *str);
+static int ShouldGrab(KeyType key);
+static void GrabKey(KeyNode *np);
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeKeys() {
+
+ bindings = NULL;
+ modifierMask = 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void StartupKeys() {
+
+ KeyNode *np;
+ int x;
+
+ modmap = JXGetModifierMapping(display);
+
+ for(x = 0; x < sizeof(mods) / sizeof(mods[0]); x++) {
+ mods[x].mask = GetModifierMask(mods[x].symbol);
+ modifierMask |= mods[x].mask;
+ }
+
+ for(np = bindings; np; np = np->next) {
+
+ np->state = 0;
+ if(np->mask & MASK_ALT) {
+ np->state |= GetModifierMask(XK_Alt_L);
+ }
+ if(np->mask & MASK_CTRL) {
+ np->state |= GetModifierMask(XK_Control_L);
+ }
+ if(np->mask & MASK_SHIFT) {
+ np->state |= GetModifierMask(XK_Shift_L);
+ }
+ if(np->mask & MASK_HYPER) {
+ np->state |= GetModifierMask(XK_Hyper_L);
+ }
+ if(np->mask & MASK_META) {
+ np->state |= GetModifierMask(XK_Meta_L);
+ }
+ if(np->mask & MASK_SUPER) {
+ np->state |= GetModifierMask(XK_Super_L);
+ }
+
+ if(!np->code) {
+ np->code = JXKeysymToKeycode(display, np->symbol);
+ }
+
+ if(ShouldGrab(np->key)) {
+ GrabKey(np);
+ }
+
+ }
+
+ JXFreeModifiermap(modmap);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShutdownKeys() {
+
+ ClientNode *np;
+ int layer;
+
+ for(layer = 0; layer < LAYER_COUNT; layer++) {
+ for(np = nodes[layer]; np; np = np->next) {
+ JXUngrabKey(display, AnyKey, AnyModifier, np->window);
+ }
+ }
+
+ JXUngrabKey(display, AnyKey, AnyModifier, rootWindow);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyKeys() {
+
+ KeyNode *np;
+
+ while(bindings) {
+ np = bindings->next;
+ if(bindings->command) {
+ Release(bindings->command);
+ }
+ Release(bindings);
+ bindings = np;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void GrabKey(KeyNode *np) {
+
+ TrayType *tp;
+ int x;
+ int index, maxIndex;
+ unsigned int mask;
+
+ maxIndex = 1 << (sizeof(mods) / sizeof(mods[0]));
+ for(index = 0; index < maxIndex; index++) {
+
+ mask = 0;
+ for(x = 0; x < sizeof(mods) / sizeof(mods[0]); x++) {
+ if(index & (1 << x)) {
+ mask |= mods[x].mask;
+ }
+ }
+
+ mask |= np->state;
+ JXGrabKey(display, np->code, mask,
+ rootWindow, True, GrabModeAsync, GrabModeAsync);
+ for(tp = GetTrays(); tp; tp = tp->next) {
+ JXGrabKey(display, np->code, mask,
+ tp->window, True, GrabModeAsync, GrabModeAsync);
+ }
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+KeyType GetKey(const XKeyEvent *event) {
+
+ KeyNode *np;
+ unsigned int state;
+
+ state = event->state & ~modifierMask;
+ for(np = bindings; np; np = np->next) {
+ if(np->state == state && np->code == event->keycode) {
+ return np->key;
+ }
+ }
+
+ return KEY_NONE;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void RunKeyCommand(const XKeyEvent *event) {
+
+ KeyNode *np;
+
+ for(np = bindings; np; np = np->next) {
+ if(np->state == event->state && np->code == event->keycode) {
+ RunCommand(np->command);
+ return;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShowKeyMenu(const XKeyEvent *event) {
+
+ KeyNode *np;
+ int button;
+
+ for(np = bindings; np; np = np->next) {
+ if(np->state == event->state && np->code == event->keycode) {
+ button = atoi(np->command);
+ if(button >= 0 && button <= 9) {
+ ShowRootMenu(button, 0, 0);
+ }
+ return;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ShouldGrab(KeyType key) {
+ switch(key & 0xFF) {
+ case KEY_NEXT:
+ case KEY_NEXT_STACKED:
+ case KEY_CLOSE:
+ case KEY_MIN:
+ case KEY_MAX:
+ case KEY_SHADE:
+ case KEY_MOVE:
+ case KEY_RESIZE:
+ case KEY_ROOT:
+ case KEY_WIN:
+ case KEY_DESKTOP:
+ case KEY_EXEC:
+ case KEY_RESTART:
+ case KEY_EXIT:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void GrabKeys(ClientNode *np) {
+
+ KeyNode *kp;
+
+ for(kp = bindings; kp; kp = kp->next) {
+ if(ShouldGrab(kp->key)) {
+ JXGrabKey(display, kp->code, kp->state,
+ np->window, True, GrabModeAsync, GrabModeAsync);
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+unsigned int GetModifierMask(KeySym key) {
+
+ KeyCode temp;
+ int x;
+
+ temp = JXKeysymToKeycode(display, key);
+ for(x = 0; x < 8 * modmap->max_keypermod; x++) {
+ if(modmap->modifiermap[x] == temp) {
+ return 1 << (x / modmap->max_keypermod);
+ }
+ }
+
+ Warning("modifier not found for keysym 0x%0x", key);
+
+ return 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+unsigned int ParseModifierString(const char *str) {
+ unsigned int mask;
+ int x;
+
+ if(!str) {
+ return MASK_NONE;
+ }
+
+ mask = MASK_NONE;
+ for(x = 0; str[x]; x++) {
+ switch(str[x]) {
+ case 'A':
+ mask |= MASK_ALT;
+ break;
+ case 'C':
+ mask |= MASK_CTRL;
+ break;
+ case 'S':
+ mask |= MASK_SHIFT;
+ break;
+ case 'H':
+ mask |= MASK_HYPER;
+ break;
+ case 'M':
+ mask |= MASK_META;
+ break;
+ case 'P':
+ mask |= MASK_SUPER;
+ break;
+ default:
+ Warning("invalid modifier: \"%c\"", str[x]);
+ break;
+ }
+ }
+
+ return mask;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+KeySym ParseKeyString(const char *str) {
+
+ KeySym symbol;
+
+ symbol = JXStringToKeysym(str);
+ if(symbol == NoSymbol) {
+ Warning("invalid key symbol: \"%s\"", str);
+ }
+
+ return symbol;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void InsertBinding(KeyType key, const char *modifiers,
+ const char *stroke, const char *code, const char *command) {
+
+ KeyNode *np;
+ unsigned int mask;
+ char *temp;
+ int offset;
+ KeySym sym;
+
+ mask = ParseModifierString(modifiers);
+
+ if(stroke && strlen(stroke) > 0) {
+
+ for(offset = 0; stroke[offset]; offset++) {
+ if(stroke[offset] == '#') {
+
+ temp = CopyString(stroke);
+
+ for(temp[offset] = '1'; temp[offset] <= '9'; temp[offset]++) {
+
+ sym = ParseKeyString(temp);
+ if(sym == NoSymbol) {
+ Release(temp);
+ return;
+ }
+
+ np = Allocate(sizeof(KeyNode));
+ np->next = bindings;
+ bindings = np;
+
+ np->key = key | ((temp[offset] - '1' + 1) << 8);
+ np->mask = mask;
+ np->symbol = sym;
+ np->command = NULL;
+ np->code = 0;
+
+ }
+
+ Release(temp);
+
+ return;
+ }
+ }
+
+ sym = ParseKeyString(stroke);
+ if(sym == NoSymbol) {
+ return;
+ }
+
+ np = Allocate(sizeof(KeyNode));
+ np->next = bindings;
+ bindings = np;
+
+ np->key = key;
+ np->mask = mask;
+ np->symbol = sym;
+ np->command = CopyString(command);
+ np->code = 0;
+
+ } else if(code && strlen(code) > 0) {
+
+ np = Allocate(sizeof(KeyNode));
+ np->next = bindings;
+ bindings = np;
+
+ np->key = key;
+ np->mask = mask;
+ np->symbol = NoSymbol;
+ np->command = CopyString(command);
+ np->code = atoi(code);
+
+ } else {
+
+ Warning("neither key nor keycode specified for Key");
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ValidateKeys() {
+
+ KeyNode *kp;
+ int bindex;
+
+ for(kp = bindings; kp; kp = kp->next) {
+ if((kp->key & 0xFF) == KEY_ROOT && kp->command) {
+ bindex = atoi(kp->command);
+ if(!IsRootMenuDefined(bindex)) {
+ Warning("key binding: root menu %d not defined", bindex);
+ }
+ }
+ }
+
+}
+
diff --git a/src/key.h b/src/key.h
new file mode 100644
index 0000000..a18aa46
--- /dev/null
+++ b/src/key.h
@@ -0,0 +1,57 @@
+/**
+ * @file key.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the key binding functions.
+ *
+ */
+
+#ifndef KEY_H
+#define KEY_H
+
+struct ClientNode;
+
+typedef enum {
+ KEY_NONE = 0,
+ KEY_UP = 1,
+ KEY_DOWN = 2,
+ KEY_RIGHT = 3,
+ KEY_LEFT = 4,
+ KEY_ESC = 5,
+ KEY_ENTER = 6,
+ KEY_NEXT = 7,
+ KEY_NEXT_STACKED = 8,
+ KEY_CLOSE = 9,
+ KEY_MIN = 10,
+ KEY_MAX = 11,
+ KEY_SHADE = 12,
+ KEY_MOVE = 13,
+ KEY_RESIZE = 14,
+ KEY_ROOT = 15,
+ KEY_WIN = 16,
+ KEY_DESKTOP = 17,
+ KEY_EXEC = 18,
+ KEY_RESTART = 19,
+ KEY_EXIT = 20
+} KeyType;
+
+void InitializeKeys();
+void StartupKeys();
+void ShutdownKeys();
+void DestroyKeys();
+
+KeyType GetKey(const XKeyEvent *event);
+void GrabKeys(struct ClientNode *np);
+
+void InsertBinding(KeyType key, const char *modifiers,
+ const char *stroke, const char *code, const char *command);
+
+void RunKeyCommand(const XKeyEvent *event);
+
+void ShowKeyMenu(const XKeyEvent *event);
+
+void ValidateKeys();
+
+#endif
+
diff --git a/src/lex.c b/src/lex.c
new file mode 100644
index 0000000..0e0f65d
--- /dev/null
+++ b/src/lex.c
@@ -0,0 +1,572 @@
+/*****************************************************************************
+ * XML lexer functions.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ *****************************************************************************/
+
+#include "jwm.h"
+#include "lex.h"
+#include "error.h"
+#include "misc.h"
+
+static const int BLOCK_SIZE = 16;
+
+/* Order is important! The order must match the order in lex.h */
+static const char *TOKEN_MAP[] = {
+ "[invalid]",
+ "ActiveBackground",
+ "ActiveForeground",
+ "Background",
+ "BorderStyle",
+ "Class",
+ "Clock",
+ "ClockStyle",
+ "Close",
+ "Desktops",
+ "Dock",
+ "DoubleClickSpeed",
+ "DoubleClickDelta",
+ "Exit",
+ "FocusModel",
+ "Font",
+ "Foreground",
+ "Group",
+ "Height",
+ "IconPath",
+ "Include",
+ "JWM",
+ "Key",
+ "Kill",
+ "Layer",
+ "Maximize",
+ "Menu",
+ "MenuStyle",
+ "Minimize",
+ "Mouse",
+ "Move",
+ "MoveMode",
+ "Name",
+ "Option",
+ "Outline",
+ "Pager",
+ "PagerStyle",
+ "Popup",
+ "PopupStyle",
+ "Program",
+ "Resize",
+ "ResizeMode",
+ "Restart",
+ "RestartCommand",
+ "RootMenu",
+ "SendTo",
+ "Separator",
+ "Shade",
+ "ShutdownCommand",
+ "SnapMode",
+ "StartupCommand",
+ "Stick",
+ "Swallow",
+ "TaskListStyle",
+ "TaskList",
+ "Theme",
+ "ThemePath",
+ "Tray",
+ "TrayButton",
+ "TrayButtonStyle",
+ "TrayStyle",
+ "Width"
+};
+
+static TokenNode *head, *current;
+
+static TokenNode *CreateNode(TokenNode *parent, const char *file, int line);
+static AttributeNode *CreateAttribute(TokenNode *np);
+
+static int IsElementEnd(char ch);
+static int IsValueEnd(char ch);
+static int IsAttributeEnd(char ch);
+static int IsSpace(char ch, int *lineNumber);
+static char *ReadElementName(const char *line);
+static char *ReadElementValue(const char *line,
+ const char *file, int *lineNumber);
+static char *ReadAttributeValue(const char *line, const char *file,
+ int *lineNumber);
+static int ParseEntity(const char *entity, char *ch,
+ const char *file, int line);
+static TokenType LookupType(const char *name, TokenNode *np);
+
+/*****************************************************************************
+ *****************************************************************************/
+TokenNode *Tokenize(const char *line, const char *fileName) {
+
+ TokenNode *np;
+ AttributeNode *ap;
+ char *temp;
+ int inElement;
+ int x;
+ int found;
+ int lineNumber;
+
+ head = NULL;
+ current = NULL;
+ inElement = 0;
+ lineNumber = 1;
+
+ x = 0;
+ /* Skip any initial white space */
+ while(IsSpace(line[x], &lineNumber)) ++x;
+
+ /* Skip any XML stuff */
+ if(!strncmp(line + x, "<?", 2)) {
+ while(line[x]) {
+ if(line[x] == '\n') {
+ ++lineNumber;
+ }
+ if(!strncmp(line + x, "?>", 2)) {
+ x += 2;
+ break;
+ }
+ ++x;
+ }
+ }
+
+ while(line[x]) {
+
+ do {
+
+ while(IsSpace(line[x], &lineNumber)) ++x;
+
+ /* Skip comments */
+ found = 0;
+ if(!strncmp(line + x, "<!--", 4)) {
+ while(line[x]) {
+ if(line[x] == '\n') {
+ ++lineNumber;
+ }
+ if(!strncmp(line + x, "-->", 3)) {
+ x += 3;
+ found = 1;
+ break;
+ }
+ ++x;
+ }
+ }
+ } while(found);
+
+ switch(line[x]) {
+ case '<':
+ ++x;
+ if(line[x] == '/') {
+ ++x;
+ temp = ReadElementName(line + x);
+
+ if(current) {
+
+ if(temp) {
+
+ if(current->type != LookupType(temp, NULL)) {
+ Warning("%s[%d]: close tag \"%s\" does not "
+ "match open tag \"%s\"",
+ fileName, lineNumber, temp,
+ GetTokenName(current));
+ }
+
+ } else {
+ Warning("%s[%d]: unexpected and invalid close tag",
+ fileName, lineNumber);
+ }
+
+ current = current->parent;
+ } else {
+ if(temp) {
+ Warning("%s[%d]: close tag \"%s\" without open "
+ "tag", fileName, lineNumber, temp);
+ } else {
+ Warning("%s[%d]: invalid close tag", fileName, lineNumber);
+ }
+ }
+
+ if(temp) {
+ x += strlen(temp);
+ Release(temp);
+ }
+
+ } else {
+ np = current;
+ current = NULL;
+ np = CreateNode(np, fileName, lineNumber);
+ temp = ReadElementName(line + x);
+ if(temp) {
+ x += strlen(temp);
+ LookupType(temp, np);
+ Release(temp);
+ } else {
+ Warning("%s[%d]: invalid open tag", fileName, lineNumber);
+ }
+ }
+ inElement = 1;
+ break;
+ case '/':
+ if(inElement) {
+ ++x;
+ if(line[x] == '>' && current) {
+ ++x;
+ current = current->parent;
+ inElement = 0;
+ } else {
+ Warning("%s[%d]: invalid tag", fileName, lineNumber);
+ }
+ } else {
+ goto ReadDefault;
+ }
+ break;
+ case '>':
+ ++x;
+ inElement = 0;
+ break;
+ default:
+ReadDefault:
+ if(inElement) {
+ ap = CreateAttribute(current);
+ ap->name = ReadElementName(line + x);
+ if(ap->name) {
+ x += strlen(ap->name);
+ if(line[x] == '=') {
+ ++x;
+ }
+ if(line[x] == '\"') {
+ ++x;
+ }
+ ap->value = ReadAttributeValue(line + x, fileName,
+ &lineNumber);
+ if(ap->value) {
+ x += strlen(ap->value);
+ }
+ if(line[x] == '\"') {
+ ++x;
+ }
+ }
+ } else {
+ temp = ReadElementValue(line + x, fileName, &lineNumber);
+ if(temp) {
+ x += strlen(temp);
+ if(current) {
+ if(current->value) {
+ current->value = Reallocate(current->value,
+ strlen(current->value) + strlen(temp) + 1);
+ strcat(current->value, temp);
+ Release(temp);
+ } else {
+ current->value = temp;
+ }
+ } else {
+ if(temp[0]) {
+ Warning("%s[%d]: unexpected text: \"%s\"",
+ fileName, lineNumber, temp);
+ }
+ Release(temp);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ return head;
+}
+
+/*****************************************************************************
+ * Parse an entity reference.
+ * The entity value is returned in ch and the length of the entity
+ * is returned as the value of the function.
+ *****************************************************************************/
+int ParseEntity(const char *entity, char *ch, const char *file, int line) {
+ char *temp;
+ int x;
+
+ if(!strncmp("&quot;", entity, 6)) {
+ *ch = '\"';
+ return 6;
+ } else if(!strncmp("&lt;", entity, 4)) {
+ *ch = '<';
+ return 4;
+ } else if(!strncmp("&gt;", entity, 4)) {
+ *ch = '>';
+ return 4;
+ } else if(!strncmp("&amp;", entity, 5)) {
+ *ch = '&';
+ return 5;
+ } else if(!strncmp("&apos;", entity, 6)) {
+ *ch = '\'';
+ return 6;
+ } else {
+ for(x = 0; entity[x]; x++) {
+ if(entity[x] == ';') {
+ break;
+ }
+ }
+ temp = AllocateStack(x + 2);
+ strncpy(temp, entity, x + 1);
+ temp[x + 1] = 0;
+ Warning("%s[%d]: invalid entity: \"%.8s\"", file, line, temp);
+ ReleaseStack(temp);
+ *ch = '&';
+ return 1;
+ }
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+int IsElementEnd(char ch) {
+ switch(ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\"':
+ case '>':
+ case '<':
+ case '/':
+ case '=':
+ case 0:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+int IsAttributeEnd(char ch) {
+ switch(ch) {
+ case 0:
+ case '\"':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+int IsValueEnd(char ch) {
+ switch(ch) {
+ case 0:
+ case '<':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+int IsSpace(char ch, int *lineNumber) {
+ switch(ch) {
+ case ' ':
+ case '\t':
+ case '\r':
+ return 1;
+ case '\n':
+ ++*lineNumber;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+char *ReadElementName(const char *line) {
+ char *buffer;
+ int len, max;
+ int x;
+
+ len = 0;
+ max = BLOCK_SIZE;
+ buffer = Allocate(max + 1);
+
+ for(x = 0; !IsElementEnd(line[x]); x++) {
+ buffer[len++] = line[x];
+ if(len >= max) {
+ max += BLOCK_SIZE;
+ buffer = Reallocate(buffer, max + 1);
+ }
+ }
+ buffer[len] = 0;
+
+ return buffer;
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+char *ReadElementValue(const char *line, const char *file, int *lineNumber) {
+ char *buffer;
+ char ch;
+ int len, max;
+ int x;
+
+ len = 0;
+ max = BLOCK_SIZE;
+ buffer = Allocate(max + 1);
+
+ for(x = 0; !IsValueEnd(line[x]); x++) {
+ if(line[x] == '&') {
+ x += ParseEntity(line + x, &ch, file, *lineNumber) - 1;
+ if(ch) {
+ buffer[len] = ch;
+ } else {
+ buffer[len] = line[x];
+ }
+ } else {
+ if(line[x] == '\n') {
+ ++*lineNumber;
+ }
+ buffer[len] = line[x];
+ }
+ ++len;
+ if(len >= max) {
+ max += BLOCK_SIZE;
+ buffer = Reallocate(buffer, max + 1);
+ }
+ }
+ buffer[len] = 0;
+ Trim(buffer);
+
+ return buffer;
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+char *ReadAttributeValue(const char *line, const char *file,
+ int *lineNumber) {
+
+ char *buffer;
+ char ch;
+ int len, max;
+ int x;
+
+ len = 0;
+ max = BLOCK_SIZE;
+ buffer = Allocate(max + 1);
+
+ for(x = 0; !IsAttributeEnd(line[x]); x++) {
+ if(line[x] == '&') {
+ x += ParseEntity(line + x, &ch, file, *lineNumber) - 1;
+ if(ch) {
+ buffer[len] = ch;
+ } else {
+ buffer[len] = line[x];
+ }
+ } else {
+ if(line[x] == '\n') {
+ ++*lineNumber;
+ }
+ buffer[len] = line[x];
+ }
+ ++len;
+ if(len >= max) {
+ max += BLOCK_SIZE;
+ buffer = Reallocate(buffer, max + 1);
+ }
+ }
+ buffer[len] = 0;
+
+ return buffer;
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+TokenType LookupType(const char *name, TokenNode *np) {
+ unsigned int x;
+
+ Assert(name);
+
+ for(x = 0; x < sizeof(TOKEN_MAP) / sizeof(char*); x++) {
+ if(!strcmp(name, TOKEN_MAP[x])) {
+ if(np) {
+ np->type = x;
+ }
+ return x;
+ }
+ }
+
+ if(np) {
+ np->type = TOK_INVALID;
+ np->invalidName = CopyString(name);
+ }
+
+ return TOK_INVALID;
+
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+const char *GetTokenName(const TokenNode *tp) {
+ if(tp->invalidName) {
+ return tp->invalidName;
+ } else if(tp->type >= sizeof(TOKEN_MAP) / sizeof(const char*)) {
+ return "[invalid]";
+ } else {
+ return TOKEN_MAP[tp->type];
+ }
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+const char *GetTokenTypeName(TokenType type) {
+ return TOKEN_MAP[type];
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+TokenNode *CreateNode(TokenNode *parent, const char *file, int line) {
+ TokenNode *np;
+
+ np = Allocate(sizeof(TokenNode));
+ np->type = TOK_INVALID;
+ np->value = NULL;
+ np->attributes = NULL;
+ np->subnodeHead = NULL;
+ np->subnodeTail = NULL;
+ np->parent = parent;
+ np->next = NULL;
+
+ np->fileName = Allocate(strlen(file) + 1);
+ strcpy(np->fileName, file);
+ np->line = line;
+ np->invalidName = NULL;
+
+ if(!head) {
+ head = np;
+ }
+ if(parent) {
+ if(parent->subnodeHead) {
+ parent->subnodeTail->next = np;
+ } else {
+ parent->subnodeHead = np;
+ }
+ parent->subnodeTail = np;
+ } else if(current) {
+ current->next = np;
+ }
+ current = np;
+
+ return np;
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+AttributeNode *CreateAttribute(TokenNode *np) {
+ AttributeNode *ap;
+
+ ap = Allocate(sizeof(AttributeNode));
+ ap->name = NULL;
+ ap->value = NULL;
+
+ ap->next = np->attributes;
+ np->attributes = ap;
+
+ return ap;
+}
+
+
diff --git a/src/lex.h b/src/lex.h
new file mode 100644
index 0000000..2105d0e
--- /dev/null
+++ b/src/lex.h
@@ -0,0 +1,115 @@
+/**
+ * @file lex.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief XML lexer header file.
+ *
+ */
+
+#ifndef LEX_H
+#define LEX_H
+
+/** Tokens.
+ * Note that any change made to this typedef must be reflected in
+ * TOKEN_MAP in lex.c.
+ */
+typedef enum {
+
+ TOK_INVALID,
+
+ TOK_ACTIVEBACKGROUND,
+ TOK_ACTIVEFOREGROUND,
+ TOK_BACKGROUND,
+ TOK_BORDERSTYLE,
+ TOK_CLASS,
+ TOK_CLOCK,
+ TOK_CLOCKSTYLE,
+ TOK_CLOSE,
+ TOK_DESKTOPS,
+ TOK_DOCK,
+ TOK_DOUBLECLICKSPEED,
+ TOK_DOUBLECLICKDELTA,
+ TOK_EXIT,
+ TOK_FOCUSMODEL,
+ TOK_FONT,
+ TOK_FOREGROUND,
+ TOK_GROUP,
+ TOK_HEIGHT,
+ TOK_ICONPATH,
+ TOK_INCLUDE,
+ TOK_JWM,
+ TOK_KEY,
+ TOK_KILL,
+ TOK_LAYER,
+ TOK_MAXIMIZE,
+ TOK_MENU,
+ TOK_MENUSTYLE,
+ TOK_MINIMIZE,
+ TOK_MOUSE,
+ TOK_MOVE,
+ TOK_MOVEMODE,
+ TOK_NAME,
+ TOK_OPTION,
+ TOK_OUTLINE,
+ TOK_PAGER,
+ TOK_PAGERSTYLE,
+ TOK_POPUP,
+ TOK_POPUPSTYLE,
+ TOK_PROGRAM,
+ TOK_RESIZE,
+ TOK_RESIZEMODE,
+ TOK_RESTART,
+ TOK_RESTARTCOMMAND,
+ TOK_ROOTMENU,
+ TOK_SENDTO,
+ TOK_SEPARATOR,
+ TOK_SHADE,
+ TOK_SHUTDOWNCOMMAND,
+ TOK_SNAPMODE,
+ TOK_STARTUPCOMMAND,
+ TOK_STICK,
+ TOK_SWALLOW,
+ TOK_TASKLISTSTYLE,
+ TOK_TASKLIST,
+ TOK_THEME,
+ TOK_THEMEPATH,
+ TOK_TRAY,
+ TOK_TRAYBUTTON,
+ TOK_TRAYBUTTONSTYLE,
+ TOK_TRAYSTYLE,
+ TOK_WIDTH
+
+} TokenType;
+
+/** Structure to represent an XML attribute. */
+typedef struct AttributeNode {
+
+ char *name; /**< The name of the attribute. */
+ char *value; /**< The value for the attribute. */
+ struct AttributeNode *next; /**< The next attribute in the list. */
+
+} AttributeNode;
+
+/** Structure to represent an XML tag. */
+typedef struct TokenNode {
+
+ TokenType type;
+ char *invalidName;
+ char *value;
+ char *fileName;
+ int line;
+ struct AttributeNode *attributes;
+ struct TokenNode *parent;
+ struct TokenNode *subnodeHead, *subnodeTail;
+ struct TokenNode *next;
+
+} TokenNode;
+
+TokenNode *Tokenize(const char *line, const char *fileName);
+
+const char *GetTokenName(const TokenNode *tp);
+const char *GetTokenTypeName(TokenType type);
+
+#endif
+
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..d9c6e78
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,507 @@
+/****************************************************************************
+ * The main entry point and related JWM functions.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "main.h"
+#include "lex.h"
+#include "parse.h"
+#include "help.h"
+#include "error.h"
+#include "event.h"
+
+#include "border.h"
+#include "client.h"
+#include "color.h"
+#include "command.h"
+#include "cursor.h"
+#include "confirm.h"
+#include "font.h"
+#include "hint.h"
+#include "group.h"
+#include "key.h"
+#include "icon.h"
+#include "outline.h"
+#include "taskbar.h"
+#include "tray.h"
+#include "traybutton.h"
+#include "popup.h"
+#include "pager.h"
+#include "swallow.h"
+#include "screen.h"
+#include "root.h"
+#include "desktop.h"
+#include "place.h"
+#include "clock.h"
+#include "dock.h"
+#include "theme.h"
+#include "misc.h"
+
+Display *display = NULL;
+Window rootWindow;
+int rootWidth, rootHeight;
+int rootDepth;
+int rootScreen;
+Colormap rootColormap;
+Visual *rootVisual;
+GC rootGC;
+int colormapCount;
+
+int shouldExit = 0;
+int shouldRestart = 0;
+int isRestarting = 0;
+int initializing = 0;
+
+unsigned int desktopCount = 4;
+unsigned int currentDesktop = 0;
+
+char *exitCommand = NULL;
+
+int borderWidth = DEFAULT_BORDER_WIDTH;
+int titleHeight = DEFAULT_TITLE_HEIGHT;
+
+unsigned int doubleClickSpeed;
+unsigned int doubleClickDelta;
+
+FocusModelType focusModel = FOCUS_SLOPPY;
+
+XContext clientContext;
+XContext frameContext;
+
+#ifdef USE_SHAPE
+int haveShape;
+int shapeEvent;
+#endif
+
+
+static const char *CONFIG_FILE = "/.jwmrc";
+
+static void Initialize();
+static void Startup();
+static void Shutdown();
+static void Destroy();
+
+static void OpenConnection();
+static void CloseConnection();
+static void StartupConnection();
+static void ShutdownConnection();
+static void EventLoop();
+static void HandleExit();
+static void DoExit(int code);
+static void SendRestart();
+static void SendExit();
+
+static char *configPath = NULL;
+static char *displayString = NULL;
+
+/****************************************************************************
+ ****************************************************************************/
+int main(int argc, char *argv[]) {
+ char *temp;
+ int x;
+
+ StartDebug();
+
+ temp = getenv("HOME");
+ if(temp) {
+ configPath = Allocate(strlen(temp) + strlen(CONFIG_FILE) + 1);
+ strcpy(configPath, temp);
+ strcat(configPath, CONFIG_FILE);
+ } else {
+ configPath = CopyString(CONFIG_FILE);
+ }
+
+ for(x = 1; x < argc; x++) {
+ if(!strcmp(argv[x], "-v")) {
+ DisplayAbout();
+ DoExit(0);
+ } else if(!strcmp(argv[x], "-h")) {
+ DisplayHelp();
+ DoExit(0);
+ } else if(!strcmp(argv[x], "-p")) {
+ Initialize();
+ ParseConfig(configPath);
+ DoExit(0);
+ } else if(!strcmp(argv[x], "-restart")) {
+ SendRestart();
+ DoExit(0);
+ } else if(!strcmp(argv[x], "-exit")) {
+ SendExit();
+ DoExit(0);
+ } else if(!strcmp(argv[x], "-display") && x + 1 < argc) {
+ displayString = argv[++x];
+ } else {
+ DisplayUsage();
+ DoExit(1);
+ }
+ }
+
+ StartupConnection();
+ do {
+
+ isRestarting = shouldRestart;
+ shouldExit = 0;
+ shouldRestart = 0;
+
+ Initialize();
+
+ ParseConfig(configPath);
+
+ Startup();
+
+ EventLoop();
+
+ Shutdown();
+
+ Destroy();
+
+ } while(shouldRestart);
+ ShutdownConnection();
+
+ if(exitCommand) {
+ execl(SHELL_NAME, SHELL_NAME, "-c", exitCommand, NULL);
+ Warning("exec failed: (%s) %s", SHELL_NAME, exitCommand);
+ DoExit(1);
+ } else {
+ DoExit(0);
+ }
+
+ /* Control shoud never get here. */
+ return -1;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DoExit(int code) {
+ Destroy();
+
+ if(configPath) {
+ Release(configPath);
+ configPath = NULL;
+ }
+ if(exitCommand) {
+ Release(exitCommand);
+ exitCommand = NULL;
+ }
+
+ StopDebug();
+ exit(code);
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void EventLoop() {
+ XEvent event;
+
+ while(!shouldExit) {
+ WaitForEvent(&event);
+ ProcessEvent(&event);
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void OpenConnection() {
+
+ display = JXOpenDisplay(displayString);
+ if(!display) {
+ if(displayString) {
+ printf("error: could not open display %s\n", displayString);
+ } else {
+ printf("error: could not open display\n");
+ }
+ DoExit(1);
+ }
+
+ rootScreen = DefaultScreen(display);
+ rootWindow = RootWindow(display, rootScreen);
+ rootWidth = DisplayWidth(display, rootScreen);
+ rootHeight = DisplayHeight(display, rootScreen);
+ rootDepth = DefaultDepth(display, rootScreen);
+ rootColormap = DefaultColormap(display, rootScreen);
+ rootVisual = DefaultVisual(display, rootScreen);
+ rootGC = DefaultGC(display, rootScreen);
+ colormapCount = MaxCmapsOfScreen(ScreenOfDisplay(display, rootScreen));
+
+ XSetGraphicsExposures(display, rootGC, False);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupConnection() {
+ XSetWindowAttributes attr;
+ int temp;
+
+ initializing = 1;
+ OpenConnection();
+
+#if 0
+ XSynchronize(display, True);
+#endif
+
+ JXSetErrorHandler(ErrorHandler);
+
+ clientContext = XUniqueContext();
+ frameContext = XUniqueContext();
+
+ attr.event_mask
+ = SubstructureRedirectMask
+ | SubstructureNotifyMask
+ | PropertyChangeMask
+ | ColormapChangeMask
+ | ButtonPressMask
+ | ButtonReleaseMask
+ | PointerMotionMask | PointerMotionHintMask;
+ JXChangeWindowAttributes(display, rootWindow, CWEventMask, &attr);
+
+ signal(SIGTERM, HandleExit);
+ signal(SIGINT, HandleExit);
+ signal(SIGHUP, HandleExit);
+
+#ifdef USE_SHAPE
+ haveShape = JXShapeQueryExtension(display, &shapeEvent, &temp);
+ if (haveShape) {
+ Debug("shape extension enabled");
+ } else {
+ Debug("shape extension disabled");
+ }
+#endif
+
+ initializing = 0;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void CloseConnection() {
+ JXFlush(display);
+ JXCloseDisplay(display);
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownConnection() {
+ CloseConnection();
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void HandleExit() {
+ signal(SIGTERM, HandleExit);
+ signal(SIGINT, HandleExit);
+ signal(SIGHUP, HandleExit);
+ shouldExit = 1;
+}
+
+/****************************************************************************
+ * This is called before the X connection is opened.
+ ****************************************************************************/
+void Initialize() {
+ InitializeBorders();
+ InitializeClients();
+ InitializeClock();
+ InitializeColors();
+ InitializeCommands();
+ InitializeCursors();
+ InitializeDesktops();
+ #ifndef DISABLE_CONFIRM
+ InitializeDialogs();
+ #endif
+ InitializeDock();
+ InitializeFonts();
+ InitializeGroups();
+ InitializeHints();
+ InitializeIcons();
+ InitializeKeys();
+ InitializeOutline();
+ InitializePager();
+ InitializePlacement();
+ InitializePopup();
+ InitializeRootMenu();
+ InitializeScreens();
+ InitializeSwallow();
+ InitializeTaskBar();
+ InitializeThemes();
+ InitializeTray();
+ InitializeTrayButtons();
+}
+
+/****************************************************************************
+ * This is called after the X connection is opened.
+ ****************************************************************************/
+void Startup() {
+
+ /* This order is important. */
+
+ StartupCommands();
+
+ /* First we grab the server to prevent clients from changing things
+ * while we're still loading. */
+ JXGrabServer(display);
+
+ StartupScreens();
+
+ StartupGroups();
+ StartupColors();
+ StartupIcons();
+ StartupFonts();
+ StartupCursors();
+ StartupOutline();
+
+ StartupThemes();
+
+ StartupPager();
+ StartupClock();
+ StartupTaskBar();
+ StartupTrayButtons();
+ StartupDock();
+ StartupTray();
+ StartupKeys();
+ StartupDesktops();
+ StartupHints();
+ StartupBorders();
+ StartupPlacement();
+ StartupClients();
+
+ #ifndef DISABLE_CONFIRM
+ StartupDialogs();
+ #endif
+ StartupPopup();
+
+ StartupRootMenu();
+
+ SetDefaultCursor(rootWindow);
+ ReadCurrentDesktop();
+ JXFlush(display);
+
+ RestackClients();
+
+ /* Allow clients to do their thing. */
+ JXSync(display, True);
+ JXUngrabServer(display);
+
+ StartupSwallow();
+
+ /* Send expose events. */
+ ExposeCurrentDesktop();
+
+}
+
+/****************************************************************************
+ * This is called before the X connection is closed.
+ ****************************************************************************/
+void Shutdown() {
+
+ /* This order is important. */
+
+ ShutdownSwallow();
+
+ ShutdownOutline();
+ #ifndef DISABLE_CONFIRM
+ ShutdownDialogs();
+ #endif
+ ShutdownPopup();
+ ShutdownKeys();
+ ShutdownPager();
+ ShutdownRootMenu();
+ ShutdownDock();
+ ShutdownTray();
+ ShutdownTrayButtons();
+ ShutdownTaskBar();
+ ShutdownClock();
+ ShutdownBorders();
+ ShutdownClients();
+ ShutdownThemes();
+ ShutdownIcons();
+ ShutdownCursors();
+ ShutdownFonts();
+ ShutdownColors();
+ ShutdownGroups();
+ ShutdownDesktops();
+
+ ShutdownPlacement();
+ ShutdownHints();
+ ShutdownScreens();
+
+ ShutdownCommands();
+
+}
+
+/****************************************************************************
+ * This is called after the X connection is closed.
+ * Note that it is possible for this to be called more than once.
+ ****************************************************************************/
+void Destroy() {
+ DestroyBorders();
+ DestroyClients();
+ DestroyClock();
+ DestroyColors();
+ DestroyCommands();
+ DestroyCursors();
+ DestroyDesktops();
+ #ifndef DISABLE_CONFIRM
+ DestroyDialogs();
+ #endif
+ DestroyDock();
+ DestroyFonts();
+ DestroyGroups();
+ DestroyHints();
+ DestroyIcons();
+ DestroyKeys();
+ DestroyOutline();
+ DestroyPager();
+ DestroyPlacement();
+ DestroyPopup();
+ DestroyRootMenu();
+ DestroyScreens();
+ DestroySwallow();
+ DestroyTaskBar();
+ DestroyThemes();
+ DestroyTray();
+ DestroyTrayButtons();
+}
+
+/****************************************************************************
+ * Send _JWM_RESTART to the root window.
+ ****************************************************************************/
+void SendRestart() {
+
+ XEvent event;
+
+ OpenConnection();
+
+ memset(&event, 0, sizeof(event));
+ event.xclient.type = ClientMessage;
+ event.xclient.window = rootWindow;
+ event.xclient.message_type = JXInternAtom(display, "_JWM_RESTART", False);
+ event.xclient.format = 32;
+
+ JXSendEvent(display, rootWindow, False, SubstructureRedirectMask, &event);
+
+ CloseConnection();
+
+}
+
+/****************************************************************************
+ * Send _JWM_EXIT to the root window.
+ ****************************************************************************/
+void SendExit() {
+
+ XEvent event;
+
+ OpenConnection();
+
+ memset(&event, 0, sizeof(event));
+ event.xclient.type = ClientMessage;
+ event.xclient.window = rootWindow;
+ event.xclient.message_type = JXInternAtom(display, "_JWM_EXIT", False);
+ event.xclient.format = 32;
+
+ JXSendEvent(display, rootWindow, False, SubstructureRedirectMask, &event);
+
+ CloseConnection();
+}
+
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..2216de0
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,56 @@
+/**
+ * @file main.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the main functions.
+ *
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+typedef enum {
+ FOCUS_SLOPPY = 0,
+ FOCUS_CLICK = 1
+} FocusModelType;
+
+extern Display *display;
+extern Window rootWindow;
+extern int rootWidth, rootHeight;
+extern int rootDepth;
+extern int rootScreen;
+extern Colormap rootColormap;
+extern Visual *rootVisual;
+extern GC rootGC;
+extern int colormapCount;
+
+extern char *exitCommand;
+
+extern unsigned int desktopCount;
+extern unsigned int currentDesktop;
+
+extern int shouldExit;
+extern int shouldRestart;
+extern int isRestarting;
+
+extern int initializing;
+
+extern int borderWidth;
+extern int titleHeight;
+
+extern unsigned int doubleClickSpeed;
+extern unsigned int doubleClickDelta;
+
+extern FocusModelType focusModel;
+
+extern XContext clientContext;
+extern XContext frameContext;
+
+#ifdef USE_SHAPE
+extern int haveShape;
+extern int shapeEvent;
+#endif
+
+#endif
+
diff --git a/src/match.c b/src/match.c
new file mode 100644
index 0000000..95edd39
--- /dev/null
+++ b/src/match.c
@@ -0,0 +1,80 @@
+/****************************************************************************
+ * Expression matching.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "match.h"
+
+typedef struct MatchStateType {
+ const char *pattern;
+ const char *expression;
+ int patternOffset;
+ int expressionOffset;
+ int expressionLength;
+} MatchStateType;
+
+static int DoMatch(MatchStateType state);
+
+/****************************************************************************
+ ****************************************************************************/
+int Match(const char *pattern, const char *expression) {
+
+ MatchStateType state;
+
+ if(!pattern && !expression) {
+ return 1;
+ } else if(!pattern || !expression) {
+ return 0;
+ }
+
+ state.pattern = pattern;
+ state.expression = expression;
+ state.patternOffset = 0;
+ state.expressionOffset = 0;
+ state.expressionLength = strlen(expression);
+
+ return DoMatch(state);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int DoMatch(MatchStateType state) {
+
+ char p, e;
+
+ for(;;) {
+ p = state.pattern[state.patternOffset];
+ e = state.expression[state.expressionOffset];
+
+ if(p == 0 && e == 0) {
+ return 1;
+ } else if(p == 0 || e == 0) {
+ return 0;
+ }
+
+ switch(p) {
+ case '*':
+ ++state.patternOffset;
+ while(state.expressionOffset < state.expressionLength) {
+ if(DoMatch(state)) {
+ return 1;
+ }
+ ++state.expressionOffset;
+ }
+ return 0;
+ default:
+ if(p == e) {
+ ++state.patternOffset;
+ ++state.expressionOffset;
+ break;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+}
+
+
diff --git a/src/match.h b/src/match.h
new file mode 100644
index 0000000..2cdb2e2
--- /dev/null
+++ b/src/match.h
@@ -0,0 +1,21 @@
+/**
+ * @file match.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Expression matching.
+ *
+ */
+
+#ifndef MATCH_H
+#define MATCH_H
+
+/** Check if an expression matches a pattern.
+ * @param pattern The pattern to match against.
+ * @param expression The expression to check.
+ * @return 1 if there is a match, 0 otherwise.
+ */
+int Match(const char *pattern, const char *expression);
+
+#endif
+
diff --git a/src/menu.c b/src/menu.c
new file mode 100644
index 0000000..9f6b80c
--- /dev/null
+++ b/src/menu.c
@@ -0,0 +1,799 @@
+/***************************************************************************
+ * Menu functions display and handling functions.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "menu.h"
+#include "font.h"
+#include "client.h"
+#include "color.h"
+#include "icon.h"
+#include "image.h"
+#include "main.h"
+#include "cursor.h"
+#include "key.h"
+#include "button.h"
+#include "event.h"
+
+#define BASE_ICON_OFFSET 3
+
+typedef enum {
+ MENU_NOSELECTION = 0,
+ MENU_LEAVE = 1,
+ MENU_SUBSELECT = 2
+} MenuSelectionType;
+
+/* Submenu arrow, 4 x 7 pixels */
+static char menu_bitmap[] = {
+ 0x01, 0x03, 0x07, 0x0F, 0x07, 0x03, 0x01
+};
+
+static int ShowSubmenu(Menu *menu, Menu *parent, int x, int y);
+
+static void CreateMenu(Menu *menu, int x, int y);
+static void HideMenu(Menu *menu);
+static void DrawMenu(Menu *menu);
+static void RedrawMenuTree(Menu *menu);
+
+static int MenuLoop(Menu *menu);
+static MenuSelectionType UpdateMotion(Menu *menu, XEvent *event);
+
+static void UpdateMenu(Menu *menu);
+static void DrawMenuItem(Menu *menu, MenuItem *item, int index);
+static MenuItem *GetMenuItem(Menu *menu, int index);
+static int GetNextMenuIndex(Menu *menu);
+static int GetPreviousMenuIndex(Menu *menu);
+static int GetMenuIndex(Menu *menu, int index);
+static void SetPosition(Menu *tp, int index);
+
+static MenuAction *menuAction = NULL;
+
+int menuShown = 0;
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeMenu(Menu *menu) {
+
+ MenuItem *np;
+ int index, temp;
+ int hasSubmenu;
+ int userHeight;
+
+ menu->textOffset = 0;
+ menu->itemCount = 0;
+
+ /* Compute the max size needed */
+ userHeight = menu->itemHeight;
+ if(userHeight < 0) {
+ userHeight = 0;
+ }
+ menu->itemHeight = GetStringHeight(FONT_MENU);
+ for(np = menu->items; np; np = np->next) {
+ if(np->iconName) {
+ np->icon = LoadNamedIcon(np->iconName);
+ if(np->icon) {
+ if(userHeight == 0) {
+ if(menu->itemHeight < (int)np->icon->image->height) {
+ menu->itemHeight = np->icon->image->height;
+ }
+ if(menu->textOffset < (int)np->icon->image->width + 4) {
+ menu->textOffset = np->icon->image->width + 4;
+ }
+ }
+ }
+ } else {
+ np->icon = NULL;
+ }
+ ++menu->itemCount;
+ }
+ menu->itemHeight += BASE_ICON_OFFSET * 2;
+
+ if(userHeight) {
+ menu->itemHeight = userHeight + BASE_ICON_OFFSET * 2;
+ menu->textOffset = menu->itemHeight + BASE_ICON_OFFSET * 2;
+ }
+
+ menu->width = 5;
+ menu->parent = NULL;
+ menu->parentOffset = 0;
+
+ menu->height = 1;
+ if(menu->label) {
+ menu->height += menu->itemHeight;
+ }
+
+ /* Nothing else to do if there is nothing in the menu. */
+ if(menu->itemCount == 0) {
+ return;
+ }
+
+ menu->offsets = Allocate(sizeof(int) * menu->itemCount);
+
+ hasSubmenu = 0;
+ index = 0;
+ for(np = menu->items; np; np = np->next) {
+ menu->offsets[index++] = menu->height;
+ if(np->type == MENU_ITEM_SEPARATOR) {
+ menu->height += 5;
+ } else {
+ menu->height += menu->itemHeight;
+ }
+ if(np->name) {
+ temp = GetStringWidth(FONT_MENU, np->name);
+ if(temp > menu->width) {
+ menu->width = temp;
+ }
+ }
+ if(np->submenu) {
+ hasSubmenu = 7;
+ InitializeMenu(np->submenu);
+ }
+ }
+ menu->height += 2;
+ menu->width += 15 + hasSubmenu + menu->textOffset;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShowMenu(Menu *menu, RunMenuCommandType runner, int x, int y) {
+
+ int mouseStatus, keyboardStatus;
+
+ mouseStatus = GrabMouseForMenu();
+ keyboardStatus = JXGrabKeyboard(display, rootWindow, False,
+ GrabModeAsync, GrabModeAsync, CurrentTime);
+ if(!mouseStatus || keyboardStatus != GrabSuccess) {
+ return;
+ }
+
+ ShowSubmenu(menu, NULL, x, y);
+
+ JXUngrabKeyboard(display, CurrentTime);
+ JXUngrabPointer(display, CurrentTime);
+ RefocusClient();
+
+ if(menuAction) {
+ (runner)(menuAction);
+ menuAction = NULL;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyMenu(Menu *menu) {
+ MenuItem *np;
+
+ if(menu) {
+ while(menu->items) {
+ np = menu->items->next;
+ if(menu->items->name) {
+ Release(menu->items->name);
+ }
+ switch(menu->items->action.type) {
+ case MA_EXECUTE:
+ case MA_EXIT:
+ if(menu->items->action.data.str) {
+ Release(menu->items->action.data.str);
+ }
+ break;
+ default:
+ break;
+ }
+ if(menu->items->iconName) {
+ Release(menu->items->iconName);
+ }
+ if(menu->items->submenu) {
+ DestroyMenu(menu->items->submenu);
+ }
+ Release(menu->items);
+ menu->items = np;
+ }
+ if(menu->label) {
+ Release(menu->label);
+ }
+ if(menu->offsets) {
+ Release(menu->offsets);
+ }
+ Release(menu);
+ menu = NULL;
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ShowSubmenu(Menu *menu, Menu *parent, int x, int y) {
+ int status;
+
+ menu->parent = parent;
+ CreateMenu(menu, x, y);
+
+ ++menuShown;
+ status = MenuLoop(menu);
+ --menuShown;
+
+ HideMenu(menu);
+
+ return status;
+}
+
+/***************************************************************************
+ * Returns 0 if no selection was made or 1 if a selection was made.
+ ***************************************************************************/
+int MenuLoop(Menu *menu) {
+
+ XEvent event;
+ MenuItem *ip;
+ int count;
+ int hadMotion;
+ int pressx, pressy;
+
+ hadMotion = 0;
+
+ GetMousePosition(&pressx, &pressy);
+
+ for(;;) {
+
+ WaitForEvent(&event);
+
+ switch(event.type) {
+ case Expose:
+ RedrawMenuTree(menu);
+ break;
+
+ case ButtonPress:
+
+ pressx = -100;
+ pressy = -100;
+
+ case KeyPress:
+ case MotionNotify:
+ hadMotion = 1;
+ switch(UpdateMotion(menu, &event)) {
+ case MENU_NOSELECTION: /* no selection */
+ break;
+ case MENU_LEAVE: /* mouse left the menu */
+ JXPutBackEvent(display, &event);
+ return 0;
+ case MENU_SUBSELECT: /* selection made */
+ return 1;
+ }
+ break;
+
+ case ButtonRelease:
+
+ if(event.xbutton.button == Button4) {
+ break;
+ }
+ if(event.xbutton.button == Button5) {
+ break;
+ }
+ if(!hadMotion) {
+ break;
+ }
+ if(abs(event.xbutton.x_root - pressx) < doubleClickDelta) {
+ if(abs(event.xbutton.y_root - pressy) < doubleClickDelta) {
+ break;
+ }
+ }
+
+ if(menu->currentIndex >= 0) {
+ count = 0;
+ for(ip = menu->items; ip; ip = ip->next) {
+ if(count == menu->currentIndex) {
+ menuAction = &ip->action;
+ break;
+ }
+ ++count;
+ }
+ }
+ return 1;
+ default:
+ break;
+ }
+
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void CreateMenu(Menu *menu, int x, int y) {
+
+ XSetWindowAttributes attr;
+ unsigned long attrMask;
+ int temp;
+
+ menu->lastIndex = -1;
+ menu->currentIndex = -1;
+
+ if(x + menu->width > rootWidth) {
+ if(menu->parent) {
+ x = menu->parent->x - menu->width;
+ } else {
+ x = rootWidth - menu->width;
+ }
+ }
+ temp = y;
+ if(y + menu->height > rootHeight) {
+ y = rootHeight - menu->height;
+ }
+ if(y < 0) {
+ y = 0;
+ }
+
+ menu->x = x;
+ menu->y = y;
+ menu->parentOffset = temp - y;
+
+ attrMask = 0;
+
+ attrMask |= CWEventMask;
+ attr.event_mask = ExposureMask;
+
+ attrMask |= CWBackPixel;
+ attr.background_pixel = colors[COLOR_MENU_BG];
+
+ attrMask |= CWSaveUnder;
+ attr.save_under = True;
+
+ menu->window = JXCreateWindow(display, rootWindow, x, y,
+ menu->width, menu->height, 0, CopyFromParent, InputOutput,
+ CopyFromParent, attrMask, &attr);
+
+ JXMapRaised(display, menu->window);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void HideMenu(Menu *menu) {
+
+ JXDestroyWindow(display, menu->window);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void RedrawMenuTree(Menu *menu) {
+
+ if(menu->parent) {
+ RedrawMenuTree(menu->parent);
+ }
+
+ DrawMenu(menu);
+ UpdateMenu(menu);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DrawMenu(Menu *menu) {
+
+ MenuItem *np;
+ int x;
+
+ if(menu->label) {
+ DrawMenuItem(menu, NULL, -1);
+ }
+
+ x = 0;
+ for(np = menu->items; np; np = np->next) {
+ DrawMenuItem(menu, np, x);
+ ++x;
+ }
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]);
+ JXDrawLine(display, menu->window, rootGC,
+ 0, 0, menu->width - 1, 0);
+ JXDrawLine(display, menu->window, rootGC,
+ 0, 1, menu->width - 2, 1);
+ JXDrawLine(display, menu->window, rootGC,
+ 0, 2, 0, menu->height - 1);
+ JXDrawLine(display, menu->window, rootGC,
+ 1, 2, 1, menu->height - 2);
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]);
+ JXDrawLine(display, menu->window, rootGC,
+ 1, menu->height - 1, menu->width - 1, menu->height - 1);
+ JXDrawLine(display, menu->window, rootGC,
+ 2, menu->height - 2, menu->width - 1, menu->height - 2);
+ JXDrawLine(display, menu->window, rootGC,
+ menu->width - 1, 1, menu->width - 1, menu->height - 3);
+ JXDrawLine(display, menu->window, rootGC,
+ menu->width - 2, 2, menu->width - 2, menu->height - 3);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+MenuSelectionType UpdateMotion(Menu *menu, XEvent *event) {
+
+ MenuItem *ip;
+ Menu *tp;
+ Window subwindow;
+ int x, y;
+
+ if(event->type == MotionNotify) {
+
+ SetMousePosition(event->xmotion.x_root, event->xmotion.y_root);
+ DiscardMotionEvents(event, menu->window);
+
+ x = event->xmotion.x_root - menu->x;
+ y = event->xmotion.y_root - menu->y;
+ subwindow = event->xmotion.subwindow;
+
+ } else if(event->type == ButtonPress) {
+
+ if(menu->currentIndex >= 0 || !menu->parent) {
+ tp = menu;
+ } else {
+ tp = menu->parent;
+ }
+
+ y = -1;
+ if(event->xbutton.button == Button4) {
+ y = GetPreviousMenuIndex(tp);
+ } else if(event->xbutton.button == Button5) {
+ y = GetNextMenuIndex(tp);
+ }
+
+ if(y >= 0) {
+ SetPosition(tp, y);
+ }
+
+ return MENU_NOSELECTION;
+
+ } else if(event->type == KeyPress) {
+
+ if(menu->currentIndex >= 0 || !menu->parent) {
+ tp = menu;
+ } else {
+ tp = menu->parent;
+ }
+
+ y = -1;
+ switch(GetKey(&event->xkey) & 0xFF) {
+ case KEY_UP:
+ y = GetPreviousMenuIndex(tp);
+ break;
+ case KEY_DOWN:
+ y = GetNextMenuIndex(tp);
+ break;
+ case KEY_RIGHT:
+ tp = menu;
+ y = 0;
+ break;
+ case KEY_LEFT:
+ if(tp->parent) {
+ tp = tp->parent;
+ if(tp->currentIndex >= 0) {
+ y = tp->currentIndex;
+ } else {
+ y = 0;
+ }
+ }
+ break;
+ case KEY_ESC:
+ return MENU_SUBSELECT;
+ case KEY_ENTER:
+ if(tp->currentIndex >= 0) {
+ x = 0;
+ for(ip = tp->items; ip; ip = ip->next) {
+ if(x == tp->currentIndex) {
+ menuAction = &ip->action;
+ break;
+ }
+ ++x;
+ }
+ }
+ return MENU_SUBSELECT;
+ default:
+ break;
+ }
+
+ if(y >= 0) {
+ SetPosition(tp, y);
+ }
+
+ return MENU_NOSELECTION;
+
+ } else {
+ Debug("invalid event type in menu.c:UpdateMotion");
+ return MENU_SUBSELECT;
+ }
+
+ /* Update the selection on the current menu */
+ if(x > 0 && y > 0 && x < menu->width && y < menu->height) {
+ menu->currentIndex = GetMenuIndex(menu, y);
+ } else if(menu->parent && subwindow != menu->parent->window) {
+
+ /* Leave if over a menu window. */
+ for(tp = menu->parent->parent; tp; tp = tp->parent) {
+ if(tp->window == subwindow) {
+ return MENU_LEAVE;
+ }
+ }
+ menu->currentIndex = -1;
+
+ } else {
+
+ /* Leave if over the parent, but not on this selection. */
+ tp = menu->parent;
+ if(tp && subwindow == tp->window) {
+ if(y < menu->parentOffset
+ || y > tp->itemHeight + menu->parentOffset) {
+ return MENU_LEAVE;
+ }
+ }
+
+ menu->currentIndex = -1;
+
+ }
+
+ /* Move the menu if needed. */
+ if(menu->height > rootHeight && menu->currentIndex >= 0) {
+
+ /* If near the top, shift down. */
+ if(y + menu->y <= 0) {
+ if(menu->currentIndex > 0) {
+ --menu->currentIndex;
+ SetPosition(menu, menu->currentIndex);
+ }
+ }
+
+ /* If near the bottom, shift up. */
+ if(y + menu->y + menu->itemHeight / 2 >= rootHeight) {
+ if(menu->currentIndex + 1 < menu->itemCount) {
+ ++menu->currentIndex;
+ SetPosition(menu, menu->currentIndex);
+ }
+ }
+
+ }
+
+ if(menu->lastIndex != menu->currentIndex) {
+ UpdateMenu(menu);
+ menu->lastIndex = menu->currentIndex;
+ }
+
+ /* If the selected item is a submenu, show it. */
+ ip = GetMenuItem(menu, menu->currentIndex);
+ if(ip && ip->submenu) {
+ if(ShowSubmenu(ip->submenu, menu, menu->x + menu->width,
+ menu->y + menu->offsets[menu->currentIndex])) {
+
+ /* Item selected; destroy the menu tree. */
+ return MENU_SUBSELECT;
+
+ } else {
+
+ /* No selection made. */
+ UpdateMenu(menu);
+
+ }
+ }
+
+ return MENU_NOSELECTION;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void UpdateMenu(Menu *menu) {
+
+ ButtonNode button;
+ Pixmap pixmap;
+ MenuItem *ip;
+
+ /* Clear the old selection. */
+ ip = GetMenuItem(menu, menu->lastIndex);
+ DrawMenuItem(menu, ip, menu->lastIndex);
+
+ /* Highlight the new selection. */
+ ip = GetMenuItem(menu, menu->currentIndex);
+ if(ip) {
+
+ if(ip->type == MENU_ITEM_SEPARATOR) {
+ return;
+ }
+
+ ResetButton(&button, menu->window, rootGC);
+ button.type = BUTTON_MENU_ACTIVE;
+ button.font = FONT_MENU;
+ button.width = menu->width - 5;
+ button.height = menu->itemHeight - 2;
+ button.icon = ip->icon;
+ button.text = ip->name;
+ button.x = 2;
+ button.y = menu->offsets[menu->currentIndex] + 1;
+ DrawButton(&button);
+
+ if(ip->submenu) {
+ pixmap = JXCreatePixmapFromBitmapData(display, menu->window,
+ menu_bitmap, 4, 7, colors[COLOR_MENU_ACTIVE_FG],
+ colors[COLOR_MENU_ACTIVE_BG], rootDepth);
+ JXCopyArea(display, pixmap, menu->window, rootGC, 0, 0, 4, 7,
+ menu->width - 9,
+ menu->offsets[menu->currentIndex] + menu->itemHeight / 2 - 4);
+ JXFreePixmap(display, pixmap);
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DrawMenuItem(Menu *menu, MenuItem *item, int index) {
+
+ ButtonNode button;
+ Pixmap pixmap;
+
+ Assert(menu);
+
+ if(!item) {
+ if(index == -1 && menu->label) {
+ ResetButton(&button, menu->window, rootGC);
+ button.x = 2;
+ button.y = 2;
+ button.width = menu->width - 5;
+ button.height = menu->itemHeight - 2;
+ button.font = FONT_MENU;
+ button.type = BUTTON_LABEL;
+ button.text = menu->label;
+ button.alignment = ALIGN_CENTER;
+ DrawButton(&button);
+ }
+ return;
+ }
+
+ if(item->name) {
+
+ ResetButton(&button, menu->window, rootGC);
+ button.x = 2;
+ button.y = 1 + menu->offsets[index];
+ button.font = FONT_MENU;
+ button.type = BUTTON_LABEL;
+ button.width = menu->width - 5;
+ button.height = menu->itemHeight - 2;
+ button.text = item->name;
+ button.icon = item->icon;
+ DrawButton(&button);
+
+ } else if(item->type == MENU_ITEM_SEPARATOR) {
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]);
+ JXDrawLine(display, menu->window, rootGC, 4,
+ menu->offsets[index] + 2, menu->width - 6,
+ menu->offsets[index] + 2);
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]);
+ JXDrawLine(display, menu->window, rootGC, 4,
+ menu->offsets[index] + 3, menu->width - 6,
+ menu->offsets[index] + 3);
+
+ }
+
+ if(item->submenu) {
+
+ pixmap = JXCreatePixmapFromBitmapData(display, menu->window,
+ menu_bitmap, 4, 7, colors[COLOR_MENU_FG],
+ colors[COLOR_MENU_BG], rootDepth);
+ JXCopyArea(display, pixmap, menu->window, rootGC, 0, 0, 4, 7,
+ menu->width - 9, menu->offsets[index] + menu->itemHeight / 2 - 4);
+ JXFreePixmap(display, pixmap);
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int GetNextMenuIndex(Menu *menu) {
+
+ MenuItem *item;
+ int x;
+
+ for(x = menu->currentIndex + 1; x < menu->itemCount; x++) {
+ item = GetMenuItem(menu, x);
+ if(item->type != MENU_ITEM_SEPARATOR) {
+ return x;
+ }
+ }
+
+ return 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int GetPreviousMenuIndex(Menu *menu) {
+
+ MenuItem *item;
+ int x;
+
+ for(x = menu->currentIndex - 1; x >= 0; x--) {
+ item = GetMenuItem(menu, x);
+ if(item->type != MENU_ITEM_SEPARATOR) {
+ return x;
+ }
+ }
+
+ return menu->itemCount - 1;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int GetMenuIndex(Menu *menu, int y) {
+
+ int x;
+
+ if(y < menu->offsets[0]) {
+ return -1;
+ }
+ for(x = 0; x < menu->itemCount - 1; x++) {
+ if(y >= menu->offsets[x] && y < menu->offsets[x + 1]) {
+ return x;
+ }
+ }
+ return x;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+MenuItem *GetMenuItem(Menu *menu, int index) {
+
+ MenuItem *ip;
+
+ if(index >= 0) {
+ for(ip = menu->items; ip; ip = ip->next) {
+ if(!index) {
+ return ip;
+ }
+ --index;
+ }
+ } else {
+ ip = NULL;
+ }
+
+ return ip;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetPosition(Menu *tp, int index) {
+
+ int y;
+ int updated;
+
+ y = tp->offsets[index] + tp->itemHeight / 2;
+
+ if(tp->height > rootHeight) {
+
+ updated = 0;
+ while(y + tp->y < tp->itemHeight / 2) {
+ tp->y += tp->itemHeight;
+ updated = tp->itemHeight;
+ }
+ while(y + tp->y >= rootHeight) {
+ tp->y -= tp->itemHeight;
+ updated = -tp->itemHeight;
+ }
+ if(updated) {
+ JXMoveWindow(display, tp->window, tp->x, tp->y);
+ y += updated;
+ }
+
+ }
+
+ /* We need to do this twice so the event gets registered
+ * on the submenu if one exists. */
+ MoveMouse(tp->window, 6, y);
+ MoveMouse(tp->window, 6, y);
+
+}
+
+
diff --git a/src/menu.h b/src/menu.h
new file mode 100644
index 0000000..5d4ad94
--- /dev/null
+++ b/src/menu.h
@@ -0,0 +1,89 @@
+/**
+ * @file menu.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the menu functions.
+ *
+ */
+
+#ifndef MENU_H
+#define MENU_H
+
+typedef enum {
+ MA_NONE,
+ MA_EXECUTE,
+ MA_DESKTOP,
+ MA_SENDTO,
+ MA_LAYER,
+ MA_STICK,
+ MA_MAXIMIZE,
+ MA_MINIMIZE,
+ MA_RESTORE,
+ MA_SHADE,
+ MA_MOVE,
+ MA_RESIZE,
+ MA_KILL,
+ MA_CLOSE,
+ MA_EXIT,
+ MA_RESTART
+} MenuActionType;
+
+typedef struct MenuAction {
+ MenuActionType type;
+ union {
+ int i;
+ char *str;
+ } data;
+} MenuAction;
+
+typedef enum {
+ MENU_ITEM_NORMAL,
+ MENU_ITEM_SUBMENU,
+ MENU_ITEM_SEPARATOR
+} MenuItemType;
+
+typedef struct MenuItem {
+
+ MenuItemType type;
+ char *name;
+ MenuAction action;
+ char *iconName;
+ struct Menu *submenu;
+ struct MenuItem *next;
+
+ /* This field is handled by menu.c */
+ struct IconNode *icon;
+
+} MenuItem;
+
+typedef struct Menu {
+
+ /* These fields must be set before calling ShowMenu */
+ struct MenuItem *items;
+ char *label;
+ int itemHeight;
+
+ /* These fields are handled by menu.c */
+ Window window;
+ int x, y;
+ int width, height;
+ int currentIndex, lastIndex;
+ unsigned int itemCount;
+ int parentOffset;
+ int textOffset;
+ int *offsets;
+ struct Menu *parent;
+
+} Menu;
+
+typedef void (*RunMenuCommandType)(const MenuAction *action);
+
+void InitializeMenu(Menu *menu);
+void ShowMenu(Menu *menu, RunMenuCommandType runner, int x, int y);
+void DestroyMenu(Menu *menu);
+
+extern int menuShown;
+
+#endif
+
diff --git a/src/misc.c b/src/misc.c
new file mode 100644
index 0000000..5728323
--- /dev/null
+++ b/src/misc.c
@@ -0,0 +1,196 @@
+/**
+ * @file misc.c
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Miscellaneous functions and macros.
+ *
+ */
+
+#include "jwm.h"
+#include "misc.h"
+
+static int IsSpace(char ch);
+static int IsSymbolic(char ch);
+static char *GetSymbolName(const char *str);
+static void ReplaceSymbol(char **str, const char *name, const char *value);
+
+/** Determine if a character is a space character. */
+int IsSpace(char ch) {
+ switch(ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/** Determine if a character is a valid for a shell variable. */
+int IsSymbolic(char ch) {
+
+ if(ch >= 'A' && ch <= 'Z') {
+ return 1;
+ } else if(ch >= 'a' && ch <= 'z') {
+ return 1;
+ } else if(ch >= '0' && ch <= '9') {
+ return 1;
+ } else if(ch == '_') {
+ return 1;
+ } else {
+ return 0;
+ }
+
+}
+
+/** Get the name of a shell variable (returns a copy). */
+char *GetSymbolName(const char *str) {
+
+ char *temp;
+ int stop;
+
+ if(*str == '$') {
+ temp = Allocate(2);
+ temp[0] = '$';
+ temp[1] = 0;
+ } else {
+ for(stop = 0; IsSymbolic(str[stop]); stop++);
+ temp = Allocate(stop + 1);
+ memcpy(temp, str, stop);
+ temp[stop] = 0;
+ }
+
+ return temp;
+
+}
+
+/** Replace "name" with "value" in str (reallocates if needed). */
+void ReplaceSymbol(char **str, const char *name, const char *value) {
+
+ char *temp;
+ int strLength;
+ int nameLength;
+ int valueLength;
+ int x;
+
+ Assert(str);
+ Assert(name);
+
+ strLength = strlen(*str);
+ nameLength = strlen(name) + 1;
+ if(value) {
+ valueLength = strlen(value);
+ } else {
+ valueLength = 0;
+ }
+
+ if(valueLength > nameLength) {
+ temp = Allocate(strLength - nameLength + valueLength + 1);
+ strcpy(temp, *str);
+ Release(*str);
+ *str = temp;
+ }
+
+ temp = strstr(*str, name);
+ Assert(temp);
+ --temp; /* Account for the "$" */
+
+ if(nameLength > valueLength) {
+
+ /* Move left */
+ for(x = 0; temp[x]; x++) {
+ temp[x] = temp[x + nameLength - valueLength];
+ }
+ temp[x] = temp[x + nameLength - valueLength];
+
+ } else if(nameLength < valueLength) {
+
+ /* Move right */
+ for(x = strlen(temp); x >= 0; x--) {
+ temp[x + valueLength - nameLength] = temp[x];
+ }
+
+ }
+
+
+ if(value) {
+ memcpy(temp, value, valueLength);
+ }
+
+}
+
+/** Perform shell-like macro path expansion. */
+void ExpandPath(char **path) {
+
+ char *name;
+ char *value;
+ int x;
+
+ Assert(path);
+
+ for(x = 0; (*path)[x]; x++) {
+
+ if((*path)[x] == '$') {
+ name = GetSymbolName(*path + x + 1);
+ value = getenv(name);
+ ReplaceSymbol(path, name, value);
+ Release(name);
+ if(value) {
+ x += strlen(value) - 1;
+ }
+ }
+
+ }
+
+}
+
+/** Trim leading and trailing whitespace from a string. */
+void Trim(char *str) {
+
+ int length;
+ int start;
+ int x;
+
+ Assert(str);
+
+ length = strlen(str);
+
+ /* Determine how much to cut off of the left. */
+ for(start = 0; IsSpace(str[start]); start++);
+
+ /* Trim the left. */
+ if(start > 0) {
+ length -= start;
+ for(x = 0; x < length + 1; x++) {
+ str[x] = str[x + start];
+ }
+ }
+
+ /* Trim the right. */
+ while(IsSpace(str[length - 1])) {
+ --length;
+ str[length] = 0;
+ }
+
+}
+
+/** Copy a string. */
+char *CopyString(const char *str) {
+
+ char *temp;
+ int len;
+
+ if(!str) {
+ return NULL;
+ }
+
+ len = strlen(str) + 1;
+ temp = Allocate(len);
+ memcpy(temp, str, len);
+
+ return temp;
+
+}
+
diff --git a/src/misc.h b/src/misc.h
new file mode 100644
index 0000000..c97a59e
--- /dev/null
+++ b/src/misc.h
@@ -0,0 +1,37 @@
+/**
+ * @file misc.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Miscellaneous functions and macros.
+ *
+ */
+
+#ifndef MISC_H
+#define MISC_H
+
+/** Return the minimum of two values. */
+#define Min( x, y ) ( (x) > (y) ? (y) : (x) )
+
+/** Return the maximum of two values. */
+#define Max( x, y ) ( (x) > (y) ? (x) : (y) )
+
+/** Perform shell-like macro path expansion.
+ * @param path The path to expand (possibly reallocated).
+ */
+void ExpandPath(char **path);
+
+/** Trim leading and trailing whitespace from a string.
+ * @param str The string to trim.
+ */
+void Trim(char *str);
+
+/** Copy a string.
+ * Note that NULL is accepted. When provided NULL, NULL will be returned.
+ * @param str The string to copy.
+ * @return A copy of the string.
+ */
+char *CopyString(const char *str);
+
+#endif
+
diff --git a/src/move.c b/src/move.c
new file mode 100644
index 0000000..882ac90
--- /dev/null
+++ b/src/move.c
@@ -0,0 +1,729 @@
+/****************************************************************************
+ * Functions to handle moving client windows.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "move.h"
+#include "client.h"
+#include "border.h"
+#include "outline.h"
+#include "error.h"
+#include "screen.h"
+#include "main.h"
+#include "cursor.h"
+#include "event.h"
+#include "pager.h"
+#include "key.h"
+#include "tray.h"
+#include "status.h"
+
+typedef struct {
+ int valid;
+ int left, right;
+ int top, bottom;
+} RectangleType;
+
+static int shouldStopMove;
+static SnapModeType snapMode = SNAP_BORDER;
+static int snapDistance = DEFAULT_SNAP_DISTANCE;
+
+static MoveModeType moveMode = MOVE_OPAQUE;
+
+static void StopMove(ClientNode *np, int doMove, int oldx, int oldy);
+static void MoveController(int wasDestroyed);
+
+static void DoSnap(ClientNode *np, int north, int west);
+static void DoSnapScreen(ClientNode *np, int north, int west);
+static void DoSnapBorder(ClientNode *np, int north, int west);
+static int ShouldSnap(const ClientNode *np);
+static void GetClientRectangle(const ClientNode *np, RectangleType *r);
+
+static int CheckOverlapTopBottom(const RectangleType *a,
+ const RectangleType *b);
+static int CheckOverlapLeftRight(const RectangleType *a,
+ const RectangleType *b);
+
+static int CheckLeftValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *left);
+static int CheckRightValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *right);
+static int CheckTopValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *top);
+static int CheckBottomValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *bottom);
+
+/****************************************************************************
+ ****************************************************************************/
+void SetSnapMode(SnapModeType mode) {
+ snapMode = mode;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetMoveMode(MoveModeType mode) {
+ moveMode = mode;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetSnapDistance(const char *value) {
+ int temp;
+
+ Assert(value);
+
+ temp = atoi(value);
+ if(temp > MAX_SNAP_DISTANCE || temp < MIN_SNAP_DISTANCE) {
+ snapDistance = DEFAULT_SNAP_DISTANCE;
+ Warning("invalid snap distance specified: %d", temp);
+ } else {
+ snapDistance = temp;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetDefaultSnapDistance() {
+ snapDistance = DEFAULT_SNAP_DISTANCE;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void MoveController(int wasDestroyed) {
+
+ if(moveMode == MOVE_OUTLINE) {
+ ClearOutline();
+ }
+
+ JXUngrabPointer(display, CurrentTime);
+ JXUngrabKeyboard(display, CurrentTime);
+
+ DestroyMoveWindow();
+ shouldStopMove = 1;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int MoveClient(ClientNode *np, int startx, int starty) {
+
+ XEvent event;
+ int oldx, oldy;
+ int doMove;
+ int north, south, east, west;
+ int height;
+
+ Assert(np);
+
+ if(!(np->state.border & BORDER_MOVE)) {
+ return 0;
+ }
+
+ GrabMouseForMove();
+
+ np->controller = MoveController;
+ shouldStopMove = 0;
+
+ oldx = np->x;
+ oldy = np->y;
+
+ if(!(GetMouseMask() & (Button1Mask | Button2Mask))) {
+ StopMove(np, 0, oldx, oldy);
+ return 0;
+ }
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ startx -= west;
+ starty -= north;
+
+ doMove = 0;
+ for(;;) {
+
+ WaitForEvent(&event);
+
+ if(shouldStopMove) {
+ np->controller = NULL;
+ SetDefaultCursor(np->parent);
+ return doMove;
+ }
+
+ switch(event.type) {
+ case ButtonRelease:
+ if(event.xbutton.button == Button1
+ || event.xbutton.button == Button2) {
+ StopMove(np, doMove, oldx, oldy);
+ return doMove;
+ }
+ break;
+ case MotionNotify:
+
+ DiscardMotionEvents(&event, np->window);
+
+ np->x = event.xmotion.x_root - startx;
+ np->y = event.xmotion.y_root - starty;
+
+ DoSnap(np, north, west);
+
+ if(!doMove && (abs(np->x - oldx) > MOVE_DELTA
+ || abs(np->y - oldy) > MOVE_DELTA)) {
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ MaximizeClient(np);
+ startx = west + np->width / 2;
+ starty = north / 2;
+ MoveMouse(np->parent, startx, starty);
+ }
+
+ CreateMoveWindow(np);
+ doMove = 1;
+ }
+
+ if(doMove) {
+
+ if(moveMode == MOVE_OUTLINE) {
+ ClearOutline();
+ height = north + south;
+ if(!(np->state.status & STAT_SHADED)) {
+ height += np->height;
+ }
+ DrawOutline(np->x - west, np->y - north,
+ np->width + west + east, height);
+ } else {
+ JXMoveWindow(display, np->parent, np->x - west,
+ np->y - north);
+ SendConfigureEvent(np);
+ }
+ UpdateMoveWindow(np);
+ UpdatePager();
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int MoveClientKeyboard(ClientNode *np) {
+
+ XEvent event;
+ int oldx, oldy;
+ int moved;
+ int height;
+ int north, south, east, west;
+
+ Assert(np);
+
+ if(!(np->state.border & BORDER_MOVE)) {
+ return 0;
+ }
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ MaximizeClient(np);
+ }
+
+ GrabMouseForMove();
+ if(JXGrabKeyboard(display, np->window, True, GrabModeAsync,
+ GrabModeAsync, CurrentTime) != GrabSuccess) {
+ Debug("could not grab keyboard for client move");
+ return 0;
+ }
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ oldx = np->x;
+ oldy = np->y;
+
+ np->controller = MoveController;
+ shouldStopMove = 0;
+
+ CreateMoveWindow(np);
+ UpdateMoveWindow(np);
+
+ MoveMouse(rootWindow, np->x, np->y);
+ DiscardMotionEvents(&event, np->window);
+
+ if(np->state.status & STAT_SHADED) {
+ height = 0;
+ } else {
+ height = np->height;
+ }
+
+ for(;;) {
+
+ WaitForEvent(&event);
+
+ if(shouldStopMove) {
+ np->controller = NULL;
+ SetDefaultCursor(np->parent);
+ return 1;
+ }
+
+ moved = 0;
+
+ if(event.type == KeyPress) {
+
+ while(JXCheckTypedWindowEvent(display, np->window, KeyPress, &event));
+
+ switch(GetKey(&event.xkey) & 0xFF) {
+ case KEY_UP:
+ if(np->y + height > 0) {
+ np->y -= 10;
+ }
+ break;
+ case KEY_DOWN:
+ if(np->y < rootHeight) {
+ np->y += 10;
+ }
+ break;
+ case KEY_RIGHT:
+ if(np->x < rootWidth) {
+ np->x += 10;
+ }
+ break;
+ case KEY_LEFT:
+ if(np->x + np->width > 0) {
+ np->x -= 10;
+ }
+ break;
+ default:
+ StopMove(np, 1, oldx, oldy);
+ return 1;
+ }
+
+ MoveMouse(rootWindow, np->x, np->y);
+ JXCheckTypedWindowEvent(display, np->window, MotionNotify, &event);
+
+ moved = 1;
+
+ } else if(event.type == MotionNotify) {
+
+ while(JXCheckTypedWindowEvent(display, np->window,
+ MotionNotify, &event));
+
+ np->x = event.xmotion.x;
+ np->y = event.xmotion.y;
+
+ moved = 1;
+
+ } else if(event.type == ButtonRelease) {
+
+ StopMove(np, 1, oldx, oldy);
+ return 1;
+
+ }
+
+ if(moved) {
+
+ if(moveMode == MOVE_OUTLINE) {
+ ClearOutline();
+ DrawOutline(np->x - west, np->y - west,
+ np->width + west + east, height + north + west);
+ } else {
+ JXMoveWindow(display, np->parent, np->x - west, np->y - north);
+ SendConfigureEvent(np);
+ }
+
+ UpdateMoveWindow(np);
+ UpdatePager();
+
+ }
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StopMove(ClientNode *np, int doMove, int oldx, int oldy) {
+
+ int north, south, east, west;
+
+ Assert(np);
+ Assert(np->controller);
+
+ (np->controller)(0);
+
+ np->controller = NULL;
+
+ SetDefaultCursor(np->parent);
+
+ if(!doMove) {
+ np->x = oldx;
+ np->y = oldy;
+ return;
+ }
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ JXMoveWindow(display, np->parent, np->x - west, np->y - north);
+ SendConfigureEvent(np);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DoSnap(ClientNode *np, int north, int west) {
+ switch(snapMode) {
+ case SNAP_BORDER:
+ DoSnapBorder(np, north, west);
+ DoSnapScreen(np, north, west);
+ break;
+ case SNAP_SCREEN:
+ DoSnapScreen(np, north, west);
+ break;
+ default:
+ break;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DoSnapScreen(ClientNode *np, int north, int west) {
+
+ RectangleType client;
+ int screen;
+ const ScreenType *sp;
+ int screenCount;
+
+ GetClientRectangle(np, &client);
+
+ screenCount = GetScreenCount();
+ for(screen = 0; screen < screenCount; screen++) {
+
+ sp = GetScreen(screen);
+
+ if(abs(client.right - sp->width - sp->x) <= snapDistance) {
+ np->x = sp->x + sp->width - west - np->width;
+ }
+ if(abs(client.left - sp->x) <= snapDistance) {
+ np->x = sp->x + west;
+ }
+ if(abs(client.bottom - sp->height - sp->y) <= snapDistance) {
+ np->y = sp->y + sp->height - west;
+ if(!(np->state.status & STAT_SHADED)) {
+ np->y -= np->height;
+ }
+ }
+ if(abs(client.top - sp->y) <= snapDistance) {
+ np->y = north + sp->y;
+ }
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DoSnapBorder(ClientNode *np, int north, int west) {
+
+ const ClientNode *tp;
+ const TrayType *tray;
+ RectangleType client, other;
+ RectangleType left = { 0 };
+ RectangleType right = { 0 };
+ RectangleType top = { 0 };
+ RectangleType bottom = { 0 };
+ int layer;
+
+ GetClientRectangle(np, &client);
+
+ other.valid = 1;
+
+ /* Work from the bottom of the window stack to the top. */
+ for(layer = 0; layer < LAYER_COUNT; layer++) {
+
+ /* Check tray windows. */
+ for(tray = GetTrays(); tray; tray = tray->next) {
+
+ if(tray->hidden) {
+ continue;
+ }
+
+ other.left = tray->x;
+ other.right = tray->x + tray->width;
+ other.top = tray->y;
+ other.bottom = tray->y + tray->height;
+
+ left.valid = CheckLeftValid(&client, &other, &left);
+ right.valid = CheckRightValid(&client, &other, &right);
+ top.valid = CheckTopValid(&client, &other, &top);
+ bottom.valid = CheckBottomValid(&client, &other, &bottom);
+
+ if(CheckOverlapTopBottom(&client, &other)) {
+ if(abs(client.left - other.right) <= snapDistance) {
+ left = other;
+ }
+ if(abs(client.right - other.left) <= snapDistance) {
+ right = other;
+ }
+ }
+ if(CheckOverlapLeftRight(&client, &other)) {
+ if(abs(client.top - other.bottom) <= snapDistance) {
+ top = other;
+ }
+ if(abs(client.bottom - other.top) <= snapDistance) {
+ bottom = other;
+ }
+ }
+
+ }
+
+ /* Check client windows. */
+ for(tp = nodeTail[layer]; tp; tp = tp->prev) {
+
+ if(tp == np || !ShouldSnap(tp)) {
+ continue;
+ }
+
+ GetClientRectangle(tp, &other);
+
+ /* Check if this border invalidates any previous value. */
+ left.valid = CheckLeftValid(&client, &other, &left);
+ right.valid = CheckRightValid(&client, &other, &right);
+ top.valid = CheckTopValid(&client, &other, &top);
+ bottom.valid = CheckBottomValid(&client, &other, &bottom);
+
+ /* Compute the new snap values. */
+ if(CheckOverlapTopBottom(&client, &other)) {
+ if(abs(client.left - other.right) <= snapDistance) {
+ left = other;
+ }
+ if(abs(client.right - other.left) <= snapDistance) {
+ right = other;
+ }
+ }
+ if(CheckOverlapLeftRight(&client, &other)) {
+ if(abs(client.top - other.bottom) <= snapDistance) {
+ top = other;
+ }
+ if(abs(client.bottom - other.top) <= snapDistance) {
+ bottom = other;
+ }
+ }
+
+ }
+
+ }
+
+ if(right.valid) {
+ np->x = right.left - np->width - west;
+ }
+ if(left.valid) {
+ np->x = left.right + west;
+ }
+ if(bottom.valid) {
+ np->y = bottom.top - west;
+ if(!(np->state.status & STAT_SHADED)) {
+ np->y -= np->height;
+ }
+ }
+ if(top.valid) {
+ np->y = top.bottom + north;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int ShouldSnap(const ClientNode *np) {
+ if(np->state.status & STAT_HIDDEN) {
+ return 0;
+ } else if(np->state.status & STAT_MINIMIZED) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void GetClientRectangle(const ClientNode *np, RectangleType *r) {
+
+ int border;
+
+ r->left = np->x;
+ r->right = np->x + np->width;
+ r->top = np->y;
+ if(np->state.status & STAT_SHADED) {
+ r->bottom = np->y;
+ } else {
+ r->bottom = np->y + np->height;
+ }
+
+ if(np->state.border & BORDER_OUTLINE) {
+ border = borderWidth;
+ r->left -= border;
+ r->right += border;
+ r->bottom += border;
+ } else {
+ border = 0;
+ }
+
+ if(np->state.border & BORDER_TITLE) {
+ r->top -= titleHeight + border;
+ } else {
+ r->top -= border;
+ }
+
+ r->valid = 1;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int CheckOverlapTopBottom(const RectangleType *a, const RectangleType *b) {
+ if(a->top >= b->bottom) {
+ return 0;
+ } else if(a->bottom <= b->top) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int CheckOverlapLeftRight(const RectangleType *a, const RectangleType *b) {
+ if(a->left >= b->right) {
+ return 0;
+ } else if(a->right <= b->left) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/****************************************************************************
+ * Check if the current left snap position is valid.
+ * client - The window being moved.
+ * other - A window higher in stacking order than previously check windows.
+ * left - The top/bottom of the current left snap window.
+ * Returns 1 if the current left snap position is still valid, otherwise 0.
+ ****************************************************************************/
+int CheckLeftValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *left) {
+
+ if(!left->valid) {
+ return 0;
+ }
+
+ if(left->right > other->right) {
+ return 1;
+ }
+
+ /* If left and client go higher than other then still valid. */
+ if(left->top < other->top && client->top < other->top) {
+ return 1;
+ }
+
+ /* If left and client go lower than other then still valid. */
+ if(left->bottom > other->bottom && client->bottom > other->bottom) {
+ return 1;
+ }
+
+ if(other->left >= left->right) {
+ return 1;
+ }
+
+ return 0;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int CheckRightValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *right) {
+
+ if(!right->valid) {
+ return 0;
+ }
+
+ if(right->left < other->left) {
+ return 1;
+ }
+
+ /* If right and client go higher than other then still valid. */
+ if(right->top < other->top && client->top < other->top) {
+ return 1;
+ }
+
+ /* If right and client go lower than other then still valid. */
+ if(right->bottom > other->bottom && client->bottom > other->bottom) {
+ return 1;
+ }
+
+ if(other->right <= right->left) {
+ return 1;
+ }
+
+ return 0;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int CheckTopValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *top) {
+
+ if(!top->valid) {
+ return 0;
+ }
+
+ if(top->bottom > other->bottom) {
+ return 1;
+ }
+
+ /* If top and client are to the left of other then still valid. */
+ if(top->left < other->left && client->left < other->left) {
+ return 1;
+ }
+
+ /* If top and client are to the right of other then still valid. */
+ if(top->right > other->right && client->right > other->right) {
+ return 1;
+ }
+
+ if(other->top >= top->bottom) {
+ return 1;
+ }
+
+ return 0;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int CheckBottomValid(const RectangleType *client,
+ const RectangleType *other, const RectangleType *bottom) {
+
+ if(!bottom->valid) {
+ return 0;
+ }
+
+ if(bottom->top < other->top) {
+ return 1;
+ }
+
+ /* If bottom and client are to the left of other then still valid. */
+ if(bottom->left < other->left && client->left < other->left) {
+ return 1;
+ }
+
+ /* If bottom and client are to the right of other then still valid. */
+ if(bottom->right > other->right && client->right > other->right) {
+ return 1;
+ }
+
+ if(other->bottom <= bottom->top) {
+ return 1;
+ }
+
+ return 0;
+
+}
+
diff --git a/src/move.h b/src/move.h
new file mode 100644
index 0000000..8a737ff
--- /dev/null
+++ b/src/move.h
@@ -0,0 +1,61 @@
+/**
+ * @file move.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for client window move functions.
+ *
+ */
+
+#ifndef MOVE_H
+#define MOVE_H
+
+struct ClientNode;
+
+/** Window snap modes. */
+typedef enum {
+ SNAP_NONE = 0, /**< Don't snap. */
+ SNAP_SCREEN = 1, /**< Snap to the edges of the screen. */
+ SNAP_BORDER = 2 /**< Snap to all borders. */
+} SnapModeType;
+
+/** Window move modes. */
+typedef enum {
+ MOVE_OPAQUE, /**< Show window contents while moving. */
+ MOVE_OUTLINE /**< Show an outline while moving. */
+} MoveModeType;
+
+/** Move a client window.
+ * @param np The client to move.
+ * @param startx The starting mouse x-coordinate (window relative).
+ * @param starty The starting mouse y-coordinate (window relative).
+ * @return 1 if the client moved, 0 otherwise.
+ */
+int MoveClient(struct ClientNode *np, int startx, int starty);
+
+/** Move a client window using the keyboard (mouse optional).
+ * @param np The client to move.
+ * @return 1 if the client moved, 0 otherwise.
+ */
+int MoveClientKeyboard(struct ClientNode *np);
+
+/** Set the snap mode to use.
+ * @param mode The snap mode to use.
+ */
+void SetSnapMode(SnapModeType mode);
+
+/** Set the snap distance to use.
+ * @param value A string representation of the distance to use.
+ */
+void SetSnapDistance(const char *value);
+
+/** Set the snap distance to the default. */
+void SetDefaultSnapDistance();
+
+/** Set the move mode to use.
+ * @param mode The move mode to use.
+ */
+void SetMoveMode(MoveModeType mode);
+
+#endif
+
diff --git a/src/outline.c b/src/outline.c
new file mode 100644
index 0000000..b42e70a
--- /dev/null
+++ b/src/outline.c
@@ -0,0 +1,73 @@
+/****************************************************************************
+ * Outlines for moving and resizing.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "outline.h"
+#include "main.h"
+
+static GC outlineGC;
+
+static int lastX, lastY;
+static int lastWidth, lastHeight;
+static int outlineDrawn;
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeOutline() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupOutline() {
+
+ XGCValues gcValues;
+
+ gcValues.function = GXinvert;
+ gcValues.subwindow_mode = IncludeInferiors;
+ gcValues.line_width = 2;
+ outlineGC = JXCreateGC(display, rootWindow,
+ GCFunction | GCSubwindowMode | GCLineWidth, &gcValues);
+ outlineDrawn = 0;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownOutline() {
+ JXFreeGC(display, outlineGC);
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyOutline() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DrawOutline(int x, int y, int width, int height) {
+ if(!outlineDrawn) {
+ JXSync(display, False);
+ JXGrabServer(display);
+ JXDrawRectangle(display, rootWindow, outlineGC, x, y, width, height);
+ lastX = x;
+ lastY = y;
+ lastWidth = width;
+ lastHeight = height;
+ outlineDrawn = 1;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ClearOutline() {
+ if(outlineDrawn) {
+ JXDrawRectangle(display, rootWindow, outlineGC,
+ lastX, lastY, lastWidth, lastHeight);
+ outlineDrawn = 0;
+ JXUngrabServer(display);
+ JXSync(display, False);
+ }
+}
+
diff --git a/src/outline.h b/src/outline.h
new file mode 100644
index 0000000..26a4a5e
--- /dev/null
+++ b/src/outline.h
@@ -0,0 +1,32 @@
+/**
+ * @file outline.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Outlines for moving and resizing client windows.
+ *
+ */
+
+#ifndef OUTLINE_H
+#define OUTLINE_H
+
+/*@{*/
+void InitializeOutline();
+void StartupOutline();
+void ShutdownOutline();
+void DestroyOutline();
+/*@}*/
+
+/** Draw an outline.
+ * @param x The x-coordinate.
+ * @param y The y-coordinate.
+ * @param width The width of the outline.
+ * @param height The height of the outline.
+ */
+void DrawOutline(int x, int y, int width, int height);
+
+/** Clear an outline. */
+void ClearOutline();
+
+#endif
+
diff --git a/src/pager.c b/src/pager.c
new file mode 100644
index 0000000..6c237bd
--- /dev/null
+++ b/src/pager.c
@@ -0,0 +1,331 @@
+/****************************************************************************
+ * Functions for displaying the pager.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "pager.h"
+#include "tray.h"
+#include "main.h"
+#include "desktop.h"
+#include "client.h"
+#include "color.h"
+
+typedef struct PagerType {
+
+ TrayComponentType *cp;
+
+ int deskWidth;
+ int deskHeight;
+ double scalex, scaley;
+ LayoutType layout;
+
+ Pixmap buffer;
+
+ struct PagerType *next;
+
+} PagerType;
+
+static PagerType *pagers;
+
+static void Create(TrayComponentType *cp);
+static void Destroy(TrayComponentType *cp);
+
+static void SetSize(TrayComponentType *cp, int width, int height);
+
+static void ProcessPagerButtonEvent(TrayComponentType *cp,
+ int x, int y, int mask);
+
+static void DrawPagerClient(const PagerType *pp, const ClientNode *np);
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializePager() {
+ pagers = NULL;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupPager() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownPager() {
+
+ PagerType *pp;
+
+ for(pp = pagers; pp; pp = pp->next) {
+ JXFreePixmap(display, pp->buffer);
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyPager() {
+
+ PagerType *pp;
+
+ while(pagers) {
+ pp = pagers->next;
+ Release(pagers);
+ pagers = pp;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+TrayComponentType *CreatePager() {
+
+ TrayComponentType *cp;
+ PagerType *pp;
+
+ pp = Allocate(sizeof(PagerType));
+ pp->next = pagers;
+ pagers = pp;
+
+ cp = CreateTrayComponent();
+ cp->object = pp;
+ pp->cp = cp;
+ cp->Create = Create;
+ cp->Destroy = Destroy;
+ cp->SetSize = SetSize;
+ cp->ProcessButtonEvent = ProcessPagerButtonEvent;
+
+ return cp;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void Create(TrayComponentType *cp) {
+
+ PagerType *pp;
+
+ Assert(cp);
+
+ pp = (PagerType*)cp->object;
+
+ Assert(pp);
+
+ Assert(cp->width > 0);
+ Assert(cp->height > 0);
+
+ cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width,
+ cp->height, rootDepth);
+ pp->buffer = cp->pixmap;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void Destroy(TrayComponentType *cp) {
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetSize(TrayComponentType *cp, int width, int height) {
+
+ PagerType *pp;
+
+ Assert(cp);
+
+ pp = (PagerType*)cp->object;
+
+ Assert(pp);
+
+ if(width) {
+
+ /* Vertical pager, compute height from width. */
+ cp->width = width;
+ pp->deskWidth = width;
+ pp->deskHeight = (cp->width * rootHeight) / rootWidth;
+ cp->height = (pp->deskHeight + 1) * desktopCount;
+ pp->layout = LAYOUT_VERTICAL;
+
+ } else if(height) {
+
+ /* Horizontal pager, compute width from height. */
+ cp->height = height;
+ pp->deskHeight = height;
+ pp->deskWidth = (cp->height * rootWidth) / rootHeight;
+ cp->width = (pp->deskWidth + 1) * desktopCount;
+ pp->layout = LAYOUT_HORIZONTAL;
+
+ } else {
+ Assert(0);
+ }
+
+ pp->scalex = (double)(pp->deskWidth - 2) / rootWidth;
+ pp->scaley = (double)(pp->deskHeight - 2) / rootHeight;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ProcessPagerButtonEvent(TrayComponentType *cp, int x, int y, int mask) {
+
+ PagerType *pp;
+
+ switch(mask) {
+ case Button1:
+ case Button2:
+ case Button3:
+ pp = (PagerType*)cp->object;
+ if(pp->layout == LAYOUT_HORIZONTAL) {
+ ChangeDesktop(x / (pp->deskWidth + 1));
+ } else {
+ ChangeDesktop(y / (pp->deskHeight + 1));
+ }
+ break;
+ case Button4:
+ PreviousDesktop();
+ break;
+ case Button5:
+ NextDesktop();
+ break;
+ default:
+ break;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void UpdatePager() {
+
+ PagerType *pp;
+ ClientNode *np;
+ Pixmap buffer;
+ int width, height;
+ int deskWidth, deskHeight;
+ unsigned int x;
+
+ if(shouldExit) {
+ return;
+ }
+
+ for(pp = pagers; pp; pp = pp->next) {
+
+ buffer = pp->cp->pixmap;
+ width = pp->cp->width;
+ height = pp->cp->height;
+ deskWidth = pp->deskWidth;
+ deskHeight = pp->deskHeight;
+
+ /* Draw the background. */
+ JXSetForeground(display, rootGC, colors[COLOR_PAGER_BG]);
+ JXFillRectangle(display, buffer, rootGC, 0, 0, width, height);
+
+ /* Highlight the current desktop. */
+ JXSetForeground(display, rootGC, colors[COLOR_PAGER_ACTIVE_BG]);
+ if(pp->layout == LAYOUT_HORIZONTAL) {
+ JXFillRectangle(display, buffer, rootGC,
+ currentDesktop * (deskWidth + 1), 0,
+ deskWidth, height);
+ } else {
+ JXFillRectangle(display, buffer, rootGC,
+ 0, currentDesktop * (deskHeight + 1),
+ width, deskHeight);
+ }
+
+ /* Draw the clients. */
+ for(x = LAYER_BOTTOM; x <= LAYER_TOP; x++) {
+ for(np = nodeTail[x]; np; np = np->prev) {
+ DrawPagerClient(pp, np);
+ }
+ }
+
+ /* Draw the desktop dividers. */
+ JXSetForeground(display, rootGC, colors[COLOR_PAGER_FG]);
+ for(x = 1; x < desktopCount; x++) {
+ if(pp->layout == LAYOUT_HORIZONTAL) {
+ JXDrawLine(display, buffer, rootGC,
+ (deskWidth + 1) * x - 1, 0,
+ (deskWidth + 1) * x - 1, height);
+ } else {
+ JXDrawLine(display, buffer, rootGC,
+ 0, (deskHeight + 1) * x - 1,
+ width, (deskHeight + 1) * x - 1);
+ }
+ }
+
+ /* Tell the tray to redraw. */
+ UpdateSpecificTray(pp->cp->tray, pp->cp);
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DrawPagerClient(const PagerType *pp, const ClientNode *np) {
+
+ int x, y;
+ int width, height;
+ int deskOffset;
+ ColorType fillColor;
+
+ if(!(np->state.status & STAT_MAPPED)) {
+ return;
+ }
+
+ if(np->state.status & STAT_STICKY) {
+ deskOffset = currentDesktop;
+ } else {
+ deskOffset = np->state.desktop;
+ }
+ if(pp->layout == LAYOUT_HORIZONTAL) {
+ deskOffset *= pp->deskWidth + 1;
+ } else {
+ deskOffset *= pp->deskHeight + 1;
+ }
+
+ x = (int)((double)np->x * pp->scalex + 1.0);
+ y = (int)((double)np->y * pp->scaley + 1.0);
+ width = (int)((double)np->width * pp->scalex);
+ height = (int)((double)np->height * pp->scaley);
+
+ if(x + width > pp->deskWidth) {
+ width = pp->deskWidth - x;
+ }
+ if(y + height > pp->deskHeight) {
+ height = pp->deskHeight - y;
+ }
+ if(x < 0) {
+ width += x;
+ x = 0;
+ }
+ if(y < 0) {
+ height += y;
+ y = 0;
+ }
+ if(width <= 0 || height <= 0) {
+ return;
+ }
+
+ if(pp->layout == LAYOUT_HORIZONTAL) {
+ x += deskOffset;
+ } else {
+ y += deskOffset;
+ }
+
+ JXSetForeground(display, rootGC, colors[COLOR_PAGER_OUTLINE]);
+ JXDrawRectangle(display, pp->cp->pixmap, rootGC, x, y, width, height);
+
+ if(width > 1 && height > 1) {
+ if((np->state.status & STAT_ACTIVE)
+ && (np->state.desktop == currentDesktop
+ || (np->state.status & STAT_STICKY))) {
+ fillColor = COLOR_PAGER_ACTIVE_FG;
+ } else {
+ fillColor = COLOR_PAGER_FG;
+ }
+ JXSetForeground(display, rootGC, colors[fillColor]);
+ JXFillRectangle(display, pp->cp->pixmap, rootGC, x + 1, y + 1,
+ width - 1, height - 1);
+ }
+
+}
+
diff --git a/src/pager.h b/src/pager.h
new file mode 100644
index 0000000..a3a8f01
--- /dev/null
+++ b/src/pager.h
@@ -0,0 +1,27 @@
+/**
+ * @file pager.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Pager tray component.
+ *
+ */
+
+#ifndef PAGER_H
+#define PAGER_H
+
+struct TrayComponentType;
+
+/*@{*/
+void InitializePager();
+void StartupPager();
+void ShutdownPager();
+void DestroyPager();
+/*@}*/
+
+struct TrayComponentType *CreatePager();
+
+void UpdatePager();
+
+#endif
+
diff --git a/src/parse.c b/src/parse.c
new file mode 100644
index 0000000..5dd82af
--- /dev/null
+++ b/src/parse.c
@@ -0,0 +1,1551 @@
+/****************************************************************************
+ * Parser for the JWM XML configuration file.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "parse.h"
+#include "lex.h"
+#include "menu.h"
+#include "root.h"
+#include "client.h"
+#include "tray.h"
+#include "group.h"
+#include "desktop.h"
+#include "move.h"
+#include "resize.h"
+#include "misc.h"
+#include "swallow.h"
+#include "pager.h"
+#include "error.h"
+#include "key.h"
+#include "cursor.h"
+#include "main.h"
+#include "font.h"
+#include "color.h"
+#include "icon.h"
+#include "command.h"
+#include "button.h"
+#include "event.h"
+#include "taskbar.h"
+#include "traybutton.h"
+#include "clock.h"
+#include "dock.h"
+#include "popup.h"
+#include "status.h"
+#include "theme.h"
+
+typedef struct KeyMapType {
+ char *name;
+ KeyType key;
+} KeyMapType;
+
+static const KeyMapType KEY_MAP[] = {
+ { "up", KEY_UP },
+ { "down", KEY_DOWN },
+ { "right", KEY_RIGHT },
+ { "left", KEY_LEFT },
+ { "escape", KEY_ESC },
+ { "select", KEY_ENTER },
+ { "next", KEY_NEXT },
+ { "nextstacked", KEY_NEXT_STACKED },
+ { "close", KEY_CLOSE },
+ { "minimize", KEY_MIN },
+ { "maximize", KEY_MAX },
+ { "shade", KEY_SHADE },
+ { "move", KEY_MOVE },
+ { "resize", KEY_RESIZE },
+ { "window", KEY_WIN },
+ { "restart", KEY_RESTART },
+ { "exit", KEY_EXIT },
+ { "desktop", KEY_DESKTOP },
+ { "desktop#", KEY_DESKTOP },
+ { NULL, KEY_NONE }
+};
+
+static const char *DEFAULT_TITLE = "JWM";
+static const char *LABEL_ATTRIBUTE = "label";
+static const char *ICON_ATTRIBUTE = "icon";
+static const char *CONFIRM_ATTRIBUTE = "confirm";
+static const char *LABELED_ATTRIBUTE = "labeled";
+static const char *ONROOT_ATTRIBUTE = "onroot";
+static const char *LAYER_ATTRIBUTE = "layer";
+static const char *LAYOUT_ATTRIBUTE = "layout";
+static const char *AUTOHIDE_ATTRIBUTE = "autohide";
+static const char *X_ATTRIBUTE = "x";
+static const char *Y_ATTRIBUTE = "y";
+static const char *WIDTH_ATTRIBUTE = "width";
+static const char *HEIGHT_ATTRIBUTE = "height";
+static const char *NAME_ATTRIBUTE = "name";
+static const char *BORDER_ATTRIBUTE = "border";
+static const char *COUNT_ATTRIBUTE = "count";
+static const char *DISTANCE_ATTRIBUTE = "distance";
+static const char *INSERT_ATTRIBUTE = "insert";
+static const char *MAX_WIDTH_ATTRIBUTE = "maxwidth";
+static const char *FORMAT_ATTRIBUTE = "format";
+static const char *VALIGN_ATTRIBUTE = "valign";
+static const char *HALIGN_ATTRIBUTE = "halign";
+static const char *POPUP_ATTRIBUTE = "popup";
+static const char *DELAY_ATTRIBUTE = "delay";
+static const char *ENABLED_ATTRIBUTE = "enabled";
+static const char *COORDINATES_ATTRIBUTE = "coordinates";
+
+static const char *FALSE_VALUE = "false";
+static const char *TRUE_VALUE = "true";
+
+static int ParseFile(const char *fileName, int depth);
+static char *ReadFile(FILE *fd);
+
+/* Misc. */
+static void Parse(const TokenNode *start, int depth);
+static void ParseInclude(const TokenNode *tp, int depth);
+static void ParseDesktops(const TokenNode *tp);
+
+/* Menus. */
+static void ParseRootMenu(const TokenNode *start);
+static MenuItem *ParseMenuItem(const TokenNode *start, Menu *menu,
+ MenuItem *last);
+static MenuItem *ParseMenuInclude(const TokenNode *tp, Menu *menu,
+ MenuItem *last);
+static MenuItem *InsertMenuItem(MenuItem *last);
+
+/* Tray. */
+static void ParseTray(const TokenNode *tp);
+static void ParsePager(const TokenNode *tp, TrayType *tray);
+static void ParseTaskList(const TokenNode *tp, TrayType *tray);
+static void ParseSwallow(const TokenNode *tp, TrayType *tray);
+static void ParseTrayButton(const TokenNode *tp, TrayType *tray);
+static void ParseClock(const TokenNode *tp, TrayType *tray);
+static void ParseDock(const TokenNode *tp, TrayType *tray);
+
+/* Groups. */
+static void ParseGroup(const TokenNode *tp);
+static void ParseGroupOption(const TokenNode *tp,
+ struct GroupType *group, const char *option);
+
+/* Style. */
+static void ParseBorderStyle(const TokenNode *start);
+static void ParseTaskListStyle(const TokenNode *start);
+static void ParseTrayStyle(const TokenNode *start);
+static void ParsePagerStyle(const TokenNode *start);
+static void ParseMenuStyle(const TokenNode *start);
+static void ParsePopupStyle(const TokenNode *start);
+static void ParseClockStyle(const TokenNode *start);
+static void ParseTrayButtonStyle(const TokenNode *start);
+
+/* Feel. */
+static void ParseKey(const TokenNode *tp);
+static void ParseMouse(const TokenNode *tp);
+static void ParseSnapMode(const TokenNode *tp);
+static void ParseMoveMode(const TokenNode *tp);
+static void ParseResizeMode(const TokenNode *tp);
+static void ParseFocusModel(const TokenNode *tp);
+
+static char *FindAttribute(AttributeNode *ap, const char *name);
+static void ReleaseTokens(TokenNode *np);
+static void InvalidTag(const TokenNode *tp, TokenType parent);
+static void ParseError(const TokenNode *tp, const char *str, ...);
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseConfig(const char *fileName) {
+ if(!ParseFile(fileName, 0)) {
+ if(!ParseFile(SYSTEM_CONFIG, 0)) {
+ ParseError(NULL, "could not open %s or %s", fileName, SYSTEM_CONFIG);
+ }
+ }
+ ValidateTrayButtons();
+ ValidateKeys();
+}
+
+/****************************************************************************
+ * Parse a specific file.
+ * Returns 1 on success and 0 on failure.
+ ****************************************************************************/
+int ParseFile(const char *fileName, int depth) {
+
+ TokenNode *tokens;
+ FILE *fd;
+ char *buffer;
+
+ ++depth;
+ if(depth > MAX_INCLUDE_DEPTH) {
+ ParseError(NULL, "include depth (%d) exceeded", MAX_INCLUDE_DEPTH);
+ return 0;
+ }
+
+ fd = fopen(fileName, "r");
+ if(!fd) {
+ return 0;
+ }
+
+ buffer = ReadFile(fd);
+ fclose(fd);
+
+ tokens = Tokenize(buffer, fileName);
+ Release(buffer);
+ Parse(tokens, depth);
+ ReleaseTokens(tokens);
+
+ return 1;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ReleaseTokens(TokenNode *np) {
+
+ AttributeNode *ap;
+ TokenNode *tp;
+
+ while(np) {
+ tp = np->next;
+
+ while(np->attributes) {
+ ap = np->attributes->next;
+ if(np->attributes->name) {
+ Release(np->attributes->name);
+ }
+ if(np->attributes->value) {
+ Release(np->attributes->value);
+ }
+ Release(np->attributes);
+ np->attributes = ap;
+ }
+
+ if(np->subnodeHead) {
+ ReleaseTokens(np->subnodeHead);
+ }
+
+ if(np->value) {
+ Release(np->value);
+ }
+
+ if(np->invalidName) {
+ Release(np->invalidName);
+ }
+
+ if(np->fileName) {
+ Release(np->fileName);
+ }
+
+ Release(np);
+ np = tp;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Parse(const TokenNode *start, int depth) {
+
+ TokenNode *tp;
+
+ if(!start) {
+ return;
+ }
+
+ if(start->type == TOK_JWM) {
+ for(tp = start->subnodeHead; tp; tp = tp->next) {
+ switch(tp->type) {
+ case TOK_BORDERSTYLE:
+ ParseBorderStyle(tp);
+ break;
+ case TOK_DESKTOPS:
+ ParseDesktops(tp);
+ break;
+ case TOK_DOUBLECLICKSPEED:
+ SetDoubleClickSpeed(tp->value);
+ break;
+ case TOK_DOUBLECLICKDELTA:
+ SetDoubleClickDelta(tp->value);
+ break;
+ case TOK_FOCUSMODEL:
+ ParseFocusModel(tp);
+ break;
+ case TOK_GROUP:
+ ParseGroup(tp);
+ break;
+ case TOK_ICONPATH:
+ AddIconPath(tp->value);
+ break;
+ case TOK_INCLUDE:
+ ParseInclude(tp, depth);
+ break;
+ case TOK_KEY:
+ ParseKey(tp);
+ break;
+ case TOK_MENUSTYLE:
+ ParseMenuStyle(tp);
+ break;
+ case TOK_MOUSE:
+ ParseMouse(tp);
+ break;
+ case TOK_MOVEMODE:
+ ParseMoveMode(tp);
+ break;
+ case TOK_PAGERSTYLE:
+ ParsePagerStyle(tp);
+ break;
+ case TOK_POPUPSTYLE:
+ ParsePopupStyle(tp);
+ break;
+ case TOK_RESIZEMODE:
+ ParseResizeMode(tp);
+ break;
+ case TOK_RESTARTCOMMAND:
+ AddRestartCommand(tp->value);
+ break;
+ case TOK_ROOTMENU:
+ ParseRootMenu(tp);
+ break;
+ case TOK_SHUTDOWNCOMMAND:
+ AddShutdownCommand(tp->value);
+ break;
+ case TOK_SNAPMODE:
+ ParseSnapMode(tp);
+ break;
+ case TOK_STARTUPCOMMAND:
+ AddStartupCommand(tp->value);
+ break;
+ case TOK_TASKLISTSTYLE:
+ ParseTaskListStyle(tp);
+ break;
+ case TOK_TRAY:
+ ParseTray(tp);
+ break;
+ case TOK_TRAYSTYLE:
+ ParseTrayStyle(tp);
+ break;
+ case TOK_TRAYBUTTONSTYLE:
+ ParseTrayButtonStyle(tp);
+ break;
+ case TOK_CLOCKSTYLE:
+ ParseClockStyle(tp);
+ break;
+ case TOK_THEMEPATH:
+ AddThemePath(tp->value);
+ break;
+ case TOK_THEME:
+ SetTheme(tp->value);
+ break;
+ default:
+ InvalidTag(tp, TOK_JWM);
+ break;
+ }
+ }
+ } else {
+ ParseError(start, "invalid start tag: %s", GetTokenName(start));
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseFocusModel(const TokenNode *tp) {
+ if(tp->value) {
+ if(!strcmp(tp->value, "sloppy")) {
+ focusModel = FOCUS_SLOPPY;
+ } else if(!strcmp(tp->value, "click")) {
+ focusModel = FOCUS_CLICK;
+ } else {
+ ParseError(tp, "invalid focus model: \"%s\"", tp->value);
+ }
+ } else {
+ ParseError(tp, "focus model not specified");
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseSnapMode(const TokenNode *tp) {
+
+ const char *distance;
+
+ distance = FindAttribute(tp->attributes, DISTANCE_ATTRIBUTE);
+ if(distance) {
+ SetSnapDistance(distance);
+ } else {
+ SetDefaultSnapDistance();
+ }
+
+ if(tp->value) {
+ if(!strcmp(tp->value, "none")) {
+ SetSnapMode(SNAP_NONE);
+ } else if(!strcmp(tp->value, "screen")) {
+ SetSnapMode(SNAP_SCREEN);
+ } else if(!strcmp(tp->value, "border")) {
+ SetSnapMode(SNAP_BORDER);
+ } else {
+ ParseError(tp, "invalid snap mode: %s", tp->value);
+ }
+ } else {
+ ParseError(tp, "snap mode not specified");
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseMoveMode(const TokenNode *tp) {
+
+ const char *str;
+
+ str = FindAttribute(tp->attributes, COORDINATES_ATTRIBUTE);
+ SetMoveStatusType(str);
+
+ if(tp->value) {
+ if(!strcmp(tp->value, "outline")) {
+ SetMoveMode(MOVE_OUTLINE);
+ } else if(!strcmp(tp->value, "opaque")) {
+ SetMoveMode(MOVE_OPAQUE);
+ } else {
+ ParseError(tp, "invalid move mode: %s", tp->value);
+ }
+ } else {
+ ParseError(tp, "move mode not specified");
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseResizeMode(const TokenNode *tp) {
+
+ const char *str;
+
+ str = FindAttribute(tp->attributes, COORDINATES_ATTRIBUTE);
+ SetResizeStatusType(str);
+
+ if(tp->value) {
+ if(!strcmp(tp->value, "outline")) {
+ SetResizeMode(RESIZE_OUTLINE);
+ } else if(!strcmp(tp->value, "opaque")) {
+ SetResizeMode(RESIZE_OPAQUE);
+ } else {
+ ParseError(tp, "invalid resize mode: %s", tp->value);
+ }
+ } else {
+ ParseError(tp, "resize mode not specified");
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseRootMenu(const TokenNode *start) {
+
+ const char *value;
+ Menu *menu;
+
+ menu = Allocate(sizeof(Menu));
+
+ value = FindAttribute(start->attributes, HEIGHT_ATTRIBUTE);
+ if(value) {
+ menu->itemHeight = atoi(value);
+ } else {
+ menu->itemHeight = 0;
+ }
+
+ value = FindAttribute(start->attributes, LABELED_ATTRIBUTE);
+ if(value && !strcmp(value, TRUE_VALUE)) {
+ value = FindAttribute(start->attributes, LABEL_ATTRIBUTE);
+ if(!value) {
+ value = DEFAULT_TITLE;
+ }
+ menu->label = CopyString(value);
+ } else {
+ menu->label = NULL;
+ }
+
+ menu->items = NULL;
+ ParseMenuItem(start->subnodeHead, menu, NULL);
+
+ value = FindAttribute(start->attributes, ONROOT_ATTRIBUTE);
+ if(!value) {
+ value = "123";
+ }
+
+ SetRootMenu(value, menu);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+MenuItem *InsertMenuItem(MenuItem *last) {
+
+ MenuItem *item;
+
+ item = Allocate(sizeof(MenuItem));
+ item->name = NULL;
+ item->type = MENU_ITEM_NORMAL;
+ item->iconName = NULL;
+ item->action.type = MA_NONE;
+ item->action.data.str = NULL;
+ item->submenu = NULL;
+
+ item->next = NULL;
+ if(last) {
+ last->next = item;
+ }
+
+ return item;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+MenuItem *ParseMenuItem(const TokenNode *start, Menu *menu,
+ MenuItem *last) {
+
+ Menu *child;
+ const char *value;
+
+ Assert(menu);
+
+ menu->offsets = NULL;
+ while(start) {
+ switch(start->type) {
+ case TOK_INCLUDE:
+
+ last = ParseMenuInclude(start, menu, last);
+
+ break;
+ case TOK_MENU:
+
+ last = InsertMenuItem(last);
+ last->type = MENU_ITEM_SUBMENU;
+ if(!menu->items) {
+ menu->items = last;
+ }
+
+ value = FindAttribute(start->attributes, LABEL_ATTRIBUTE);
+ last->name = CopyString(value);
+
+ value = FindAttribute(start->attributes, ICON_ATTRIBUTE);
+ last->iconName = CopyString(value);
+
+ last->submenu = Allocate(sizeof(Menu));
+ child = last->submenu;
+
+ value = FindAttribute(start->attributes, HEIGHT_ATTRIBUTE);
+ if(value) {
+ child->itemHeight = atoi(value);
+ } else {
+ child->itemHeight = menu->itemHeight;
+ }
+
+ value = FindAttribute(start->attributes, LABELED_ATTRIBUTE);
+ if(value && !strcmp(value, TRUE_VALUE)) {
+ if(last->name) {
+ child->label = CopyString(last->name);
+ } else {
+ child->label = CopyString(DEFAULT_TITLE);
+ }
+ } else {
+ child->label = NULL;
+ }
+
+ last->submenu->items = NULL;
+ ParseMenuItem(start->subnodeHead, last->submenu, NULL);
+
+ break;
+ case TOK_PROGRAM:
+
+ last = InsertMenuItem(last);
+ if(!menu->items) {
+ menu->items = last;
+ }
+
+ value = FindAttribute(start->attributes, LABEL_ATTRIBUTE);
+ if(value) {
+ last->name = CopyString(value);
+ } else if(start->value) {
+ last->name = CopyString(start->value);
+ }
+
+ value = FindAttribute(start->attributes, ICON_ATTRIBUTE);
+ last->iconName = CopyString(value);
+
+ last->action.type = MA_EXECUTE;
+ last->action.data.str = CopyString(start->value);
+
+ break;
+ case TOK_SEPARATOR:
+
+ last = InsertMenuItem(last);
+ last->type = MENU_ITEM_SEPARATOR;
+ if(!menu->items) {
+ menu->items = last;
+ }
+
+ break;
+ case TOK_DESKTOPS:
+ case TOK_STICK:
+ case TOK_MAXIMIZE:
+ case TOK_MINIMIZE:
+ case TOK_SHADE:
+ case TOK_MOVE:
+ case TOK_RESIZE:
+ case TOK_KILL:
+ case TOK_CLOSE:
+
+ last = InsertMenuItem(last);
+ if(!menu->items) {
+ menu->items = last;
+ }
+
+ value = FindAttribute(start->attributes, LABEL_ATTRIBUTE);
+ if(!value) {
+ value = GetTokenName(start);
+ }
+ last->name = CopyString(value);
+
+ value = FindAttribute(start->attributes, ICON_ATTRIBUTE);
+ last->iconName = CopyString(value);
+
+ switch(start->type) {
+ case TOK_DESKTOPS:
+ last->action.type = MA_DESKTOP;
+ break;
+ case TOK_STICK:
+ last->action.type = MA_STICK;
+ break;
+ case TOK_MAXIMIZE:
+ last->action.type = MA_MAXIMIZE;
+ break;
+ case TOK_MINIMIZE:
+ last->action.type = MA_MINIMIZE;
+ break;
+ case TOK_SHADE:
+ last->action.type = MA_SHADE;
+ break;
+ case TOK_MOVE:
+ last->action.type = MA_MOVE;
+ break;
+ case TOK_RESIZE:
+ last->action.type = MA_RESIZE;
+ break;
+ case TOK_KILL:
+ last->action.type = MA_KILL;
+ break;
+ case TOK_CLOSE:
+ last->action.type = MA_CLOSE;
+ break;
+ default:
+ break;
+ }
+
+ break;
+ case TOK_EXIT:
+
+ last = InsertMenuItem(last);
+ if(!menu->items) {
+ menu->items = last;
+ }
+
+ value = FindAttribute(start->attributes, CONFIRM_ATTRIBUTE);
+ if(value && !strcmp(value, FALSE_VALUE)) {
+ SetShowExitConfirmation(0);
+ } else {
+ SetShowExitConfirmation(1);
+ }
+
+ value = FindAttribute(start->attributes, LABEL_ATTRIBUTE);
+ if(!value) {
+ value = GetTokenName(start);
+ }
+ last->name = CopyString(value);
+
+ value = FindAttribute(start->attributes, ICON_ATTRIBUTE);
+ last->iconName = CopyString(value);
+
+ last->action.type = MA_EXIT;
+ last->action.data.str = CopyString(start->value);
+
+ break;
+ case TOK_RESTART:
+
+ last = InsertMenuItem(last);
+ if(!menu->items) {
+ menu->items = last;
+ }
+
+ value = FindAttribute(start->attributes, LABEL_ATTRIBUTE);
+ if(!value) {
+ value = GetTokenName(start);
+ }
+ last->name = CopyString(value);
+
+ value = FindAttribute(start->attributes, ICON_ATTRIBUTE);
+ last->iconName = CopyString(value);
+
+ last->action.type = MA_RESTART;
+
+ break;
+ default:
+ InvalidTag(start, TOK_MENU);
+ break;
+ }
+ start = start->next;
+ }
+
+ return last;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+MenuItem *ParseMenuInclude(const TokenNode *tp, Menu *menu,
+ MenuItem *last) {
+
+ FILE *fd;
+ char *path;
+ char *buffer = NULL;
+ TokenNode *mp;
+
+ Assert(tp);
+
+ if(!strncmp(tp->value, "exec:", 5)) {
+
+ path = Allocate(strlen(tp->value) - 5 + 1);
+ strcpy(path, tp->value + 5);
+ ExpandPath(&path);
+
+ fd = popen(path, "r");
+ if(fd) {
+ buffer = ReadFile(fd);
+ pclose(fd);
+ } else {
+ ParseError(tp, "could not execute included program: %s", path);
+ }
+
+ } else {
+
+ path = CopyString(tp->value);
+ ExpandPath(&path);
+
+ fd = fopen(path, "r");
+ if(fd) {
+ buffer = ReadFile(fd);
+ fclose(fd);
+ } else {
+ ParseError(tp, "could not open include: %s", path);
+ }
+
+ }
+
+ if(!buffer) {
+ Release(path);
+ return last;
+ }
+
+ mp = Tokenize(buffer, path);
+ Release(buffer);
+ Release(path);
+
+ if(!mp || mp->type != TOK_MENU) {
+ ParseError(tp, "invalid included menu: %s", tp->value);
+ } else {
+ last = ParseMenuItem(mp, menu, last);
+ }
+
+ if(mp) {
+ ReleaseTokens(mp);
+ }
+
+ return last;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseKey(const TokenNode *tp) {
+
+ const char *key;
+ const char *code;
+ const char *mask;
+ const char *action;
+ const char *command;
+ KeyType k;
+ int x;
+
+ Assert(tp);
+
+ mask = FindAttribute(tp->attributes, "mask");
+ key = FindAttribute(tp->attributes, "key");
+ code = FindAttribute(tp->attributes, "keycode");
+
+ action = tp->value;
+ if(action == NULL) {
+ ParseError(tp, "no action specified for Key");
+ return;
+ }
+
+ command = NULL;
+ k = KEY_NONE;
+ if(!strncmp(action, "exec:", 5)) {
+ k = KEY_EXEC;
+ command = action + 5;
+ } else if(!strncmp(action, "root:", 5)) {
+ k = KEY_ROOT;
+ command = action + 5;
+ } else {
+ for(x = 0; KEY_MAP[x].name; x++) {
+ if(!strcmp(action, KEY_MAP[x].name)) {
+ k = KEY_MAP[x].key;
+ break;
+ }
+ }
+ }
+
+ if(k == KEY_NONE) {
+ ParseError(tp, "invalid Key action: \"%s\"", action);
+ } else {
+ InsertBinding(k, mask, key, code, command);
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseMouse(const TokenNode *tp) {
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseBorderStyle(const TokenNode *tp) {
+
+ const TokenNode *np;
+
+ Assert(tp);
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_FONT:
+ SetFont(FONT_BORDER, np->value);
+ break;
+ case TOK_WIDTH:
+ SetBorderWidth(np->value);
+ break;
+ case TOK_HEIGHT:
+ SetTitleHeight(np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_BORDER_FG, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_BORDER_BG, np->value);
+ break;
+ case TOK_ACTIVEFOREGROUND:
+ SetColor(COLOR_BORDER_ACTIVE_FG, np->value);
+ break;
+ case TOK_ACTIVEBACKGROUND:
+ SetColor(COLOR_BORDER_ACTIVE_BG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_BORDERSTYLE);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseInclude(const TokenNode *tp, int depth) {
+
+ char *temp;
+
+ Assert(tp);
+
+ if(!tp->value) {
+
+ ParseError(tp, "no include file specified", temp);
+
+ } else {
+
+ temp = CopyString(tp->value);
+
+ ExpandPath(&temp);
+
+ if(!ParseFile(temp, depth)) {
+ ParseError(tp, "could not open included file %s", temp);
+ }
+
+ Release(temp);
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseDesktops(const TokenNode *tp) {
+
+ TokenNode *np;
+ char *attr;
+ unsigned int x;
+
+ Assert(tp);
+
+ attr = FindAttribute(tp->attributes, COUNT_ATTRIBUTE);
+ if(attr) {
+ SetDesktopCount(attr);
+ } else {
+ desktopCount = DEFAULT_DESKTOP_COUNT;
+ }
+
+ x = 0;
+ for(x = 0, np = tp->subnodeHead; np; np = np->next, x++) {
+ if(x >= desktopCount) {
+ break;
+ }
+ switch(np->type) {
+ case TOK_NAME:
+ SetDesktopName(x, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_DESKTOPS);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseTaskListStyle(const TokenNode *tp) {
+
+ const char *temp;
+ TokenNode *np;
+
+ temp = FindAttribute(tp->attributes, INSERT_ATTRIBUTE);
+ if(temp) {
+ SetTaskBarInsertMode(temp);
+ }
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_FONT:
+ SetFont(FONT_TASK, np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_TASK_FG, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_TASK_BG, np->value);
+ break;
+ case TOK_ACTIVEFOREGROUND:
+ SetColor(COLOR_TASK_ACTIVE_FG, np->value);
+ break;
+ case TOK_ACTIVEBACKGROUND:
+ SetColor(COLOR_TASK_ACTIVE_BG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_TASKLISTSTYLE);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseTrayStyle(const TokenNode *tp) {
+
+ const TokenNode *np;
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_FONT:
+ SetFont(FONT_TRAY, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_TRAY_BG, np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_TRAY_FG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_TRAYSTYLE);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseTray(const TokenNode *tp) {
+
+ const TokenNode *np;
+ const char *attr;
+ TrayType *tray;
+
+ Assert(tp);
+
+ tray = CreateTray();
+
+ attr = FindAttribute(tp->attributes, AUTOHIDE_ATTRIBUTE);
+ if(attr && !strcmp(attr, TRUE_VALUE)) {
+ SetAutoHideTray(tray, 1);
+ } else {
+ SetAutoHideTray(tray, 0);
+ }
+
+ attr = FindAttribute(tp->attributes, X_ATTRIBUTE);
+ if(attr) {
+ SetTrayX(tray, attr);
+ }
+
+ attr = FindAttribute(tp->attributes, Y_ATTRIBUTE);
+ if(attr) {
+ SetTrayY(tray, attr);
+ }
+
+ attr = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE);
+ if(attr) {
+ SetTrayWidth(tray, attr);
+ }
+
+ attr = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE);
+ if(attr) {
+ SetTrayHeight(tray, attr);
+ }
+
+ attr = FindAttribute(tp->attributes, VALIGN_ATTRIBUTE);
+ SetTrayVerticalAlignment(tray, attr);
+
+ attr = FindAttribute(tp->attributes, HALIGN_ATTRIBUTE);
+ SetTrayHorizontalAlignment(tray, attr);
+
+ attr = FindAttribute(tp->attributes, LAYOUT_ATTRIBUTE);
+ SetTrayLayout(tray, attr);
+
+ attr = FindAttribute(tp->attributes, LAYER_ATTRIBUTE);
+ if(attr) {
+ SetTrayLayer(tray, attr);
+ }
+
+ attr = FindAttribute(tp->attributes, BORDER_ATTRIBUTE);
+ if(attr) {
+ SetTrayBorder(tray, attr);
+ }
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_PAGER:
+ ParsePager(np, tray);
+ break;
+ case TOK_TASKLIST:
+ ParseTaskList(np, tray);
+ break;
+ case TOK_SWALLOW:
+ ParseSwallow(np, tray);
+ break;
+ case TOK_TRAYBUTTON:
+ ParseTrayButton(np, tray);
+ break;
+ case TOK_CLOCK:
+ ParseClock(np, tray);
+ break;
+ case TOK_DOCK:
+ ParseDock(np, tray);
+ break;
+ default:
+ InvalidTag(np, TOK_TRAY);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParsePager(const TokenNode *tp, TrayType *tray) {
+
+ TrayComponentType *cp;
+
+ Assert(tp);
+ Assert(tray);
+
+ cp = CreatePager();
+ AddTrayComponent(tray, cp);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseTaskList(const TokenNode *tp, TrayType *tray) {
+
+ TrayComponentType *cp;
+ const char *temp;
+
+ Assert(tp);
+ Assert(tray);
+
+ cp = CreateTaskBar();
+ AddTrayComponent(tray, cp);
+
+ temp = FindAttribute(tp->attributes, MAX_WIDTH_ATTRIBUTE);
+ if(temp) {
+ SetMaxTaskBarItemWidth(cp, temp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseSwallow(const TokenNode *tp, TrayType *tray) {
+
+ TrayComponentType *cp;
+ const char *name;
+ const char *temp;
+ int width, height;
+
+ Assert(tp);
+ Assert(tray);
+
+ name = FindAttribute(tp->attributes, NAME_ATTRIBUTE);
+ if(name == NULL) {
+ name = tp->value;
+ }
+
+ temp = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE);
+ if(temp) {
+ width = atoi(temp);
+ } else {
+ width = 0;
+ }
+
+ temp = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE);
+ if(temp) {
+ height = atoi(temp);
+ } else {
+ height = 0;
+ }
+
+ cp = CreateSwallow(name, tp->value, width, height);
+ if(cp) {
+ AddTrayComponent(tray, cp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseTrayButton(const TokenNode *tp, TrayType *tray) {
+
+ TrayComponentType *cp;
+ const char *icon;
+ const char *label;
+ const char *popup;
+ const char *temp;
+ int width, height;
+
+ Assert(tp);
+ Assert(tray);
+
+ icon = FindAttribute(tp->attributes, ICON_ATTRIBUTE);
+ label = FindAttribute(tp->attributes, LABEL_ATTRIBUTE);
+ popup = FindAttribute(tp->attributes, POPUP_ATTRIBUTE);
+
+ temp = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE);
+ if(temp) {
+ width = atoi(temp);
+ } else {
+ width = 0;
+ }
+
+ temp = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE);
+ if(temp) {
+ height = atoi(temp);
+ } else {
+ height = 0;
+ }
+
+ cp = CreateTrayButton(icon, label, tp->value, popup, width, height);
+ if(cp) {
+ AddTrayComponent(tray, cp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseClock(const TokenNode *tp, TrayType *tray) {
+
+ TrayComponentType *cp;
+ const char *format;
+ const char *command;
+ const char *temp;
+ int width, height;
+
+ Assert(tp);
+ Assert(tray);
+
+ format = FindAttribute(tp->attributes, FORMAT_ATTRIBUTE);
+
+ if(tp->value && strlen(tp->value) > 0) {
+ command = tp->value;
+ } else {
+ command = NULL;
+ }
+
+ temp = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE);
+ if(temp) {
+ width = atoi(temp);
+ } else {
+ width = 0;
+ }
+
+ temp = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE);
+ if(temp) {
+ height = atoi(temp);
+ } else {
+ height = 0;
+ }
+
+ cp = CreateClock(format, command, width, height);
+ if(cp) {
+ AddTrayComponent(tray, cp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseDock(const TokenNode *tp, TrayType *tray) {
+
+ TrayComponentType *cp;
+
+ Assert(tp);
+ Assert(tray);
+
+ cp = CreateDock();
+ if(cp) {
+ AddTrayComponent(tray, cp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParsePagerStyle(const TokenNode *tp) {
+
+ const TokenNode *np;
+
+ Assert(tp);
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_OUTLINE:
+ SetColor(COLOR_PAGER_OUTLINE, np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_PAGER_FG, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_PAGER_BG, np->value);
+ break;
+ case TOK_ACTIVEFOREGROUND:
+ SetColor(COLOR_PAGER_ACTIVE_FG, np->value);
+ break;
+ case TOK_ACTIVEBACKGROUND:
+ SetColor(COLOR_PAGER_ACTIVE_BG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_PAGERSTYLE);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParsePopupStyle(const TokenNode *tp) {
+
+ const TokenNode *np;
+ const char *str;
+
+ Assert(tp);
+
+ str = FindAttribute(tp->attributes, ENABLED_ATTRIBUTE);
+ if(str) {
+ if(!strcmp(str, TRUE_VALUE)) {
+ SetPopupEnabled(1);
+ } else if(!strcmp(str, FALSE_VALUE)) {
+ SetPopupEnabled(0);
+ } else {
+ ParseError(tp, "invalid enabled value: \"%s\"", str);
+ }
+ }
+
+ str = FindAttribute(tp->attributes, DELAY_ATTRIBUTE);
+ if(str) {
+ SetPopupDelay(str);
+ }
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_FONT:
+ SetFont(FONT_POPUP, np->value);
+ break;
+ case TOK_OUTLINE:
+ SetColor(COLOR_POPUP_OUTLINE, np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_POPUP_FG, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_POPUP_BG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_POPUPSTYLE);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseMenuStyle(const TokenNode *tp) {
+
+ const TokenNode *np;
+
+ Assert(tp);
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_FONT:
+ SetFont(FONT_MENU, np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_MENU_FG, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_MENU_BG, np->value);
+ break;
+ case TOK_ACTIVEFOREGROUND:
+ SetColor(COLOR_MENU_ACTIVE_FG, np->value);
+ break;
+ case TOK_ACTIVEBACKGROUND:
+ SetColor(COLOR_MENU_ACTIVE_BG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_MENUSTYLE);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseClockStyle(const TokenNode *tp) {
+
+ const TokenNode *np;
+
+ Assert(tp);
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_FONT:
+ SetFont(FONT_CLOCK, np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_CLOCK_FG, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_CLOCK_BG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_CLOCKSTYLE);
+ break;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseTrayButtonStyle(const TokenNode *tp) {
+
+ const TokenNode *np;
+
+ Assert(tp);
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_FONT:
+ SetFont(FONT_TRAYBUTTON, np->value);
+ break;
+ case TOK_FOREGROUND:
+ SetColor(COLOR_TRAYBUTTON_FG, np->value);
+ break;
+ case TOK_BACKGROUND:
+ SetColor(COLOR_TRAYBUTTON_BG, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_TRAYBUTTONSTYLE);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseGroup(const TokenNode *tp) {
+
+ const TokenNode *np;
+ struct GroupType *group;
+
+ Assert(tp);
+
+ group = CreateGroup();
+
+ for(np = tp->subnodeHead; np; np = np->next) {
+ switch(np->type) {
+ case TOK_CLASS:
+ AddGroupClass(group, np->value);
+ break;
+ case TOK_NAME:
+ AddGroupName(group, np->value);
+ break;
+ case TOK_OPTION:
+ ParseGroupOption(np, group, np->value);
+ break;
+ default:
+ InvalidTag(np, TOK_GROUP);
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ParseGroupOption(const TokenNode *tp, struct GroupType *group,
+ const char *option) {
+
+ if(!option) {
+ return;
+ }
+
+ if(!strcmp(option, "sticky")) {
+ AddGroupOption(group, OPTION_STICKY);
+ } else if(!strcmp(option, "nolist")) {
+ AddGroupOption(group, OPTION_NOLIST);
+ } else if(!strcmp(option, "border")) {
+ AddGroupOption(group, OPTION_BORDER);
+ } else if(!strcmp(option, "noborder")) {
+ AddGroupOption(group, OPTION_NOBORDER);
+ } else if(!strcmp(option, "title")) {
+ AddGroupOption(group, OPTION_TITLE);
+ } else if(!strcmp(option, "notitle")) {
+ AddGroupOption(group, OPTION_NOTITLE);
+ } else if(!strcmp(option, "pignore")) {
+ AddGroupOption(group, OPTION_PIGNORE);
+ } else if(!strcmp(option, "maximized")) {
+ AddGroupOption(group, OPTION_MAXIMIZED);
+ } else if(!strcmp(option, "minimized")) {
+ AddGroupOption(group, OPTION_MINIMIZED);
+ } else if(!strcmp(option, "shaded")) {
+ AddGroupOption(group, OPTION_SHADED);
+ } else if(!strncmp(option, "layer:", 6)) {
+ AddGroupOptionValue(group, OPTION_LAYER, option + 6);
+ } else if(!strncmp(option, "desktop:", 8)) {
+ AddGroupOptionValue(group, OPTION_DESKTOP, option + 8);
+ } else if(!strncmp(option, "icon:", 5)) {
+ AddGroupOptionValue(group, OPTION_ICON, option + 5);
+ } else {
+ ParseError(tp, "invalid Group Option: %s", option);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+char *FindAttribute(AttributeNode *ap, const char *name) {
+
+ while(ap) {
+ if(!strcmp(name, ap->name)) {
+ return ap->value;
+ }
+ ap = ap->next;
+ }
+
+ return NULL;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+char *ReadFile(FILE *fd) {
+
+ const int BLOCK_SIZE = 1024;
+
+ char *buffer;
+ int len, max;
+ int ch;
+
+ len = 0;
+ max = BLOCK_SIZE;
+ buffer = Allocate(max + 1);
+
+ for(;;) {
+ ch = fgetc(fd);
+ if(ch == EOF) {
+ break;
+ }
+ buffer[len++] = ch;
+ if(len >= max) {
+ max += BLOCK_SIZE;
+ buffer = Reallocate(buffer, max + 1);
+ }
+ }
+ buffer[len] = 0;
+
+ return buffer;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void InvalidTag(const TokenNode *tp, TokenType parent) {
+
+ ParseError(tp, "invalid tag in %s: %s",
+ GetTokenTypeName(parent), GetTokenName(tp));
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ParseError(const TokenNode *tp, const char *str, ...) {
+
+ va_list ap;
+
+ static const char *NULL_MESSAGE = "configuration error";
+ static const char *FILE_MESSAGE = "%s[%d]";
+
+ char *msg;
+
+ va_start(ap, str);
+
+ if(tp) {
+ msg = Allocate(strlen(FILE_MESSAGE) + strlen(tp->fileName) + 1);
+ sprintf(msg, FILE_MESSAGE, tp->fileName, tp->line);
+ } else {
+ msg = CopyString(NULL_MESSAGE);
+ }
+
+ WarningVA(msg, str, ap);
+
+ Release(msg);
+
+ va_end(ap);
+
+}
+
+
diff --git a/src/parse.h b/src/parse.h
new file mode 100644
index 0000000..4e60f13
--- /dev/null
+++ b/src/parse.h
@@ -0,0 +1,19 @@
+/**
+ * @file parse.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header file for the JWM configuration parser.
+ *
+ */
+
+#ifndef PARSE_H
+#define PARSE_H
+
+/** Parse a configuration file.
+ * @param fileName The file to parse.
+ */
+void ParseConfig(const char *fileName);
+
+#endif
+
diff --git a/src/place.c b/src/place.c
new file mode 100644
index 0000000..f70ce73
--- /dev/null
+++ b/src/place.c
@@ -0,0 +1,647 @@
+/****************************************************************************
+ * Client placement functions.
+ * Copyright (C) 2005 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "place.h"
+#include "client.h"
+#include "screen.h"
+#include "border.h"
+#include "tray.h"
+#include "main.h"
+
+typedef struct BoundingBox {
+ int x, y;
+ int width, height;
+} BoundingBox;
+
+typedef struct Strut {
+ ClientNode *client;
+ BoundingBox box;
+ struct Strut *prev;
+ struct Strut *next;
+} Strut;
+
+static Strut *struts = NULL;
+static Strut *strutsTail = NULL;
+
+/* desktopCount x screenCount */
+/* Note that we assume x and y are 0 based for all screens here. */
+static int *cascadeOffsets = NULL;
+
+static void GetScreenBounds(const ScreenType *sp, BoundingBox *box);
+static void UpdateTrayBounds(BoundingBox *box, unsigned int layer);
+static void UpdateStrutBounds(BoundingBox *box);
+static void SubtractBounds(const BoundingBox *src, BoundingBox *dest);
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializePlacement() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupPlacement() {
+
+ int count;
+ int x;
+
+ count = desktopCount * GetScreenCount();
+ cascadeOffsets = Allocate(count * sizeof(int));
+
+ for(x = 0; x < count; x++) {
+ cascadeOffsets[x] = borderWidth + titleHeight;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownPlacement() {
+
+ Strut *sp;
+
+ Release(cascadeOffsets);
+
+ while(struts) {
+ sp = struts->next;
+ Release(struts);
+ struts = sp;
+ }
+ strutsTail = NULL;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyPlacement() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void RemoveClientStrut(ClientNode *np) {
+
+ Strut *sp;
+
+ for(sp = struts; sp; sp = sp->next) {
+ if(sp->client == np) {
+ if(sp->prev) {
+ sp->prev->next = sp->next;
+ } else {
+ struts = sp->next;
+ }
+ if(sp->next) {
+ sp->next->prev = sp->prev;
+ } else {
+ strutsTail = sp->prev;
+ }
+ Release(sp);
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ReadClientStrut(ClientNode *np) {
+
+ BoundingBox box;
+ Strut *sp;
+ int status;
+ Atom actualType;
+ int actualFormat;
+ unsigned long count;
+ unsigned long bytesLeft;
+ unsigned char *value;
+ long *lvalue;
+ long leftWidth, rightWidth, topHeight, bottomHeight;
+ long leftStart, leftEnd, rightStart, rightEnd;
+ long topStart, topEnd, bottomStart, bottomEnd;
+
+ RemoveClientStrut(np);
+
+ box.x = 0;
+ box.y = 0;
+ box.width = 0;
+ box.height = 0;
+
+ /* First try to read _NET_WM_STRUT_PARTIAL */
+ /* Format is:
+ * left_width, right_width, top_width, bottom_width,
+ * left_start_y, left_end_y, right_start_y, right_end_y,
+ * top_start_x, top_end_x, bottom_start_x, bottom_end_x
+ */
+ status = JXGetWindowProperty(display, np->window,
+ atoms[ATOM_NET_WM_STRUT_PARTIAL], 0, 12, False, XA_CARDINAL,
+ &actualType, &actualFormat, &count, &bytesLeft, &value);
+ if(status == Success) {
+ if(count == 12) {
+ lvalue = (long*)value;
+ leftWidth = lvalue[0];
+ rightWidth = lvalue[1];
+ topHeight = lvalue[2];
+ bottomHeight = lvalue[3];
+ leftStart = lvalue[4];
+ leftEnd = lvalue[5];
+ rightStart = lvalue[6];
+ rightEnd = lvalue[7];
+ topStart = lvalue[8];
+ topEnd = lvalue[9];
+ bottomStart = lvalue[10];
+ bottomEnd = lvalue[11];
+
+ if(leftWidth > 0) {
+ box.width = leftWidth;
+ box.x = leftStart;
+ }
+
+ if(rightWidth > 0) {
+ box.width = rightWidth;
+ box.x = rightStart;
+ }
+
+ if(topHeight > 0) {
+ box.height = topHeight;
+ box.y = topStart;
+ }
+
+ if(bottomHeight > 0) {
+ box.height = bottomHeight;
+ box.y = bottomStart;
+ }
+
+ sp = Allocate(sizeof(Strut));
+ sp->client = np;
+ sp->box = box;
+ sp->prev = NULL;
+ sp->next = struts;
+ if(struts) {
+ struts->prev = sp;
+ } else {
+ strutsTail = sp;
+ }
+ struts = sp;
+
+ }
+ JXFree(value);
+ return;
+ }
+
+ /* Next try to read _NET_WM_STRUT */
+ /* Format is: left_width, right_width, top_width, bottom_width */
+ status = JXGetWindowProperty(display, np->window,
+ atoms[ATOM_NET_WM_STRUT], 0, 4, False, XA_CARDINAL,
+ &actualType, &actualFormat, &count, &bytesLeft, &value);
+ if(status == Success) {
+ if(count == 4) {
+ lvalue = (long*)value;
+ leftWidth = lvalue[0];
+ rightWidth = lvalue[1];
+ topHeight = lvalue[2];
+ bottomHeight = lvalue[3];
+
+ if(leftWidth > 0) {
+ box.x = 0;
+ box.width = leftWidth;
+ }
+
+ if(rightWidth > 0) {
+ box.x = rootWidth - rightWidth;
+ box.width = rightWidth;
+ }
+
+ if(topHeight > 0) {
+ box.y = 0;
+ box.height = topHeight;
+ }
+
+ if(bottomHeight > 0) {
+ box.y = rootHeight - bottomHeight;
+ box.height = bottomHeight;
+ }
+
+ sp = Allocate(sizeof(Strut));
+ sp->client = np;
+ sp->box = box;
+ sp->prev = NULL;
+ sp->next = struts;
+ if(struts) {
+ struts->prev = sp;
+ } else {
+ strutsTail = sp;
+ }
+ struts = sp;
+
+ }
+ JXFree(value);
+ return;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void GetScreenBounds(const ScreenType *sp, BoundingBox *box) {
+
+ box->x = sp->x;
+ box->y = sp->y;
+ box->width = sp->width;
+ box->height = sp->height;
+
+}
+
+/****************************************************************************
+ * Shrink dest such that it does not intersect with src.
+ ****************************************************************************/
+void SubtractBounds(const BoundingBox *src, BoundingBox *dest) {
+
+ BoundingBox boxes[4];
+
+ if(src->x + src->width <= dest->x) {
+ return;
+ }
+ if(src->y + src->height <= dest->y) {
+ return;
+ }
+ if(dest->x + dest->width <= src->x) {
+ return;
+ }
+ if(dest->y + dest->height <= src->y) {
+ return;
+ }
+
+ /* There are four ways to do this:
+ * 0. Increase the x-coordinate and decrease the width of dest.
+ * 1. Increase the y-coordinate and decrease the height of dest.
+ * 2. Decrease the width of dest.
+ * 3. Decrease the height of dest.
+ * We will chose the option which leaves the greatest area.
+ * Note that negative areas are possible.
+ */
+
+ /* 0 */
+ boxes[0] = *dest;
+ boxes[0].x = src->x + src->width;
+ boxes[0].width = dest->x + dest->width - boxes[0].x;
+
+ /* 1 */
+ boxes[1] = *dest;
+ boxes[1].y = src->y + src->height;
+ boxes[1].height = dest->y + dest->height - boxes[1].y;
+
+ /* 2 */
+ boxes[2] = *dest;
+ boxes[2].width = src->x - dest->x;
+
+ /* 3 */
+ boxes[3] = *dest;
+ boxes[3].height = src->y - dest->y;
+
+ /* 0 and 1, winner in 0. */
+ if(boxes[0].width * boxes[0].height < boxes[1].width * boxes[1].height) {
+ boxes[0] = boxes[1];
+ }
+
+ /* 2 and 3, winner in 2. */
+ if(boxes[2].width * boxes[2].height < boxes[3].width * boxes[3].height) {
+ boxes[2] = boxes[3];
+ }
+
+ /* 0 and 2, winner in dest. */
+ if(boxes[0].width * boxes[0].height < boxes[2].width * boxes[2].height) {
+ *dest = boxes[2];
+ } else {
+ *dest = boxes[0];
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void UpdateTrayBounds(BoundingBox *box, unsigned int layer) {
+
+ TrayType *tp;
+ BoundingBox src;
+ BoundingBox last;
+
+ for(tp = GetTrays(); tp; tp = tp->next) {
+
+ if(tp->layer > layer && !tp->autoHide) {
+
+ src.x = tp->x;
+ src.y = tp->y;
+ src.width = tp->width;
+ src.height = tp->height;
+
+ last = *box;
+ SubtractBounds(&src, box);
+ if(box->width * box->height <= 0) {
+ *box = last;
+ break;
+ }
+
+ }
+
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void UpdateStrutBounds(BoundingBox *box) {
+
+ Strut *sp;
+ BoundingBox last;
+
+ for(sp = struts; sp; sp = sp->next) {
+ if(sp->client->state.desktop == currentDesktop
+ || (sp->client->state.status & STAT_STICKY)) {
+ continue;
+ }
+ last = *box;
+ SubtractBounds(&sp->box, box);
+ if(box->width * box->height <= 0) {
+ *box = last;
+ break;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void PlaceClient(ClientNode *np, int alreadyMapped) {
+
+ BoundingBox box;
+ int north, south, east, west;
+ const ScreenType *sp;
+ int cascadeIndex;
+ int overflow;
+
+ Assert(np);
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ if(np->x + np->width > rootWidth || np->y + np->height > rootWidth) {
+ overflow = 1;
+ } else {
+ overflow = 0;
+ }
+
+ sp = GetMouseScreen();
+ GetScreenBounds(sp, &box);
+
+ if(!overflow && (alreadyMapped
+ || (!(np->state.status & STAT_PIGNORE)
+ && (np->sizeFlags & (PPosition | USPosition))))) {
+
+ GravitateClient(np, 0);
+
+ } else {
+
+ UpdateTrayBounds(&box, np->state.layer);
+ UpdateStrutBounds(&box);
+
+ cascadeIndex = sp->index * desktopCount + currentDesktop;
+
+ /* Set the cascaded location. */
+ np->x = box.x + west + cascadeOffsets[cascadeIndex];
+ np->y = box.y + north + cascadeOffsets[cascadeIndex];
+ cascadeOffsets[cascadeIndex] += borderWidth + titleHeight;
+
+ /* Check for cascade overflow. */
+ overflow = 0;
+ if(np->x + np->width - box.x > box.width) {
+ overflow = 1;
+ } else if(np->y + np->height - box.y > box.height) {
+ overflow = 1;
+ }
+
+ if(overflow) {
+
+ cascadeOffsets[cascadeIndex] = borderWidth + titleHeight;
+ np->x = box.x + west + cascadeOffsets[cascadeIndex];
+ np->y = box.y + north + cascadeOffsets[cascadeIndex];
+
+ /* Check for client overflow. */
+ overflow = 0;
+ if(np->x + np->width - box.x > box.width) {
+ overflow = 1;
+ } else if(np->y + np->height - box.y > box.height) {
+ overflow = 1;
+ }
+
+ /* Update cascade position or position client. */
+ if(overflow) {
+ np->x = box.x + west;
+ np->y = box.y + north;
+ } else {
+ cascadeOffsets[cascadeIndex] += borderWidth + titleHeight;
+ }
+
+ }
+
+ }
+
+ if(np->state.status & STAT_FULLSCREEN) {
+ JXMoveWindow(display, np->parent, sp->x, sp->y);
+ } else {
+ JXMoveWindow(display, np->parent, np->x - west, np->y - north);
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ConstrainSize(ClientNode *np) {
+
+ BoundingBox box;
+ const ScreenType *sp;
+ int north, south, east, west;
+ float ratio, minr, maxr;
+
+ Assert(np);
+
+ /* Determine if the size needs to be constrained. */
+ sp = GetCurrentScreen(np->x, np->y);
+ if(np->width < sp->width && np->height < sp->height) {
+ return;
+ }
+
+ /* Constrain the size. */
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ GetScreenBounds(sp, &box);
+ UpdateTrayBounds(&box, np->state.layer);
+ UpdateStrutBounds(&box);
+
+ box.x += west;
+ box.y += north;
+ box.width -= east + west;
+ box.height -= north + south;
+
+ if(box.width > np->maxWidth) {
+ box.width = np->maxWidth;
+ }
+ if(box.height > np->maxHeight) {
+ box.height = np->maxHeight;
+ }
+
+ if(np->sizeFlags & PAspect) {
+
+ ratio = (float)box.width / box.height;
+
+ minr = (float)np->aspect.minx / np->aspect.miny;
+ if(ratio < minr) {
+ box.height = (int)((float)box.width / minr);
+ }
+
+ maxr = (float)np->aspect.maxx / np->aspect.maxy;
+ if(ratio > maxr) {
+ box.width = (int)((float)box.height * maxr);
+ }
+
+ }
+
+ np->x = box.x;
+ np->y = box.y;
+ np->width = box.width - (box.width % np->xinc);
+ np->height = box.height - (box.height % np->yinc);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void PlaceMaximizedClient(ClientNode *np) {
+
+ BoundingBox box;
+ const ScreenType *sp;
+ int north, south, east, west;
+ float ratio, minr, maxr;
+
+ np->oldx = np->x;
+ np->oldy = np->y;
+ np->oldWidth = np->width;
+ np->oldHeight = np->height;
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ sp = GetCurrentScreen(
+ np->x + (east + west + np->width) / 2,
+ np->y + (north + south + np->height) / 2);
+ GetScreenBounds(sp, &box);
+ UpdateTrayBounds(&box, np->state.layer);
+ UpdateStrutBounds(&box);
+
+ box.x += west;
+ box.y += north;
+ box.width -= east + west;
+ box.height -= north + south;
+
+ if(box.width > np->maxWidth) {
+ box.width = np->maxWidth;
+ }
+ if(box.height > np->maxHeight) {
+ box.height = np->maxHeight;
+ }
+
+ if(np->sizeFlags & PAspect) {
+
+ ratio = (float)box.width / box.height;
+
+ minr = (float)np->aspect.minx / np->aspect.miny;
+ if(ratio < minr) {
+ box.height = (int)((float)box.width / minr);
+ }
+
+ maxr = (float)np->aspect.maxx / np->aspect.maxy;
+ if(ratio > maxr) {
+ box.width = (int)((float)box.height * maxr);
+ }
+
+ }
+
+ np->x = box.x;
+ np->y = box.y;
+ np->width = box.width - (box.width % np->xinc);
+ np->height = box.height - (box.height % np->yinc);
+
+ np->state.status |= STAT_MAXIMIZED;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void GetGravityDelta(const ClientNode *np, int *x, int *y) {
+
+ int north, south, east, west;
+
+ Assert(np);
+ Assert(x);
+ Assert(y);
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ switch(np->gravity) {
+ case NorthWestGravity:
+ *y = -north;
+ *x = -west;
+ break;
+ case NorthGravity:
+ *y = -north;
+ break;
+ case NorthEastGravity:
+ *y = -north;
+ *x = west;
+ break;
+ case WestGravity:
+ *x = -west;
+ break;
+ case CenterGravity:
+ *y = (north + south) / 2;
+ *x = (east + west) / 2;
+ break;
+ case EastGravity:
+ *x = west;
+ break;
+ case SouthWestGravity:
+ *y = south;
+ *x = -west;
+ break;
+ case SouthGravity:
+ *y = south;
+ break;
+ case SouthEastGravity:
+ *y = south;
+ *x = west;
+ break;
+ default: /* Static */
+ *x = 0;
+ *y = 0;
+ break;
+ }
+
+}
+
+/****************************************************************************
+ * Move the window in the specified direction for reparenting.
+ ****************************************************************************/
+void GravitateClient(ClientNode *np, int negate) {
+
+ int deltax, deltay;
+
+ Assert(np);
+
+ GetGravityDelta(np, &deltax, &deltay);
+
+ if(negate) {
+ np->x += deltax;
+ np->y += deltay;
+ } else {
+ np->x -= deltax;
+ np->y -= deltay;
+ }
+
+}
+
diff --git a/src/place.h b/src/place.h
new file mode 100644
index 0000000..7508881
--- /dev/null
+++ b/src/place.h
@@ -0,0 +1,34 @@
+/**
+ * @file place.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for client placement functions.
+ *
+ */
+
+#ifndef PLACE_H
+#define PLACE_H
+
+struct ClientNode;
+
+/*@{*/
+void InitializePlacement();
+void StartupPlacement();
+void ShutdownPlacement();
+void DestroyPlacement();
+/*@}*/
+
+void RemoveClientStrut(struct ClientNode *np);
+void ReadClientStrut(struct ClientNode *np);
+
+void PlaceClient(struct ClientNode *np, int alreadyMapped);
+void PlaceMaximizedClient(struct ClientNode *np);
+void GravitateClient(struct ClientNode *np, int negate);
+
+void GetGravityDelta(const struct ClientNode *np, int *x, int *y);
+
+void ConstrainSize(struct ClientNode *np);
+
+#endif
+
diff --git a/src/popup.c b/src/popup.c
new file mode 100644
index 0000000..c9caafc
--- /dev/null
+++ b/src/popup.c
@@ -0,0 +1,232 @@
+/****************************************************************************
+ * Functions for displaying popup windows.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "popup.h"
+#include "main.h"
+#include "color.h"
+#include "font.h"
+#include "screen.h"
+#include "cursor.h"
+#include "error.h"
+#include "timing.h"
+#include "misc.h"
+
+#define DEFAULT_POPUP_DELAY 600
+
+typedef struct PopupType {
+ int isActive;
+ int x, y; /* The coordinates of the upper-left corner of the popup. */
+ int mx, my; /* The mouse position when the popup was created. */
+ int width, height;
+ char *text;
+ Window window;
+} PopupType;
+
+static PopupType popup;
+static int popupEnabled;
+int popupDelay;
+
+static void DrawPopup();
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializePopup() {
+ popupDelay = DEFAULT_POPUP_DELAY;
+ popupEnabled = 1;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupPopup() {
+ popup.isActive = 0;
+ popup.text = NULL;
+ popup.window = None;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownPopup() {
+ if(popup.text) {
+ Release(popup.text);
+ popup.text = NULL;
+ }
+ if(popup.window != None) {
+ JXDestroyWindow(display, popup.window);
+ popup.window = None;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyPopup() {
+}
+
+/****************************************************************************
+ * Show a popup window.
+ * x - The x coordinate of the popup window.
+ * y - The y coordinate of the popup window.
+ * text - The text to display in the popup.
+ ****************************************************************************/
+void ShowPopup(int x, int y, const char *text) {
+
+ unsigned long attrMask;
+ XSetWindowAttributes attr;
+ const ScreenType *sp;
+
+ Assert(text);
+
+ if(!popupEnabled) {
+ return;
+ }
+
+ if(popup.text) {
+ Release(popup.text);
+ popup.text = NULL;
+ }
+
+ if(!strlen(text)) {
+ return;
+ }
+
+ popup.text = CopyString(text);
+
+ popup.height = GetStringHeight(FONT_POPUP);
+ popup.width = GetStringWidth(FONT_POPUP, popup.text);
+
+ popup.height += 2;
+ popup.width += 8;
+
+ sp = GetCurrentScreen(x, y);
+
+ if(popup.width > sp->width) {
+ popup.width = sp->width;
+ }
+
+ popup.x = x;
+ popup.y = y - popup.height - 2;
+
+ if(popup.width + popup.x >= sp->width) {
+ popup.x = sp->width - popup.width - 2;
+ }
+ if(popup.height + popup.y >= sp->height) {
+ popup.y = sp->height - popup.height - 2;
+ }
+
+ if(popup.window == None) {
+
+ attrMask = 0;
+
+ attrMask |= CWEventMask;
+ attr.event_mask
+ = ExposureMask
+ | PointerMotionMask | PointerMotionHintMask;
+
+ attrMask |= CWSaveUnder;
+ attr.save_under = True;
+
+ attrMask |= CWBackPixel;
+ attr.background_pixel = colors[COLOR_POPUP_BG];
+
+ attrMask |= CWBorderPixel;
+ attr.border_pixel = colors[COLOR_POPUP_OUTLINE];
+
+ attrMask |= CWDontPropagate;
+ attr.do_not_propagate_mask
+ = PointerMotionMask
+ | ButtonPressMask
+ | ButtonReleaseMask;
+
+ popup.window = JXCreateWindow(display, rootWindow, popup.x, popup.y,
+ popup.width, popup.height, 1, CopyFromParent,
+ InputOutput, CopyFromParent, attrMask, &attr);
+
+ } else {
+ JXMoveResizeWindow(display, popup.window, popup.x, popup.y,
+ popup.width, popup.height);
+ }
+
+ popup.mx = x;
+ popup.my = y;
+
+ if(!popup.isActive) {
+ JXMapRaised(display, popup.window);
+ popup.isActive = 1;
+ } else {
+ DrawPopup();
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetPopupEnabled(int e) {
+ popupEnabled = e;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetPopupDelay(const char *str) {
+
+ int temp;
+
+ if(str == NULL) {
+ return;
+ }
+
+ temp = atoi(str);
+
+ if(temp < 0) {
+ Warning("invalid popup delay specified: %s\n", str);
+ } else {
+ popupDelay = temp;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SignalPopup(const TimeType *now, int x, int y) {
+
+ if(popup.isActive) {
+ if(abs(popup.mx - x) > 2 || abs(popup.my - y) > 2) {
+ JXUnmapWindow(display, popup.window);
+ popup.isActive = 0;
+ }
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int ProcessPopupEvent(const XEvent *event) {
+
+ if(popup.isActive && event->xany.window == popup.window) {
+ if(event->type == Expose) {
+ DrawPopup();
+ return 1;
+ } else if(event->type == MotionNotify) {
+ JXUnmapWindow(display, popup.window);
+ popup.isActive = 0;
+ return 1;
+ }
+ }
+
+ return 0;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DrawPopup() {
+
+ Assert(popup.isActive);
+
+ JXClearWindow(display, popup.window);
+ RenderString(popup.window, FONT_POPUP, COLOR_POPUP_FG, 4, 1,
+ popup.width, NULL, popup.text);
+
+}
+
diff --git a/src/popup.h b/src/popup.h
new file mode 100644
index 0000000..21e84db
--- /dev/null
+++ b/src/popup.h
@@ -0,0 +1,35 @@
+/**
+ * @file popup.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for popup functions.
+ *
+ */
+
+#ifndef POPUP_H
+#define POPUP_H
+
+#define POPUP_DELTA 2
+
+struct TimeType;
+
+/*@{*/
+void InitializePopup();
+void StartupPopup();
+void ShutdownPopup();
+void DestroyPopup();
+/*@}*/
+
+void ShowPopup(int x, int y, const char *text);
+
+void SetPopupEnabled(int e);
+void SetPopupDelay(const char *str);
+
+void SignalPopup(const struct TimeType *now, int x, int y);
+int ProcessPopupEvent(const XEvent *event);
+
+extern int popupDelay;
+
+#endif
+
diff --git a/src/render.c b/src/render.c
new file mode 100644
index 0000000..d2d747e
--- /dev/null
+++ b/src/render.c
@@ -0,0 +1,226 @@
+/****************************************************************************
+ * Functions to render icons using the XRender extension.
+ * Copyright (C) 2005 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "render.h"
+#include "icon.h"
+#include "image.h"
+#include "main.h"
+#include "color.h"
+#include "error.h"
+
+#ifdef USE_XRENDER
+static int haveRender = 0;
+#endif
+
+/****************************************************************************
+ ****************************************************************************/
+void QueryRenderExtension()
+{
+
+#ifdef USE_XRENDER
+ int event, error;
+ Bool rc;
+
+ rc = JXRenderQueryExtension(display, &event, &error);
+ if(rc == True) {
+ haveRender = 1;
+ Debug("render extension enabled");
+ } else {
+ haveRender = 0;
+ Debug("render extension disabled");
+ }
+
+ if(haveRender && rootDepth < 24) {
+ Warning("color depth is %d, disabling icon alpha channel", rootDepth);
+ haveRender = 0;
+ }
+
+#endif
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int PutScaledRenderIcon(IconNode *icon, ScaledIconNode *node, Drawable d,
+ int x, int y)
+{
+
+#ifdef USE_XRENDER
+
+ Picture dest;
+ Picture source;
+ XRenderPictFormat *fp;
+ int width, height;
+
+ Assert(icon);
+
+ if(!haveRender) {
+ return 0;
+ }
+
+ source = node->imagePicture;
+ if(source != None) {
+
+ fp = JXRenderFindVisualFormat(display, rootVisual);
+ Assert(fp);
+
+ dest = JXRenderCreatePicture(display, d, fp, 0, NULL);
+
+ if(node->width == 0) {
+ width = icon->image->width;
+ } else {
+ width = node->width;
+ }
+ if(node->height == 0) {
+ height = icon->image->height;
+ } else {
+ height = node->height;
+ }
+
+ JXRenderComposite(display, PictOpOver, source, None, dest,
+ 0, 0, 0, 0, x, y, width, height);
+
+ JXRenderFreePicture(display, dest);
+
+ }
+
+ return 1;
+
+#else
+
+ return 0;
+
+#endif
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+ScaledIconNode *CreateScaledRenderIcon(IconNode *icon,
+ int width, int height) {
+
+ ScaledIconNode *result = NULL;
+
+#ifdef USE_XRENDER
+
+ XRenderPictureAttributes picAttributes;
+ XRenderPictFormat picFormat;
+ XRenderPictFormat *fp;
+ XColor color;
+ GC maskGC;
+ XImage *destImage;
+ XImage *destMask;
+ unsigned long alpha;
+ int index;
+ int x, y;
+ double scalex, scaley;
+ double srcx, srcy;
+ int imageLine;
+ int maskLine;
+
+ Assert(icon);
+
+ if(!haveRender) {
+ return NULL;
+ }
+
+ result = Allocate(sizeof(ScaledIconNode));
+ result->next = icon->nodes;
+ icon->nodes = result;
+
+ if(width == 0) {
+ width = icon->image->width;
+ }
+ if(height == 0) {
+ height = icon->image->height;
+ }
+ result->width = width;
+ result->height = height;
+
+ scalex = (double)icon->image->width / width;
+ scaley = (double)icon->image->height / height;
+
+ result->mask = JXCreatePixmap(display, rootWindow, width, height, 8);
+ maskGC = JXCreateGC(display, result->mask, 0, NULL);
+ result->image = JXCreatePixmap(display, rootWindow,
+ width, height, rootDepth);
+
+ destImage = JXCreateImage(display, rootVisual, rootDepth, ZPixmap, 0,
+ NULL, width, height, 8, 0);
+ destImage->data = Allocate(sizeof(unsigned long) * width * height);
+
+ destMask = JXCreateImage(display, rootVisual, 8, ZPixmap, 0,
+ NULL, width, height, 8, 0);
+ destMask->data = Allocate(width * height);
+
+ imageLine = 0;
+ maskLine = 0;
+ srcy = 0.0;
+ for(y = 0; y < height; y++) {
+ srcx = 0.0;
+ for(x = 0; x < width; x++) {
+
+ index = (int)srcy * icon->image->width + (int)srcx;
+ alpha = (icon->image->data[index] >> 24) & 0xFFUL;
+ color.red = ((icon->image->data[index] >> 16) & 0xFFUL) * 257;
+ color.green = ((icon->image->data[index] >> 8) & 0xFFUL) * 257;
+ color.blue = (icon->image->data[index] & 0xFFUL) * 257;
+
+ GetColor(&color);
+ XPutPixel(destImage, x, y, color.pixel);
+ destMask->data[maskLine + x] = alpha * 257;
+
+ srcx += scalex;
+
+ }
+ srcy += scaley;
+ imageLine += destImage->bytes_per_line;
+ maskLine += destMask->bytes_per_line;
+ }
+
+ /* Render the image data to the image pixmap. */
+ JXPutImage(display, result->image, rootGC, destImage, 0, 0, 0, 0,
+ width, height);
+ Release(destImage->data);
+ destImage->data = NULL;
+ JXDestroyImage(destImage);
+
+ /* Render the alpha data to the mask pixmap. */
+ JXPutImage(display, result->mask, maskGC, destMask, 0, 0, 0, 0,
+ width, height);
+ Release(destMask->data);
+ destMask->data = NULL;
+ JXDestroyImage(destMask);
+ JXFreeGC(display, maskGC);
+
+ /* Create the render picture. */
+ picFormat.type = PictTypeDirect;
+ picFormat.depth = 8;
+ picFormat.direct.alphaMask = 0xFF;
+ fp = JXRenderFindFormat(display,
+ PictFormatType | PictFormatDepth | PictFormatAlphaMask,
+ &picFormat, 0);
+ Assert(fp);
+ result->maskPicture = JXRenderCreatePicture(display, result->mask,
+ fp, 0, NULL);
+ picAttributes.alpha_map = result->maskPicture;
+ fp = JXRenderFindVisualFormat(display, rootVisual);
+ Assert(fp);
+ result->imagePicture = JXRenderCreatePicture(display, result->image,
+ fp, CPAlphaMap, &picAttributes);
+
+ /* Free unneeded pixmaps. */
+ JXFreePixmap(display, result->image);
+ result->image = None;
+ JXFreePixmap(display, result->mask);
+ result->mask = None;
+
+#endif
+
+ return result;
+
+}
+
diff --git a/src/render.h b/src/render.h
new file mode 100644
index 0000000..1e7164b
--- /dev/null
+++ b/src/render.h
@@ -0,0 +1,25 @@
+/**
+ * @file render.h
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Functions to render icons using the XRender extension.
+ *
+ */
+
+#ifndef RENDER_H
+#define RENDER_H
+
+struct IconNode;
+struct ScaledIconNode;
+
+void QueryRenderExtension();
+
+int PutScaledRenderIcon(struct IconNode *icon, struct ScaledIconNode *node,
+ Drawable d, int x, int y);
+
+struct ScaledIconNode *CreateScaledRenderIcon(struct IconNode *icon,
+ int width, int height);
+
+#endif
+
diff --git a/src/resize.c b/src/resize.c
new file mode 100644
index 0000000..c7961f9
--- /dev/null
+++ b/src/resize.c
@@ -0,0 +1,491 @@
+/**
+ * @file resize.c
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Functions to handle resizing client windows.
+ *
+ */
+
+#include "jwm.h"
+#include "resize.h"
+#include "client.h"
+#include "outline.h"
+#include "main.h"
+#include "cursor.h"
+#include "misc.h"
+#include "pager.h"
+#include "status.h"
+#include "key.h"
+#include "event.h"
+#include "border.h"
+
+static ResizeModeType resizeMode = RESIZE_OPAQUE;
+
+static int shouldStopResize;
+
+static void StopResize(ClientNode *np);
+static void ResizeController(int wasDestroyed);
+static void FixWidth(ClientNode *np);
+static void FixHeight(ClientNode *np);
+
+/** Set the resize mode to use. */
+void SetResizeMode(ResizeModeType mode) {
+ resizeMode = mode;
+}
+
+/** Callback to stop a resize. */
+void ResizeController(int wasDestroyed) {
+ if(resizeMode == RESIZE_OUTLINE) {
+ ClearOutline();
+ }
+ JXUngrabPointer(display, CurrentTime);
+ JXUngrabKeyboard(display, CurrentTime);
+ DestroyResizeWindow();
+ shouldStopResize = 1;
+}
+
+/** Resize a client window (mouse initiated). */
+void ResizeClient(ClientNode *np, BorderActionType action,
+ int startx, int starty) {
+
+ XEvent event;
+ int oldx, oldy;
+ int oldw, oldh;
+ int gwidth, gheight;
+ int lastgwidth, lastgheight;
+ int delta;
+ int north, south, east, west;
+ float ratio, minr, maxr;
+
+ Assert(np);
+
+ if(!(np->state.border & BORDER_RESIZE)) {
+ return;
+ }
+
+ if(!GrabMouseForResize(action)) {
+ Debug("ResizeClient: could not grab mouse");
+ return;
+ }
+
+ if(np->state.status & STAT_SHADED) {
+ action &= ~(BA_RESIZE_N | BA_RESIZE_S);
+ }
+
+ np->controller = ResizeController;
+ shouldStopResize = 0;
+
+ oldx = np->x;
+ oldy = np->y;
+ oldw = np->width;
+ oldh = np->height;
+
+ gwidth = (np->width - np->baseWidth) / np->xinc;
+ gheight = (np->height - np->baseHeight) / np->yinc;
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ startx += np->x - west;
+ starty += np->y - north;
+
+ CreateResizeWindow(np);
+ UpdateResizeWindow(np, gwidth, gheight);
+
+ if(!(GetMouseMask() & Button1Mask)) {
+ StopResize(np);
+ return;
+ }
+
+ for(;;) {
+
+ WaitForEvent(&event);
+
+ if(shouldStopResize) {
+ np->controller = NULL;
+ return;
+ }
+
+ switch(event.type) {
+ case ButtonRelease:
+ if(event.xbutton.button == Button1) {
+ StopResize(np);
+ return;
+ }
+ break;
+ case MotionNotify:
+
+ SetMousePosition(event.xmotion.x_root, event.xmotion.y_root);
+ DiscardMotionEvents(&event, np->window);
+
+ if(action & BA_RESIZE_N) {
+ delta = (event.xmotion.y - starty) / np->yinc;
+ delta *= np->yinc;
+ if(oldh - delta >= np->minHeight
+ && (oldh - delta <= np->maxHeight || delta > 0)) {
+ np->height = oldh - delta;
+ np->y = oldy + delta;
+ }
+ if(!(action & (BA_RESIZE_E | BA_RESIZE_W))) {
+ FixWidth(np);
+ }
+ }
+ if(action & BA_RESIZE_S) {
+ delta = (event.xmotion.y - starty) / np->yinc;
+ delta *= np->yinc;
+ np->height = oldh + delta;
+ np->height = Max(np->height, np->minHeight);
+ np->height = Min(np->height, np->maxHeight);
+ if(!(action & (BA_RESIZE_E | BA_RESIZE_W))) {
+ FixWidth(np);
+ }
+ }
+ if(action & BA_RESIZE_E) {
+ delta = (event.xmotion.x - startx) / np->xinc;
+ delta *= np->xinc;
+ np->width = oldw + delta;
+ np->width = Max(np->width, np->minWidth);
+ np->width = Min(np->width, np->maxWidth);
+ if(!(action & (BA_RESIZE_N | BA_RESIZE_S))) {
+ FixHeight(np);
+ }
+ }
+ if(action & BA_RESIZE_W) {
+ delta = (event.xmotion.x - startx) / np->xinc;
+ delta *= np->xinc;
+ if(oldw - delta >= np->minWidth
+ && (oldw - delta <= np->maxWidth || delta > 0)) {
+ np->width = oldw - delta;
+ np->x = oldx + delta;
+ }
+ if(!(action & (BA_RESIZE_N | BA_RESIZE_S))) {
+ FixHeight(np);
+ }
+ }
+
+ if(np->sizeFlags & PAspect) {
+ if((action & (BA_RESIZE_N | BA_RESIZE_S)) &&
+ (action & (BA_RESIZE_E | BA_RESIZE_W))) {
+
+ ratio = (float)np->width / np->height;
+
+ minr = (float)np->aspect.minx / np->aspect.miny;
+ if(ratio < minr) {
+ delta = np->width;
+ np->width = (int)((float)np->height * minr);
+ if(action & BA_RESIZE_W) {
+ np->x -= np->width - delta;
+ }
+ }
+
+ maxr = (float)np->aspect.maxx / np->aspect.maxy;
+ if(ratio > maxr) {
+ delta = np->height;
+ np->height = (int)((float)np->width / maxr);
+ if(action & BA_RESIZE_N) {
+ np->y -= np->height - delta;
+ }
+ }
+
+ }
+ }
+
+ lastgwidth = gwidth;
+ lastgheight = gheight;
+
+ gwidth = (np->width - np->baseWidth) / np->xinc;
+ gheight = (np->height - np->baseHeight) / np->yinc;
+
+ if(lastgheight != gheight || lastgwidth != gwidth) {
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ np->state.status &= ~STAT_MAXIMIZED;
+ WriteState(np);
+ SendConfigureEvent(np);
+ }
+
+ UpdateResizeWindow(np, gwidth, gheight);
+
+ if(resizeMode == RESIZE_OUTLINE) {
+ ClearOutline();
+ if(np->state.status & STAT_SHADED) {
+ DrawOutline(np->x - west, np->y - north,
+ np->width + west + east, north + south);
+ } else {
+ DrawOutline(np->x - west, np->y - north,
+ np->width + west + east,
+ np->height + north + south);
+ }
+ } else {
+ if(np->state.status & STAT_SHADED) {
+ JXMoveResizeWindow(display, np->parent,
+ np->x - west, np->y - north,
+ np->width + west + east, north + south);
+ } else {
+ JXMoveResizeWindow(display, np->parent,
+ np->x - west, np->y - north,
+ np->width + west + east,
+ np->height + north + south);
+ }
+ JXMoveResizeWindow(display, np->window, west,
+ north, np->width, np->height);
+ SendConfigureEvent(np);
+ }
+
+ UpdatePager();
+
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+}
+
+/** Resize a client window (keyboard or menu initiated). */
+void ResizeClientKeyboard(ClientNode *np) {
+
+ XEvent event;
+ int gwidth, gheight;
+ int lastgwidth, lastgheight;
+ int north, south, east, west;
+ int deltax, deltay;
+ float ratio, minr, maxr;
+
+ Assert(np);
+
+ if(!(np->state.border & BORDER_RESIZE)) {
+ return;
+ }
+
+ if(JXGrabKeyboard(display, np->window, True, GrabModeAsync,
+ GrabModeAsync, CurrentTime) != GrabSuccess) {
+ Debug("ResizeClientKeyboard: could not grab keyboard");
+ return;
+ }
+ GrabMouseForResize(BA_RESIZE_S | BA_RESIZE_E | BA_RESIZE);
+
+ np->controller = ResizeController;
+ shouldStopResize = 0;
+
+ gwidth = (np->width - np->baseWidth) / np->xinc;
+ gheight = (np->height - np->baseHeight) / np->yinc;
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ CreateResizeWindow(np);
+ UpdateResizeWindow(np, gwidth, gheight);
+
+ MoveMouse(rootWindow, np->x + np->width, np->y + np->height);
+ DiscardMotionEvents(&event, np->window);
+
+ for(;;) {
+
+ WaitForEvent(&event);
+
+ if(shouldStopResize) {
+ np->controller = NULL;
+ return;
+ }
+
+ deltax = 0;
+ deltay = 0;
+
+ if(event.type == KeyPress) {
+
+ while(JXCheckTypedWindowEvent(display, np->window, KeyPress, &event));
+
+ switch(GetKey(&event.xkey) & 0xFF) {
+ case KEY_UP:
+ deltay = Min(-np->yinc, -10);
+ break;
+ case KEY_DOWN:
+ deltay = Max(np->yinc, 10);
+ break;
+ case KEY_RIGHT:
+ deltax = Max(np->xinc, 10);
+ break;
+ case KEY_LEFT:
+ deltax = Min(-np->xinc, -10);
+ break;
+ default:
+ StopResize(np);
+ return;
+ }
+
+ } else if(event.type == MotionNotify) {
+
+ SetMousePosition(event.xmotion.x_root, event.xmotion.y_root);
+ DiscardMotionEvents(&event, np->window);
+
+ deltax = event.xmotion.x - (np->x + np->width);
+ deltay = event.xmotion.y - (np->y + np->height);
+
+ } else if(event.type == ButtonRelease) {
+
+ StopResize(np);
+ return;
+
+ }
+
+ if(abs(deltax) < np->xinc && abs(deltay) < np->yinc) {
+ continue;
+ }
+
+ deltay -= deltay % np->yinc;
+ np->height += deltay;
+ np->height = Max(np->height, np->minHeight);
+ np->height = Min(np->height, np->maxHeight);
+ deltax -= deltax % np->xinc;
+ np->width += deltax;
+ np->width = Max(np->width, np->minWidth);
+ np->width = Min(np->width, np->maxWidth);
+
+ if(np->sizeFlags & PAspect) {
+
+ ratio = (float)np->width / np->height;
+
+ minr = (float)np->aspect.minx / np->aspect.miny;
+ if(ratio < minr) {
+ np->width = (int)((float)np->height * minr);
+ }
+
+ maxr = (float)np->aspect.maxx / np->aspect.maxy;
+ if(ratio > maxr) {
+ np->height = (int)((float)np->width / maxr);
+ }
+
+ }
+
+ lastgwidth = gwidth;
+ lastgheight = gheight;
+ gwidth = (np->width - np->baseWidth) / np->xinc;
+ gheight = (np->height - np->baseHeight) / np->yinc;
+
+ if(lastgwidth != gwidth || lastgheight != gheight) {
+
+ if(np->state.status & STAT_MAXIMIZED) {
+ np->state.status &= ~STAT_MAXIMIZED;
+ WriteState(np);
+ SendConfigureEvent(np);
+ }
+
+ UpdateResizeWindow(np, gwidth, gheight);
+
+ if(resizeMode == RESIZE_OUTLINE) {
+ ClearOutline();
+ if(np->state.status & STAT_SHADED) {
+ DrawOutline(np->x - west, np->y - north,
+ np->width + west + east,
+ north + south);
+ } else {
+ DrawOutline(np->x - west, np->y - north,
+ np->width + west + east,
+ np->height + north + south);
+ }
+ } else {
+ if(np->state.status & STAT_SHADED) {
+ JXResizeWindow(display, np->parent,
+ np->width + west + east, north + south);
+ } else {
+ JXResizeWindow(display, np->parent,
+ np->width + west + east, np->height + north + south);
+ }
+ JXResizeWindow(display, np->window, np->width, np->height);
+ SendConfigureEvent(np);
+ }
+
+ UpdatePager();
+
+ }
+
+ }
+
+}
+
+/** Stop a resize action. */
+void StopResize(ClientNode *np) {
+
+ int north, south, east, west;
+
+ np->controller = NULL;
+
+ if(resizeMode == RESIZE_OUTLINE) {
+ ClearOutline();
+ }
+
+ JXUngrabPointer(display, CurrentTime);
+ JXUngrabKeyboard(display, CurrentTime);
+
+ DestroyResizeWindow();
+
+ GetBorderSize(np, &north, &south, &east, &west);
+
+ if(np->state.status & STAT_SHADED) {
+ JXMoveResizeWindow(display, np->parent,
+ np->x - west, np->y - north,
+ np->width + east + west, north + south);
+ } else {
+ JXMoveResizeWindow(display, np->parent,
+ np->x - west, np->y - north,
+ np->width + east + west,
+ np->height + north + south);
+ }
+ JXMoveResizeWindow(display, np->window, west,
+ north, np->width, np->height);
+ SendConfigureEvent(np);
+
+}
+
+/** Fix the width to match the aspect ratio. */
+void FixWidth(ClientNode *np) {
+
+ float ratio, minr, maxr;
+
+ Assert(np);
+
+ if((np->sizeFlags & PAspect) && np->height > 0) {
+
+ ratio = (float)np->width / np->height;
+
+ minr = (float)np->aspect.minx / np->aspect.miny;
+ if(ratio < minr) {
+ np->width = (int)((float)np->height * minr);
+ }
+
+ maxr = (float)np->aspect.maxx / np->aspect.maxy;
+ if(ratio > maxr) {
+ np->width = (int)((float)np->height * maxr);
+ }
+
+ }
+
+}
+
+/** Fix the height to match the aspect ratio. */
+void FixHeight(ClientNode *np) {
+
+ float ratio, minr, maxr;
+
+ Assert(np);
+
+ if((np->sizeFlags & PAspect) && np->height > 0) {
+
+ ratio = (float)np->width / np->height;
+
+ minr = (float)np->aspect.minx / np->aspect.miny;
+ if(ratio < minr) {
+ np->height = (int)((float)np->width / minr);
+ }
+
+ maxr = (float)np->aspect.maxx / np->aspect.maxy;
+ if(ratio > maxr) {
+ np->height = (int)((float)np->width / maxr);
+ }
+
+ }
+
+}
+
diff --git a/src/resize.h b/src/resize.h
new file mode 100644
index 0000000..92a407f
--- /dev/null
+++ b/src/resize.h
@@ -0,0 +1,42 @@
+/**
+ * @file resize.h
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Header for client window resize functions.
+ *
+ */
+
+#ifndef RESIZE_H
+#define RESIZE_H
+
+#include "border.h"
+
+struct ClientNode;
+
+typedef enum {
+ RESIZE_OPAQUE, /**< Show window contents while resizing. */
+ RESIZE_OUTLINE /**< Show an outline while resizing. */
+} ResizeModeType;
+
+/** Resize a client window.
+ * @param np The client to resize.
+ * @param action The location on the border where the move should take place.
+ * @param startx The starting mouse x-coordinate (window relative).
+ * @param starty The starting mouse y-coordinate (window relative).
+ */
+void ResizeClient(struct ClientNode *np, BorderActionType action,
+ int startx, int starty);
+
+/** Resize a client window using the keyboard (mouse optional).
+ * @param np The client to resize.
+ */
+void ResizeClientKeyboard(struct ClientNode *np);
+
+/** Set the resize mode to use.
+ * @param mode The resize mode to use.
+ */
+void SetResizeMode(ResizeModeType mode);
+
+#endif
+
diff --git a/src/root.c b/src/root.c
new file mode 100644
index 0000000..b110018
--- /dev/null
+++ b/src/root.c
@@ -0,0 +1,315 @@
+/***************************************************************************
+ * Functions to handle the root menu.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "root.h"
+#include "menu.h"
+#include "client.h"
+#include "main.h"
+#include "error.h"
+#include "confirm.h"
+#include "desktop.h"
+#include "misc.h"
+#include "winmenu.h"
+
+/* Allow for menus 0 to 9. */
+#define ROOT_MENU_COUNT 10
+
+static Menu *rootMenu[ROOT_MENU_COUNT];
+static int showExitConfirmation = 1;
+
+static void ExitHandler(ClientNode *np);
+static void PatchRootMenu(Menu *menu);
+static void UnpatchRootMenu(Menu *menu);
+
+static void RunRootCommand(const MenuAction *action);
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeRootMenu() {
+
+ int x;
+
+ for(x = 0; x < ROOT_MENU_COUNT; x++) {
+ rootMenu[x] = NULL;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void StartupRootMenu() {
+
+ int x, y;
+ int found;
+
+ for(x = 0; x < ROOT_MENU_COUNT; x++) {
+ if(rootMenu[x]) {
+ found = 0;
+ for(y = 0; y < x; y++) {
+ if(rootMenu[y] == rootMenu[x]) {
+ found = 1;
+ break;
+ }
+ }
+ if(!found) {
+ InitializeMenu(rootMenu[x]);
+ }
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShutdownRootMenu() {
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyRootMenu() {
+
+ int x, y;
+
+ for(x = 0; x < ROOT_MENU_COUNT; x++) {
+ if(rootMenu[x]) {
+ DestroyMenu(rootMenu[x]);
+ for(y = x + 1; y < ROOT_MENU_COUNT; y++) {
+ if(rootMenu[x] == rootMenu[y]) {
+ rootMenu[y] = NULL;
+ }
+ }
+ rootMenu[x] = NULL;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetRootMenu(const char *indexes, Menu *m) {
+
+ int x, y;
+ int index;
+ int found;
+
+ /* Loop over each index to consider. */
+ for(x = 0; indexes[x]; x++) {
+
+ /* Get the index and make sure it's in range. */
+ index = indexes[x] - '0';
+ if(index < 0 || index >= ROOT_MENU_COUNT) {
+ Warning("invalid root menu specified: \"%c\"", indexes[x]);
+ continue;
+ }
+
+ if(rootMenu[index] && rootMenu[index] != m) {
+
+ /* See if replacing this value will cause an orphan. */
+ found = 0;
+ for(y = 0; y < ROOT_MENU_COUNT; y++) {
+ if(x != y && rootMenu[y] == rootMenu[x]) {
+ found = 1;
+ break;
+ }
+ }
+
+ /* If we have an orphan, destroy it. */
+ if(!found) {
+ DestroyMenu(rootMenu[index]);
+ }
+
+ }
+
+ rootMenu[index] = m;
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetShowExitConfirmation(int v) {
+ showExitConfirmation = v;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int IsRootMenuDefined(int index) {
+ if(index >= 0 && index < ROOT_MENU_COUNT && rootMenu[index]) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void GetRootMenuSize(int index, int *width, int *height) {
+
+ if(!rootMenu[index]) {
+ *width = 0;
+ *height = 0;
+ return;
+ }
+
+ PatchRootMenu(rootMenu[index]);
+ *width = rootMenu[index]->width;
+ *height = rootMenu[index]->height;
+ UnpatchRootMenu(rootMenu[index]);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ShowRootMenu(int index, int x, int y) {
+
+ if(!rootMenu[index]) {
+ return 0;
+ }
+
+ PatchRootMenu(rootMenu[index]);
+ ShowMenu(rootMenu[index], RunRootCommand, x, y);
+ UnpatchRootMenu(rootMenu[index]);
+
+ return 1;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void PatchRootMenu(Menu *menu) {
+
+ MenuItem *item;
+
+ for(item = menu->items; item; item = item->next) {
+ if(item->submenu) {
+ PatchRootMenu(item->submenu);
+ }
+ if(item->action.type == MA_DESKTOP) {
+ item->submenu = CreateDesktopMenu(1 << currentDesktop);
+ InitializeMenu(item->submenu);
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void UnpatchRootMenu(Menu *menu) {
+
+ MenuItem *item;
+
+ for(item = menu->items; item; item = item->next) {
+ if(item->action.type == MA_DESKTOP) {
+ DestroyMenu(item->submenu);
+ item->submenu = NULL;
+ } else if(item->submenu) {
+ UnpatchRootMenu(item->submenu);
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ExitHandler(ClientNode *np) {
+ shouldExit = 1;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Restart() {
+ shouldRestart = 1;
+ shouldExit = 1;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Exit() {
+ if(showExitConfirmation) {
+ ShowConfirmDialog(NULL, ExitHandler,
+ "Exit JWM",
+ "Are you sure?",
+ NULL);
+ } else {
+ ExitHandler(NULL);
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void RunRootCommand(const MenuAction *action) {
+
+ switch(action->type) {
+
+ case MA_EXECUTE:
+ RunCommand(action->data.str);
+ break;
+ case MA_RESTART:
+ Restart();
+ break;
+ case MA_EXIT:
+ if(exitCommand) {
+ Release(exitCommand);
+ }
+ exitCommand = CopyString(action->data.str);
+ Exit();
+ break;
+ case MA_DESKTOP:
+ ChangeDesktop(action->data.i);
+ break;
+
+ case MA_SENDTO:
+ case MA_LAYER:
+ case MA_MAXIMIZE:
+ case MA_MINIMIZE:
+ case MA_RESTORE:
+ case MA_SHADE:
+ case MA_MOVE:
+ case MA_RESIZE:
+ case MA_KILL:
+ case MA_CLOSE:
+ ChooseWindow(action);
+ break;
+
+ default:
+ Debug("invalid RunRootCommand action: %d", action->type);
+ break;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void RunCommand(const char *command) {
+ char *displayString;
+ char *str;
+
+ if(!command) {
+ return;
+ }
+
+ displayString = DisplayString(display);
+
+ if(!fork()) {
+ if(!fork()) {
+ close(ConnectionNumber(display));
+ if(displayString && displayString[0]) {
+ str = malloc(strlen(displayString) + 9);
+ sprintf(str, "DISPLAY=%s", displayString);
+ putenv(str);
+ }
+ execl(SHELL_NAME, SHELL_NAME, "-c", command, NULL);
+ Warning("exec failed: (%s) %s", SHELL_NAME, command);
+ exit(1);
+ }
+ exit(0);
+ }
+
+ wait(NULL);
+
+}
+
diff --git a/src/root.h b/src/root.h
new file mode 100644
index 0000000..b546156
--- /dev/null
+++ b/src/root.h
@@ -0,0 +1,65 @@
+/**
+ * @file root.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the root menu functions.
+ *
+ */
+
+#ifndef ROOT_H
+#define ROOT_H
+
+struct Menu;
+
+/*@{*/
+void InitializeRootMenu();
+void StartupRootMenu();
+void ShutdownRootMenu();
+void DestroyRootMenu();
+/*@}*/
+
+/** Set the root menu to be used for the specified indexes.
+ * @param indexes The indexes (ASCII string of '0' to '9').
+ * @param m The menu to use for the specified indexes.
+ */
+void SetRootMenu(const char *indexes, struct Menu *m);
+
+/** Set whether a confirmation dialog is displayed on exit.
+ * @param v 1 to display confirmation, 0 to just exit.
+ */
+void SetShowExitConfirmation(int v);
+
+/** Determine if a root menu is defined for the specified index.
+ * @return 1 if it is defined, 0 if not.
+ */
+int IsRootMenuDefined(int index);
+
+/** Get the size of a root menu.
+ * @param index The root menu index.
+ * @param width The width output.
+ * @param height The height output.
+ */
+void GetRootMenuSize(int index, int *width, int *height);
+
+/** Show a root menu.
+ * @param index The root menu index.
+ * @param x The x-coordinate.
+ * @param y The y-coordinate.
+ * @return 1 if a menu was displayed, 0 if not.
+ */
+int ShowRootMenu(int index, int x, int y);
+
+/** Run a command.
+ * @param command The command to run (run in sh).
+ */
+void RunCommand(const char *command);
+
+/** Restart the window manager. */
+void Restart();
+
+/** Exit the window manager. */
+void Exit();
+
+#endif
+
diff --git a/src/screen.c b/src/screen.c
new file mode 100644
index 0000000..cc2b8fd
--- /dev/null
+++ b/src/screen.c
@@ -0,0 +1,138 @@
+/****************************************************************************
+ * Screen functions.
+ * Copyright (C) 2005 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "screen.h"
+#include "main.h"
+#include "cursor.h"
+
+static ScreenType *screens;
+static int screenCount;
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeScreens() {
+ screens = NULL;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupScreens() {
+#ifdef USE_XINERAMA
+
+ XineramaScreenInfo *info;
+ int x;
+
+ if(XineramaIsActive(display)) {
+
+ info = XineramaQueryScreens(display, &screenCount);
+
+ screens = Allocate(sizeof(ScreenType) * screenCount);
+ for(x = 0; x < screenCount; x++) {
+ screens[x].index = x;
+ screens[x].x = info[x].x_org;
+ screens[x].y = info[x].y_org;
+ screens[x].width = info[x].width;
+ screens[x].height = info[x].height;
+ }
+
+ JXFree(info);
+
+ } else {
+
+ screenCount = 1;
+ screens = Allocate(sizeof(ScreenType));
+ screens->index = 0;
+ screens->x = 0;
+ screens->y = 0;
+ screens->width = rootWidth;
+ screens->height = rootHeight;
+
+ }
+
+#else
+
+ screenCount = 1;
+ screens = Allocate(sizeof(ScreenType));
+ screens->index = 0;
+ screens->x = 0;
+ screens->y = 0;
+ screens->width = rootWidth;
+ screens->height = rootHeight;
+
+#endif /* USE_XINERAMA */
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownScreens() {
+ if(screens) {
+ Release(screens);
+ screens = NULL;
+ }
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyScreens() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+const ScreenType *GetCurrentScreen(int x, int y) {
+
+ ScreenType *sp;
+ int index;
+
+ for(index = 1; index < screenCount; index++) {
+ sp = &screens[index];
+ if(x >= sp->x && x < sp->x + sp->width) {
+ if(y >= sp->y && y <= sp->y + sp->height) {
+ return sp;
+ }
+ }
+ }
+
+ return &screens[0];
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+const ScreenType *GetMouseScreen() {
+#ifdef USE_XINERAMA
+
+ int x, y;
+
+ GetMousePosition(&x, &y);
+ return GetCurrentScreen(x, y);
+
+#else
+
+ return &screens[0];
+
+#endif
+}
+
+/****************************************************************************
+ ****************************************************************************/
+const ScreenType *GetScreen(int index) {
+
+ Assert(index >= 0);
+ Assert(index < screenCount);
+
+ return &screens[index];
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+int GetScreenCount() {
+
+ return screenCount;
+
+}
+
+
diff --git a/src/screen.h b/src/screen.h
new file mode 100644
index 0000000..2d9a351
--- /dev/null
+++ b/src/screen.h
@@ -0,0 +1,53 @@
+/**
+ * @file screen.h
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Header for screen functions.
+ *
+ * Note that screen here refers to physical monitors. Screens are
+ * determined using the xinerama extension (if available). There will
+ * always be at least one screen.
+ *
+ */
+
+#ifndef SCREEN_H
+#define SCREEN_H
+
+/** Structure to contain information about a screen. */
+typedef struct ScreenType {
+ int index; /**< The index of this screen. */
+ int x, y; /**< The location of this screen. */
+ int width, height; /**< The size of this screen. */
+} ScreenType;
+
+void InitializeScreens();
+void StartupScreens();
+void ShutdownScreens();
+void DestroyScreens();
+
+/** Get the screen of the specified coordinates.
+ * @param x The x-coordinate.
+ * @param y The y-coordinate.
+ * @return The screen.
+ */
+const ScreenType *GetCurrentScreen(int x, int y);
+
+/** Get the screen containing the mouse.
+ * @return The screen containing the mouse.
+ */
+const ScreenType *GetMouseScreen();
+
+/** Get the screen of the specified index.
+ * @param index The screen index (0 based).
+ * @return The screen.
+ */
+const ScreenType *GetScreen(int index);
+
+/** Get the number of screens.
+ * @return The number of screens.
+ */
+int GetScreenCount();
+
+#endif
+
diff --git a/src/status.c b/src/status.c
new file mode 100644
index 0000000..88c7ddb
--- /dev/null
+++ b/src/status.c
@@ -0,0 +1,274 @@
+/*************************************************************************
+ * Functions for displaying window move/resize status.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ *************************************************************************/
+
+#include "jwm.h"
+#include "status.h"
+#include "font.h"
+#include "screen.h"
+#include "color.h"
+#include "main.h"
+#include "client.h"
+#include "error.h"
+
+typedef enum {
+ SW_INVALID,
+ SW_OFF,
+ SW_SCREEN,
+ SW_WINDOW,
+ SW_CORNER
+} StatusWindowType;
+
+static Window statusWindow;
+static unsigned int statusWindowHeight;
+static unsigned int statusWindowWidth;
+static int statusWindowX, statusWindowY;
+static StatusWindowType moveStatusType;
+static StatusWindowType resizeStatusType;
+
+static void CreateMoveResizeWindow(const ClientNode *np,
+ StatusWindowType type);
+static void DrawMoveResizeWindow(const ClientNode *np, StatusWindowType type);
+static void DestroyMoveResizeWindow();
+static void GetMoveResizeCoordinates(const ClientNode *np,
+ StatusWindowType type, int *x, int *y);
+static StatusWindowType ParseType(const char *str);
+
+/*************************************************************************
+ *************************************************************************/
+void GetMoveResizeCoordinates(const ClientNode *np, StatusWindowType type,
+ int *x, int *y) {
+
+ const ScreenType *sp;
+
+ if(type == SW_WINDOW) {
+ *x = np->x + np->width / 2 - statusWindowWidth / 2;
+ *y = np->y + np->height / 2 - statusWindowHeight / 2;
+ return;
+ }
+
+ sp = GetCurrentScreen(np->x, np->y);
+
+ if(type == SW_CORNER) {
+ *x = sp->x;
+ *y = sp->y;
+ return;
+ }
+
+ /* SW_SCREEN */
+
+ *x = sp->x + sp->width / 2 - statusWindowWidth / 2;
+ *y = sp->y + sp->height / 2 - statusWindowHeight / 2;
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void CreateMoveResizeWindow(const ClientNode *np, StatusWindowType type) {
+
+ XSetWindowAttributes attrs;
+
+ if(type == SW_OFF) {
+ return;
+ }
+
+ statusWindowHeight = GetStringHeight(FONT_MENU) + 8;
+ statusWindowWidth = GetStringWidth(FONT_MENU, " 00000 x 00000 ");
+
+ GetMoveResizeCoordinates(np, type, &statusWindowX, &statusWindowY);
+
+ attrs.background_pixel = colors[COLOR_MENU_BG];
+ attrs.save_under = True;
+ attrs.override_redirect = True;
+
+ statusWindow = JXCreateWindow(display, rootWindow,
+ statusWindowX, statusWindowY,
+ statusWindowWidth, statusWindowHeight, 0,
+ CopyFromParent, InputOutput, CopyFromParent,
+ CWBackPixel | CWOverrideRedirect | CWSaveUnder,
+ &attrs);
+
+ JXMapRaised(display, statusWindow);
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void DrawMoveResizeWindow(const ClientNode *np, StatusWindowType type) {
+
+ int x, y;
+
+ GetMoveResizeCoordinates(np, type, &x, &y);
+ if(x != statusWindowX || y != statusWindowX) {
+ statusWindowX = x;
+ statusWindowY = y;
+ JXMoveResizeWindow(display, statusWindow, x, y,
+ statusWindowWidth, statusWindowHeight);
+ }
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_BG]);
+ JXFillRectangle(display, statusWindow, rootGC, 2, 2,
+ statusWindowWidth - 3, statusWindowHeight - 3);
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]);
+ JXDrawLine(display, statusWindow, rootGC,
+ 0, 0, statusWindowWidth - 1, 0);
+ JXDrawLine(display, statusWindow, rootGC,
+ 0, 1, statusWindowWidth - 2, 1);
+ JXDrawLine(display, statusWindow, rootGC,
+ 0, 2, 0, statusWindowHeight - 1);
+ JXDrawLine(display, statusWindow, rootGC,
+ 1, 2, 1, statusWindowHeight - 2);
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]);
+ JXDrawLine(display, statusWindow, rootGC,
+ 1, statusWindowHeight - 1, statusWindowWidth - 1,
+ statusWindowHeight - 1);
+ JXDrawLine(display, statusWindow, rootGC,
+ 2, statusWindowHeight - 2, statusWindowWidth - 1,
+ statusWindowHeight - 2);
+ JXDrawLine(display, statusWindow, rootGC,
+ statusWindowWidth - 1, 1, statusWindowWidth - 1,
+ statusWindowHeight - 3);
+ JXDrawLine(display, statusWindow, rootGC,
+ statusWindowWidth - 2, 2, statusWindowWidth - 2,
+ statusWindowHeight - 3);
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void DestroyMoveResizeWindow() {
+
+ if(statusWindow != None) {
+ JXDestroyWindow(display, statusWindow);
+ statusWindow = None;
+ }
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void CreateMoveWindow(ClientNode *np) {
+
+ CreateMoveResizeWindow(np, moveStatusType);
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void UpdateMoveWindow(ClientNode *np) {
+
+ char str[80];
+ unsigned int width;
+
+ if(moveStatusType == SW_OFF) {
+ return;
+ }
+
+ DrawMoveResizeWindow(np, moveStatusType);
+
+ snprintf(str, sizeof(str), "(%d, %d)", np->x, np->y);
+ width = GetStringWidth(FONT_MENU, str);
+ RenderString(statusWindow, FONT_MENU, COLOR_MENU_FG,
+ statusWindowWidth / 2 - width / 2, 4, rootWidth, NULL, str);
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void DestroyMoveWindow() {
+
+ DestroyMoveResizeWindow();
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void CreateResizeWindow(ClientNode *np) {
+
+ CreateMoveResizeWindow(np, resizeStatusType);
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void UpdateResizeWindow(ClientNode *np, int gwidth, int gheight) {
+
+ char str[80];
+ unsigned int fontWidth;
+
+ if(resizeStatusType == SW_OFF) {
+ return;
+ }
+
+ DrawMoveResizeWindow(np, resizeStatusType);
+
+ snprintf(str, sizeof(str), "%d x %d", gwidth, gheight);
+ fontWidth = GetStringWidth(FONT_MENU, str);
+ RenderString(statusWindow, FONT_MENU, COLOR_MENU_FG,
+ statusWindowWidth / 2 - fontWidth / 2, 4, rootWidth, NULL, str);
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void DestroyResizeWindow() {
+
+ DestroyMoveResizeWindow();
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+StatusWindowType ParseType(const char *str) {
+
+ if(!str) {
+ return SW_SCREEN;
+ } else if(!strcmp(str, "off")) {
+ return SW_OFF;
+ } else if(!strcmp(str, "screen")) {
+ return SW_SCREEN;
+ } else if(!strcmp(str, "window")) {
+ return SW_WINDOW;
+ } else if(!strcmp(str, "corner")) {
+ return SW_CORNER;
+ } else {
+ return SW_INVALID;
+ }
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void SetMoveStatusType(const char *str) {
+
+ StatusWindowType type;
+
+ type = ParseType(str);
+ if(type == SW_INVALID) {
+ moveStatusType = SW_SCREEN;
+ Warning("invalid MoveMode coordinates: \"%s\"", str);
+ } else {
+ moveStatusType = type;
+ }
+
+}
+
+/*************************************************************************
+ *************************************************************************/
+void SetResizeStatusType(const char *str) {
+
+ StatusWindowType type;
+
+ type = ParseType(str);
+ if(type == SW_INVALID) {
+ resizeStatusType = SW_SCREEN;
+ Warning("invalid ResizeMode coordinates: \"%s\"", str);
+ } else {
+ resizeStatusType = type;
+ }
+
+}
+
diff --git a/src/status.h b/src/status.h
new file mode 100644
index 0000000..9fcabb8
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,55 @@
+/**
+ * @file status.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the status functions.
+ *
+ */
+
+#ifndef STATUS_H
+#define STATUS_H
+
+struct ClientNode;
+
+/** Create a move status window.
+ * @param np The client to be moved.
+ */
+void CreateMoveWindow(struct ClientNode *np);
+
+/** Update a move status window.
+ * @param np The client being moved.
+ */
+void UpdateMoveWindow(struct ClientNode *np);
+
+/** Destroy a move status window. */
+void DestroyMoveWindow();
+
+/** Create a resize status window.
+ * @param np The client being resized.
+ */
+void CreateResizeWindow(struct ClientNode *np);
+
+/** Update a resize status window.
+ * @param np The client being resized.
+ * @param gwidth The width to display.
+ * @param gheight The height to display.
+ */
+void UpdateResizeWindow(struct ClientNode *np, int gwidth, int gheight);
+
+/** Destroy a resize status window. */
+void DestroyResizeWindow();
+
+/** Set the location of move status windows.
+ * @param str The location (off, screen, window, or corner).
+ */
+void SetMoveStatusType(const char *str);
+
+/** Set the location of resize status windows.
+ * @param str The location (off, screen, window, or corner).
+ */
+void SetResizeStatusType(const char *str);
+
+#endif
+
+
diff --git a/src/swallow.c b/src/swallow.c
new file mode 100644
index 0000000..26836f1
--- /dev/null
+++ b/src/swallow.c
@@ -0,0 +1,274 @@
+/**
+ * @file swallow.c
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Swallow tray component.
+ *
+ */
+
+#include "jwm.h"
+#include "swallow.h"
+#include "main.h"
+#include "tray.h"
+#include "error.h"
+#include "root.h"
+#include "color.h"
+#include "client.h"
+#include "event.h"
+#include "misc.h"
+
+typedef struct SwallowNode {
+
+ TrayComponentType *cp;
+
+ char *name;
+ char *command;
+ int border;
+ int userWidth;
+ int userHeight;
+
+ struct SwallowNode *next;
+
+} SwallowNode;
+
+static SwallowNode *swallowNodes;
+
+static void Destroy(TrayComponentType *cp);
+static void Resize(TrayComponentType *cp);
+
+/** Initialize swallow data. */
+void InitializeSwallow() {
+ swallowNodes = NULL;
+}
+
+/** Start swallow processing. */
+void StartupSwallow() {
+
+ SwallowNode *np;
+
+ for(np = swallowNodes; np; np = np->next) {
+ if(np->command) {
+ RunCommand(np->command);
+ }
+ }
+
+}
+
+/** Stop swallow processing. */
+void ShutdownSwallow() {
+}
+
+/** Destroy swallow data. */
+void DestroySwallow() {
+
+ SwallowNode *np;
+
+ while(swallowNodes) {
+
+ np = swallowNodes->next;
+
+ Assert(swallowNodes->name);
+ Release(swallowNodes->name);
+
+ if(swallowNodes->command) {
+ Release(swallowNodes->command);
+ }
+
+ Release(swallowNodes);
+ swallowNodes = np;
+
+ }
+
+}
+
+/** Create a swallowed application tray component. */
+TrayComponentType *CreateSwallow(const char *name, const char *command,
+ int width, int height) {
+
+ TrayComponentType *cp;
+ SwallowNode *np;
+
+ if(!name) {
+ Warning("cannot swallow a client with no name");
+ return NULL;
+ }
+
+ /* Make sure this name isn't already used. */
+ for(np = swallowNodes; np; np = np->next) {
+ if(!strcmp(np->name, name)) {
+ Warning("cannot swallow the same client multiple times");
+ return NULL;
+ }
+ }
+
+ np = Allocate(sizeof(SwallowNode));
+ np->name = CopyString(name);
+ np->command = CopyString(command);
+
+ np->next = swallowNodes;
+ swallowNodes = np;
+
+ cp = CreateTrayComponent();
+ np->cp = cp;
+ cp->object = np;
+ cp->Destroy = Destroy;
+ cp->Resize = Resize;
+
+ if(width) {
+ cp->requestedWidth = width;
+ np->userWidth = 1;
+ } else {
+ cp->requestedWidth = 1;
+ np->userWidth = 0;
+ }
+ if(height) {
+ cp->requestedHeight = height;
+ np->userHeight = 1;
+ } else {
+ cp->requestedHeight = 1;
+ np->userHeight = 0;
+ }
+
+ return cp;
+
+}
+
+/** Process an event on a swallowed window. */
+int ProcessSwallowEvent(const XEvent *event) {
+
+ SwallowNode *np;
+ int width, height;
+
+ for(np = swallowNodes; np; np = np->next) {
+ if(event->xany.window == np->cp->window) {
+ switch(event->type) {
+ case DestroyNotify:
+ np->cp->window = None;
+ np->cp->requestedWidth = 1;
+ np->cp->requestedHeight = 1;
+ ResizeTray(np->cp->tray);
+ break;
+ case ResizeRequest:
+ np->cp->requestedWidth
+ = event->xresizerequest.width + np->border * 2;
+ np->cp->requestedHeight
+ = event->xresizerequest.height + np->border * 2;
+ ResizeTray(np->cp->tray);
+ break;
+ case ConfigureNotify:
+ /* I don't think this should be necessary, but somehow
+ * resize requests slip by sometimes... */
+ width = event->xconfigure.width + np->border * 2;
+ height = event->xconfigure.height + np->border * 2;
+ if( width != np->cp->requestedWidth
+ && height != np->cp->requestedHeight) {
+ np->cp->requestedWidth = width;
+ np->cp->requestedHeight = height;
+ ResizeTray(np->cp->tray);
+ }
+ break;
+ default:
+ break;
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+
+}
+
+/** Handle a tray resize. */
+void Resize(TrayComponentType *cp) {
+
+ int width, height;
+
+ SwallowNode *np = (SwallowNode*)cp->object;
+
+ if(cp->window != None) {
+
+ width = cp->width - np->border * 2;
+ height = cp->height - np->border * 2;
+
+ JXResizeWindow(display, cp->window, width, height);
+
+ }
+
+}
+
+/** Destroy a swallow tray component. */
+void Destroy(TrayComponentType *cp) {
+
+ ClientProtocolType protocols;
+
+ if(cp->window) {
+
+ JXReparentWindow(display, cp->window, rootWindow, 0, 0);
+ JXRemoveFromSaveSet(display, cp->window);
+
+ protocols = ReadWMProtocols(cp->window);
+ if(protocols & PROT_DELETE) {
+ SendClientMessage(cp->window, ATOM_WM_PROTOCOLS,
+ ATOM_WM_DELETE_WINDOW);
+ } else {
+ JXKillClient(display, cp->window);
+ }
+
+ }
+
+}
+
+/** Determine if this is a window to be swallowed, if it is, swallow it. */
+int CheckSwallowMap(const XMapEvent *event) {
+
+ SwallowNode *np;
+ XClassHint hint;
+ XWindowAttributes attr;
+
+ for(np = swallowNodes; np; np = np->next) {
+
+ if(np->cp->window != None) {
+ continue;
+ }
+
+ Assert(np->cp->tray->window != None);
+
+ if(JXGetClassHint(display, event->window, &hint)) {
+ if(!strcmp(hint.res_name, np->name)) {
+
+ /* Swallow the window. */
+ JXSelectInput(display, event->window,
+ StructureNotifyMask | ResizeRedirectMask);
+ JXAddToSaveSet(display, event->window);
+ JXSetWindowBorder(display, event->window, colors[COLOR_TRAY_BG]);
+ JXReparentWindow(display, event->window,
+ np->cp->tray->window, 0, 0);
+ JXMapRaised(display, event->window);
+ JXFree(hint.res_name);
+ JXFree(hint.res_class);
+ np->cp->window = event->window;
+
+ /* Update the size. */
+ JXGetWindowAttributes(display, event->window, &attr);
+ np->border = attr.border_width;
+ if(!np->userWidth) {
+ np->cp->requestedWidth = attr.width + 2 * np->border;
+ }
+ if(!np->userHeight) {
+ np->cp->requestedHeight = attr.height + 2 * np->border;
+ }
+
+ ResizeTray(np->cp->tray);
+
+ return 1;
+
+ }
+ }
+
+ }
+
+ return 0;
+
+}
+
diff --git a/src/swallow.h b/src/swallow.h
new file mode 100644
index 0000000..148bea6
--- /dev/null
+++ b/src/swallow.h
@@ -0,0 +1,43 @@
+/**
+ * @file swallow.h
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Swallow tray component.
+ *
+ */
+
+#ifndef SWALLOW_H
+#define SWALLOW_H
+
+/*@{*/
+void InitializeSwallow();
+void StartupSwallow();
+void ShutdownSwallow();
+void DestroySwallow();
+/*@}*/
+
+/** Create a swallowed application tray component.
+ * @param name The name of the application to swallow.
+ * @param command The command used to start the swallowed application.
+ * @param width The width to use (0 for default).
+ * @param height the height to use (0 for default).
+ */
+struct TrayComponentType *CreateSwallow(
+ const char *name, const char *command,
+ int width, int height);
+
+/** Determine if a map event was for a window that should be swallowed.
+ * @param event The map event.
+ * @return 1 if this window should be swallowed, 0 if not.
+ */
+int CheckSwallowMap(const XMapEvent *event);
+
+/** Process an event on a swallowed window.
+ * @param event The event to process.
+ * @return 1 if the event was for a swallowed window, 0 if not.
+ */
+int ProcessSwallowEvent(const XEvent *event);
+
+#endif
+
diff --git a/src/taskbar.c b/src/taskbar.c
new file mode 100644
index 0000000..f8ab13c
--- /dev/null
+++ b/src/taskbar.c
@@ -0,0 +1,936 @@
+/***************************************************************************
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "taskbar.h"
+#include "tray.h"
+#include "timing.h"
+#include "main.h"
+#include "client.h"
+#include "color.h"
+#include "popup.h"
+#include "button.h"
+#include "cursor.h"
+#include "icon.h"
+#include "error.h"
+#include "font.h"
+#include "winmenu.h"
+#include "screen.h"
+
+typedef enum {
+ INSERT_LEFT,
+ INSERT_RIGHT
+} InsertModeType;
+
+typedef struct TaskBarType {
+
+ TrayComponentType *cp;
+
+ int itemHeight;
+ LayoutType layout;
+
+ Pixmap buffer;
+
+ TimeType mouseTime;
+ int mousex, mousey;
+
+ unsigned int maxItemWidth;
+
+ struct TaskBarType *next;
+
+} TaskBarType;
+
+typedef struct Node {
+ ClientNode *client;
+ int y;
+ struct Node *next;
+ struct Node *prev;
+} Node;
+
+static char minimized_bitmap[] = {
+ 0x01, 0x03,
+ 0x07, 0x0F
+};
+
+static const int TASK_SPACER = 2;
+
+static Pixmap minimizedPixmap;
+static InsertModeType insertMode;
+
+static TaskBarType *bars;
+static Node *taskBarNodes;
+static Node *taskBarNodesTail;
+
+static Node *GetNode(TaskBarType *bar, int x);
+static unsigned int GetItemCount();
+static int ShouldShowItem(const ClientNode *np);
+static int ShouldFocusItem(const ClientNode *np);
+static unsigned int GetItemWidth(const TaskBarType *bp,
+ unsigned int itemCount);
+static void Render(const TaskBarType *bp);
+static void ShowTaskWindowMenu(TaskBarType *bar, Node *np);
+
+static void SetSize(TrayComponentType *cp, int width, int height);
+static void Create(TrayComponentType *cp);
+static void Destroy(TrayComponentType *cp);
+static void Resize(TrayComponentType *cp);
+static void ProcessTaskButtonEvent(TrayComponentType *cp,
+ int x, int y, int mask);
+static void ProcessTaskMotionEvent(TrayComponentType *cp,
+ int x, int y, int mask);
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeTaskBar() {
+ bars = NULL;
+ taskBarNodes = NULL;
+ taskBarNodesTail = NULL;
+ insertMode = INSERT_RIGHT;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void StartupTaskBar() {
+ minimizedPixmap = JXCreatePixmapFromBitmapData(display, rootWindow,
+ minimized_bitmap, 4, 4, colors[COLOR_TASK_FG],
+ colors[COLOR_TASK_BG], rootDepth);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShutdownTaskBar() {
+
+ TaskBarType *bp;
+
+ for(bp = bars; bp; bp = bp->next) {
+ JXFreePixmap(display, bp->buffer);
+ }
+
+ JXFreePixmap(display, minimizedPixmap);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyTaskBar() {
+
+ TaskBarType *bp;
+
+ while(bars) {
+ bp = bars->next;
+ Release(bars);
+ bars = bp;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+TrayComponentType *CreateTaskBar() {
+
+ TrayComponentType *cp;
+ TaskBarType *tp;
+
+ tp = Allocate(sizeof(TaskBarType));
+ tp->next = bars;
+ bars = tp;
+ tp->itemHeight = 0;
+ tp->layout = LAYOUT_HORIZONTAL;
+ tp->mousex = -1;
+ tp->mousey = -1;
+ tp->mouseTime.seconds = 0;
+ tp->mouseTime.ms = 0;
+ tp->maxItemWidth = 0;
+
+ cp = CreateTrayComponent();
+ cp->object = tp;
+ tp->cp = cp;
+
+ cp->SetSize = SetSize;
+ cp->Create = Create;
+ cp->Destroy = Destroy;
+ cp->Resize = Resize;
+ cp->ProcessButtonEvent = ProcessTaskButtonEvent;
+ cp->ProcessMotionEvent = ProcessTaskMotionEvent;
+
+ return cp;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetSize(TrayComponentType *cp, int width, int height) {
+
+ TaskBarType *tp;
+
+ Assert(cp);
+
+ tp = (TaskBarType*)cp->object;
+
+ Assert(tp);
+
+ if(width == 0) {
+ tp->layout = LAYOUT_HORIZONTAL;
+ } else if(height == 0) {
+ tp->layout = LAYOUT_VERTICAL;
+ } else if(width > height) {
+ tp->layout = LAYOUT_HORIZONTAL;
+ } else {
+ tp->layout = LAYOUT_VERTICAL;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Create(TrayComponentType *cp) {
+
+ TaskBarType *tp;
+
+ Assert(cp);
+
+ tp = (TaskBarType*)cp->object;
+
+ Assert(tp);
+
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ tp->itemHeight = cp->height - TASK_SPACER;
+ } else {
+ tp->itemHeight = GetStringHeight(FONT_TASK) + 12;
+ }
+
+ Assert(cp->width > 0);
+ Assert(cp->height > 0);
+
+ cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height,
+ rootDepth);
+ tp->buffer = cp->pixmap;
+
+ JXSetForeground(display, rootGC, colors[COLOR_TASK_BG]);
+ JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, cp->width, cp->height);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Resize(TrayComponentType *cp) {
+
+ TaskBarType *tp;
+
+ Assert(cp);
+
+ tp = (TaskBarType*)cp->object;
+
+ Assert(tp);
+
+ if(tp->buffer != None) {
+ JXFreePixmap(display, tp->buffer);
+ }
+
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ tp->itemHeight = cp->height - TASK_SPACER;
+ } else {
+ tp->itemHeight = GetStringHeight(FONT_TASK) + 12;
+ }
+
+ Assert(cp->width > 0);
+ Assert(cp->height > 0);
+
+ cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height,
+ rootDepth);
+ tp->buffer = cp->pixmap;
+
+ JXSetForeground(display, rootGC, colors[COLOR_TASK_BG]);
+ JXFillRectangle(display, cp->pixmap, rootGC,
+ 0, 0, cp->width, cp->height);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Destroy(TrayComponentType *cp) {
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ProcessTaskButtonEvent(TrayComponentType *cp, int x, int y, int mask) {
+
+ TaskBarType *bar = (TaskBarType*)cp->object;
+ Node *np;
+
+ Assert(bar);
+
+ if(bar->layout == LAYOUT_HORIZONTAL) {
+ np = GetNode(bar, x);
+ } else {
+ np = GetNode(bar, y);
+ }
+
+ if(np) {
+ switch(mask) {
+ case Button1:
+ if(np->client->state.status & STAT_ACTIVE
+ && np->client == nodes[np->client->state.layer]) {
+ MinimizeClient(np->client);
+ } else {
+ RestoreClient(np->client, 1);
+ FocusClient(np->client);
+ }
+ break;
+ case Button3:
+ ShowTaskWindowMenu(bar, np);
+ break;
+ case Button4:
+ FocusPrevious();
+ break;
+ case Button5:
+ FocusNext();
+ break;
+ default:
+ break;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ProcessTaskMotionEvent(TrayComponentType *cp, int x, int y, int mask) {
+
+ TaskBarType *bp = (TaskBarType*)cp->object;
+
+ bp->mousex = cp->screenx + x;
+ bp->mousey = cp->screeny + y;
+ GetCurrentTime(&bp->mouseTime);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShowTaskWindowMenu(TaskBarType *bar, Node *np) {
+
+ int x, y;
+ int mwidth, mheight;
+ const ScreenType *sp;
+
+ GetWindowMenuSize(np->client, &mwidth, &mheight);
+
+ sp = GetCurrentScreen(x, y);
+
+ if(bar->layout == LAYOUT_HORIZONTAL) {
+ GetMousePosition(&x, &y);
+ if(bar->cp->screeny + bar->cp->height / 2 < sp->y + sp->height / 2) {
+ y = bar->cp->screeny + bar->cp->height;
+ } else {
+ y = bar->cp->screeny - mheight;
+ }
+ x -= mwidth / 2;
+ } else {
+ if(bar->cp->screenx + bar->cp->width / 2 < sp->x + sp->width / 2) {
+ x = bar->cp->screenx + bar->cp->width;
+ } else {
+ x = bar->cp->screenx - mwidth;
+ }
+ y = bar->cp->screeny + np->y;
+ }
+
+ ShowWindowMenu(np->client, x, y);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void AddClientToTaskBar(ClientNode *np) {
+
+ Node *tp;
+
+ Assert(np);
+
+ tp = Allocate(sizeof(Node));
+ tp->client = np;
+
+ if(insertMode == INSERT_RIGHT) {
+ tp->next = NULL;
+ tp->prev = taskBarNodesTail;
+ if(taskBarNodesTail) {
+ taskBarNodesTail->next = tp;
+ } else {
+ taskBarNodes = tp;
+ }
+ taskBarNodesTail = tp;
+ } else {
+ tp->prev = NULL;
+ tp->next = taskBarNodes;
+ if(taskBarNodes) {
+ taskBarNodes->prev = tp;
+ }
+ taskBarNodes = tp;
+ if(!taskBarNodesTail) {
+ taskBarNodesTail = tp;
+ }
+ }
+
+ UpdateTaskBar();
+
+ UpdateNetClientList();
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void RemoveClientFromTaskBar(ClientNode *np) {
+
+ Node *tp;
+
+ Assert(np);
+
+ for(tp = taskBarNodes; tp; tp = tp->next) {
+ if(tp->client == np) {
+ if(tp->prev) {
+ tp->prev->next = tp->next;
+ } else {
+ taskBarNodes = tp->next;
+ }
+ if(tp->next) {
+ tp->next->prev = tp->prev;
+ } else {
+ taskBarNodesTail = tp->prev;
+ }
+ Release(tp);
+ break;
+ }
+ }
+
+ UpdateTaskBar();
+
+ UpdateNetClientList();
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void UpdateTaskBar() {
+
+ TaskBarType *bp;
+ unsigned int count;
+ int lastHeight;
+
+ if(shouldExit) {
+ return;
+ }
+
+ for(bp = bars; bp; bp = bp->next) {
+
+ if(bp->layout == LAYOUT_VERTICAL) {
+ lastHeight = bp->cp->requestedHeight;
+ count = GetItemCount();
+ bp->cp->requestedHeight = GetStringHeight(FONT_TASK) + 12;
+ bp->cp->requestedHeight *= count;
+ bp->cp->requestedHeight += 2;
+ if(lastHeight != bp->cp->requestedHeight) {
+ ResizeTray(bp->cp->tray);
+ }
+ }
+
+ Render(bp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SignalTaskbar(const TimeType *now, int x, int y) {
+
+ TaskBarType *bp;
+ Node *np;
+
+ for(bp = bars; bp; bp = bp->next) {
+ if(abs(bp->mousex - x) < POPUP_DELTA
+ && abs(bp->mousey - y) < POPUP_DELTA) {
+ if(GetTimeDifference(now, &bp->mouseTime) >= popupDelay) {
+ if(bp->layout == LAYOUT_HORIZONTAL) {
+ np = GetNode(bp, x - bp->cp->screenx);
+ } else {
+ np = GetNode(bp, y - bp->cp->screeny);
+ }
+ if(np) {
+ ShowPopup(x, y, np->client->name);
+ }
+ }
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Render(const TaskBarType *bp) {
+
+ Node *tp;
+ ButtonNode button;
+ int x, y;
+ int remainder;
+ int itemWidth, itemCount;
+ int width, height;
+ int iconSize;
+ Pixmap buffer;
+ GC gc;
+ char *minimizedName;
+
+ if(shouldExit) {
+ return;
+ }
+
+ Assert(bp);
+ Assert(bp->cp);
+
+ width = bp->cp->width;
+ height = bp->cp->height;
+ buffer = bp->cp->pixmap;
+ gc = rootGC;
+
+ x = TASK_SPACER;
+ width -= x;
+ y = 1;
+
+ JXSetForeground(display, gc, colors[COLOR_TASK_BG]);
+ JXFillRectangle(display, buffer, gc, 0, 0, width, height);
+
+ itemCount = GetItemCount();
+ if(!itemCount) {
+ UpdateSpecificTray(bp->cp->tray, bp->cp);
+ return;
+ }
+ if(bp->layout == LAYOUT_HORIZONTAL) {
+ itemWidth = GetItemWidth(bp, itemCount);
+ remainder = width - itemWidth * itemCount;
+ } else {
+ itemWidth = width;
+ remainder = 0;
+ }
+
+ iconSize = bp->itemHeight - 2 * TASK_SPACER - 4;
+
+ ResetButton(&button, buffer, gc);
+ button.font = FONT_TASK;
+
+ for(tp = taskBarNodes; tp; tp = tp->next) {
+ if(ShouldShowItem(tp->client)) {
+
+ tp->y = y;
+
+ if(tp->client->state.status & STAT_ACTIVE) {
+ button.type = BUTTON_TASK_ACTIVE;
+ } else {
+ button.type = BUTTON_TASK;
+ }
+
+ if(remainder) {
+ button.width = itemWidth - TASK_SPACER;
+ } else {
+ button.width = itemWidth - TASK_SPACER - 1;
+ }
+ button.height = bp->itemHeight - 1;
+ button.x = x;
+ button.y = y;
+ button.icon = tp->client->icon;
+
+ if(tp->client->state.status & STAT_MINIMIZED) {
+ minimizedName = AllocateStack(strlen(tp->client->name) + 3);
+ sprintf(minimizedName, "[%s]", tp->client->name);
+ button.text = minimizedName;
+ DrawButton(&button);
+ ReleaseStack(minimizedName);
+ } else {
+ button.text = tp->client->name;
+ DrawButton(&button);
+ }
+
+ if(tp->client->state.status & STAT_MINIMIZED) {
+ JXCopyArea(display, minimizedPixmap, buffer, gc,
+ 0, 0, 4, 4, x + 3, y + bp->itemHeight - 7);
+ }
+
+ if(bp->layout == LAYOUT_HORIZONTAL) {
+ x += itemWidth;
+ if(remainder) {
+ ++x;
+ --remainder;
+ }
+ } else {
+ y += bp->itemHeight;
+ if(remainder) {
+ ++y;
+ --remainder;
+ }
+ }
+
+ }
+ }
+
+ UpdateSpecificTray(bp->cp->tray, bp->cp);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void FocusNext() {
+
+ Node *tp;
+
+ for(tp = taskBarNodes; tp; tp = tp->next) {
+ if(ShouldFocusItem(tp->client)) {
+ if(tp->client->state.status & STAT_ACTIVE) {
+ tp = tp->next;
+ break;
+ }
+ }
+ }
+
+ if(!tp) {
+ tp = taskBarNodes;
+ }
+
+ while(tp && !ShouldFocusItem(tp->client)) {
+ tp = tp->next;
+ }
+
+ if(!tp) {
+ tp = taskBarNodes;
+ while(tp && !ShouldFocusItem(tp->client)) {
+ tp = tp->next;
+ }
+ }
+
+ if(tp) {
+ RestoreClient(tp->client, 1);
+ FocusClient(tp->client);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void FocusPrevious() {
+
+ Node *tp;
+
+ for(tp = taskBarNodesTail; tp; tp = tp->prev) {
+ if(ShouldFocusItem(tp->client)) {
+ if(tp->client->state.status & STAT_ACTIVE) {
+ tp = tp->prev;
+ break;
+ }
+ }
+ }
+
+ if(!tp) {
+ tp = taskBarNodesTail;
+ }
+
+ while(tp && !ShouldFocusItem(tp->client)) {
+ tp = tp->prev;
+ }
+
+ if(!tp) {
+ tp = taskBarNodesTail;
+ while(tp && !ShouldFocusItem(tp->client)) {
+ tp = tp->prev;
+ }
+ }
+
+ if(tp) {
+ RestoreClient(tp->client, 1);
+ FocusClient(tp->client);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void FocusNextStackedCircular() {
+
+ ClientNode *ac;
+ ClientNode *np;
+ int x;
+
+ ac = GetActiveClient();
+ np = NULL;
+
+ /* Check for a valid client below this client in the same layer. */
+ if(ac) {
+ for(np = ac->next; np; np = np->next) {
+ if(ShouldFocusItem(np)) {
+ break;
+ }
+ }
+ }
+
+ /* Check for a valid client in lower layers. */
+ if(ac && !np) {
+ for(x = ac->state.layer - 1; x >= LAYER_BOTTOM; x--) {
+ for(np = nodes[x]; np; np = np->next) {
+ if(ShouldFocusItem(np)) {
+ break;
+ }
+ }
+ if(np) {
+ break;
+ }
+ }
+ }
+
+ /* Revert to the top-most valid client. */
+ if(!np) {
+ for(x = LAYER_TOP; x >= LAYER_BOTTOM; x--) {
+ for(np = nodes[x]; np; np = np->next) {
+ if(ShouldFocusItem(np)) {
+ break;
+ }
+ }
+ if(np) {
+ break;
+ }
+ }
+ }
+
+ if(np) {
+ FocusClient(np);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+Node *GetNode(TaskBarType *bar, int x) {
+
+ Node *tp;
+ int remainder;
+ int itemCount;
+ int itemWidth;
+ int index, stop;
+ int width;
+
+ index = TASK_SPACER;
+
+ itemCount = GetItemCount();
+
+ if(bar->layout == LAYOUT_HORIZONTAL) {
+
+ width = bar->cp->width - index;
+ itemWidth = GetItemWidth(bar, itemCount);
+ remainder = width - itemWidth * itemCount;
+
+ for(tp = taskBarNodes; tp; tp = tp->next) {
+ if(ShouldShowItem(tp->client)) {
+ if(remainder) {
+ stop = index + itemWidth + 1;
+ --remainder;
+ } else {
+ stop = index + itemWidth;
+ }
+ if(x >= index && x < stop) {
+ return tp;
+ }
+ index = stop;
+ }
+ }
+
+ } else {
+
+ for(tp = taskBarNodes; tp; tp = tp->next) {
+ if(ShouldShowItem(tp->client)) {
+ stop = index + bar->itemHeight;
+ if(x >= index && x < stop) {
+ return tp;
+ }
+ index = stop;
+ }
+ }
+
+ }
+
+ return NULL;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+unsigned int GetItemCount() {
+
+ Node *tp;
+ unsigned int count;
+
+ count = 0;
+ for(tp = taskBarNodes; tp; tp = tp->next) {
+ if(ShouldShowItem(tp->client)) {
+ ++count;
+ }
+ }
+
+ return count;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ShouldShowItem(const ClientNode *np) {
+
+ if(np->state.desktop != currentDesktop
+ && !(np->state.status & STAT_STICKY)) {
+ return 0;
+ }
+
+ if(np->state.status & STAT_NOLIST) {
+ return 0;
+ }
+
+ if(np->owner != None) {
+ return 0;
+ }
+
+ if(!(np->state.status & STAT_MAPPED)
+ && !(np->state.status & (STAT_MINIMIZED | STAT_SHADED))) {
+ return 0;
+ }
+
+ return 1;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ShouldFocusItem(const ClientNode *np) {
+
+ if(np->state.desktop != currentDesktop
+ && !(np->state.status & STAT_STICKY)) {
+ return 0;
+ }
+
+ if(np->state.status & STAT_NOLIST) {
+ return 0;
+ }
+
+ if(!(np->state.status & STAT_MAPPED)) {
+ return 0;
+ }
+
+ if(np->owner != None) {
+ return 0;
+ }
+
+ return 1;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+unsigned int GetItemWidth(const TaskBarType *bp, unsigned int itemCount) {
+
+ unsigned int itemWidth;
+
+ itemWidth = bp->cp->width - TASK_SPACER;
+
+ if(!itemCount) {
+ return itemWidth;
+ }
+
+ itemWidth /= itemCount;
+ if(!itemWidth) {
+ itemWidth = 1;
+ }
+
+ if(bp->maxItemWidth > 0 && itemWidth > bp->maxItemWidth) {
+ itemWidth = bp->maxItemWidth;
+ }
+
+ return itemWidth;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetMaxTaskBarItemWidth(TrayComponentType *cp, const char *value) {
+
+ int temp;
+ TaskBarType *bp;
+
+ Assert(cp);
+
+ if(value) {
+ temp = atoi(value);
+ if(temp < 0) {
+ Warning("invalid maxwidth for TaskList: %s", value);
+ return;
+ }
+ bp = (TaskBarType*)cp->object;
+ bp->maxItemWidth = temp;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTaskBarInsertMode(const char *mode) {
+
+ if(!mode) {
+ insertMode = INSERT_RIGHT;
+ return;
+ }
+
+ if(!strcmp(mode, "right")) {
+ insertMode = INSERT_RIGHT;
+ } else if(!strcmp(mode, "left")) {
+ insertMode = INSERT_LEFT;
+ } else {
+ Warning("invalid insert mode: \"%s\"", mode);
+ insertMode = INSERT_RIGHT;
+ }
+
+}
+
+/***************************************************************************
+ * Maintain the _NET_CLIENT_LIST[_STACKING] properties on the root window.
+ ***************************************************************************/
+void UpdateNetClientList() {
+
+ Node *np;
+ ClientNode *client;
+ Window *windows;
+ int count;
+ int layer;
+
+ count = 0;
+ for(np = taskBarNodes; np; np = np->next) {
+ ++count;
+ }
+
+ if(count == 0) {
+ windows = NULL;
+ } else {
+ windows = AllocateStack(count * sizeof(Window));
+ }
+
+ /* Set _NET_CLIENT_LIST */
+ count = 0;
+ for(np = taskBarNodes; np; np = np->next) {
+ windows[count++] = np->client->window;
+ }
+ JXChangeProperty(display, rootWindow, atoms[ATOM_NET_CLIENT_LIST],
+ XA_WINDOW, 32, PropModeReplace, (unsigned char*)windows, count);
+
+ /* Set _NET_CLIENT_LIST_STACKING */
+ count = 0;
+ for(layer = LAYER_BOTTOM; layer <= LAYER_TOP; layer++) {
+ for(client = nodes[layer]; client; client = client->next) {
+ windows[count++] = client->window;
+ }
+ }
+ JXChangeProperty(display, rootWindow, atoms[ATOM_NET_CLIENT_LIST_STACKING],
+ XA_WINDOW, 32, PropModeReplace, (unsigned char*)windows, count);
+
+ if(windows != NULL) {
+ ReleaseStack(windows);
+ }
+
+}
+
diff --git a/src/taskbar.h b/src/taskbar.h
new file mode 100644
index 0000000..e65600f
--- /dev/null
+++ b/src/taskbar.h
@@ -0,0 +1,64 @@
+/**
+ * @file taskbar.h
+ * @author Joe Wingbermuehle
+ * @date 2005-2006
+ *
+ * @brief Task list tray component.
+ *
+ */
+
+#ifndef TASKBAR_H
+#define TASKBAR_H
+
+struct ClientNode;
+struct TimeType;
+
+/*@{*/
+void InitializeTaskBar();
+void StartupTaskBar();
+void ShutdownTaskBar();
+void DestroyTaskBar();
+/*@}*/
+
+/** Create a new task bar tray component. */
+struct TrayComponentType *CreateTaskBar();
+
+/** Add a client to the task bar(s).
+ * @param np The client to add.
+ */
+void AddClientToTaskBar(struct ClientNode *np);
+
+/** Remove a client from the task bar(s).
+ * @param np The client to remove.
+ */
+void RemoveClientFromTaskBar(struct ClientNode *np);
+
+void UpdateTaskBar();
+
+void SignalTaskbar(const struct TimeType *now, int x, int y);
+
+/** Focus the next client in the task bar. */
+void FocusNext();
+
+/** Focus the previous client in the task bar. */
+void FocusPrevious();
+
+/** Focus the next stacked client. */
+void FocusNextStackedCircular();
+
+/** Set the maximum width of task bar items.
+ * @param cp The task bar component.
+ * @param value The maximum width.
+ */
+void SetMaxTaskBarItemWidth(struct TrayComponentType *cp, const char *value);
+
+/** Set the insertion mode for task bars.
+ * @param mode The insertion mode (either right or left).
+ */
+void SetTaskBarInsertMode(const char *mode);
+
+/** Update the _NET_CLIENT_LIST property. */
+void UpdateNetClientList();
+
+#endif
+
diff --git a/src/theme.c b/src/theme.c
new file mode 100644
index 0000000..4560abc
--- /dev/null
+++ b/src/theme.c
@@ -0,0 +1,88 @@
+/****************************************************************************
+ * Theme functions.
+ * Copyright (C) 2006 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "theme.h"
+#include "error.h"
+#include "misc.h"
+
+typedef struct ThemePathNode {
+ char *path;
+ struct ThemePathNode *next;
+} ThemePathNode;
+
+static ThemePathNode *themePaths;
+static char *themeName;
+
+/****************************************************************************
+ ****************************************************************************/
+void InitializeThemes() {
+
+ themeName = NULL;
+ themePaths = NULL;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void StartupThemes() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShutdownThemes() {
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void DestroyThemes() {
+
+ ThemePathNode *tp;
+
+ if(themeName) {
+ Release(themeName);
+ }
+
+ while(themePaths) {
+ tp = themePaths->next;
+ Release(themePaths->path);
+ Release(themePaths);
+ themePaths = tp;
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddThemePath(const char *path) {
+
+ ThemePathNode *tp;
+
+ if(!path) {
+ return;
+ }
+
+ tp = Allocate(sizeof(ThemePathNode));
+ tp->path = CopyString(path);
+ Trim(tp->path);
+
+ tp->next = themePaths;
+ themePaths = tp;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void SetTheme(const char *name) {
+
+ if(themeName) {
+ Warning("theme set multiple times");
+ }
+
+ themeName = CopyString(name);
+ Trim(themeName);
+
+}
+
diff --git a/src/theme.h b/src/theme.h
new file mode 100644
index 0000000..2dd1400
--- /dev/null
+++ b/src/theme.h
@@ -0,0 +1,31 @@
+/**
+ * @file theme.h
+ * @author Joe Wingbermuehle
+ * @date 2006
+ *
+ * @brief Header for the theme functions.
+ *
+ */
+
+#ifndef THEME_H
+#define THEME_H
+
+/*@{*/
+void InitializeThemes();
+void StartupThemes();
+void ShutdownThemes();
+void DestroyThemes();
+/*@}*/
+
+/** Add a theme path.
+ * @param path The path to add.
+ */
+void AddThemePath(const char *path);
+
+/** Set the theme to use.
+ * @param name The name of the theme.
+ */
+void SetTheme(const char *name);
+
+#endif
+
diff --git a/src/timing.c b/src/timing.c
new file mode 100644
index 0000000..047d919
--- /dev/null
+++ b/src/timing.c
@@ -0,0 +1,71 @@
+/****************************************************************************
+ * Timing functions.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "timing.h"
+
+static const unsigned long MAX_TIME_SECONDS = 60;
+
+/****************************************************************************
+ * Get the current time in milliseconds since midnight 1970-01-01 UTC.
+ ****************************************************************************/
+void GetCurrentTime(TimeType *t) {
+ struct timeval val;
+ gettimeofday(&val, NULL);
+ t->seconds = val.tv_sec;
+ t->ms = val.tv_usec / 1000;
+}
+
+/****************************************************************************
+ * Get the absolute difference between two times in milliseconds.
+ * If the difference is larger than a MAX_TIME_SECONDS, then
+ * MAX_TIME_SECONDS will be returned.
+ * Note that the times must be normalized.
+ ****************************************************************************/
+unsigned long GetTimeDifference(const TimeType *t1, const TimeType *t2) {
+ unsigned long deltaSeconds;
+ int deltaMs;
+
+ if(t1->seconds > t2->seconds) {
+ deltaSeconds = t1->seconds - t2->seconds;
+ deltaMs = t1->ms - t2->ms;
+ } else if(t1->seconds < t2->seconds) {
+ deltaSeconds = t2->seconds - t1->seconds;
+ deltaMs = t2->ms - t1->ms;
+ } else if(t1->ms > t2->ms) {
+ deltaSeconds = 0;
+ deltaMs = t1->ms - t2->ms;
+ } else {
+ deltaSeconds = 0;
+ deltaMs = t2->ms - t1->ms;
+ }
+
+ if(deltaSeconds > MAX_TIME_SECONDS) {
+ return MAX_TIME_SECONDS * 1000;
+ } else {
+ return deltaSeconds * 1000 + deltaMs;
+ }
+
+}
+
+/****************************************************************************
+ * Get the current time.
+ * Not reenterent.
+ ****************************************************************************/
+const char *GetTimeString(const char *format) {
+
+ static char str[80];
+ time_t t;
+
+ Assert(format);
+
+ time(&t);
+ strftime(str, sizeof(str), format, localtime(&t));
+
+ return str;
+
+}
+
+
diff --git a/src/timing.h b/src/timing.h
new file mode 100644
index 0000000..25a8fca
--- /dev/null
+++ b/src/timing.h
@@ -0,0 +1,43 @@
+/**
+ * @file timing.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Timing functions.
+ *
+ */
+
+#ifndef TIMING_H
+#define TIMING_H
+
+#define ZERO_TIME { 0, 0 }
+
+typedef struct TimeType {
+
+ unsigned long seconds;
+ int ms;
+
+} TimeType;
+
+/** Get the current time.
+ * @param t The TimeType to fill.
+ */
+void GetCurrentTime(TimeType *t);
+
+/** Get the difference between two times.
+ * Note that the times must be normalized.
+ * @param t1 The first time.
+ * @param t2 The second time.
+ */
+unsigned long GetTimeDifference(const TimeType *t1, const TimeType *t2);
+
+/** Get a time string.
+ * Note that the string returned is a static value and should not be
+ * deleted. Therefore, this function is not thread safe.
+ * @param format The format to use for the string.
+ * @return The time string.
+ */
+const char *GetTimeString(const char *format);
+
+#endif
+
diff --git a/src/tray.c b/src/tray.c
new file mode 100644
index 0000000..d30dd1e
--- /dev/null
+++ b/src/tray.c
@@ -0,0 +1,1104 @@
+/***************************************************************************
+ * Functions to handle the tray.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "tray.h"
+#include "color.h"
+#include "main.h"
+#include "pager.h"
+#include "cursor.h"
+#include "error.h"
+#include "taskbar.h"
+#include "menu.h"
+#include "timing.h"
+
+#define DEFAULT_TRAY_WIDTH 32
+#define DEFAULT_TRAY_HEIGHT 32
+
+static TrayType *trays;
+static Window supportingWindow;
+
+static void HandleTrayExpose(TrayType *tp, const XExposeEvent *event);
+static void HandleTrayEnterNotify(TrayType *tp, const XCrossingEvent *event);
+
+static void HandleTrayButtonPress(TrayType *tp, const XButtonEvent *event);
+static void HandleTrayMotionNotify(TrayType *tp, const XMotionEvent *event);
+
+static void ComputeTraySize(TrayType *tp);
+static int ComputeMaxWidth(TrayType *tp);
+static int ComputeTotalWidth(TrayType *tp);
+static int ComputeMaxHeight(TrayType *tp);
+static int ComputeTotalHeight(TrayType *tp);
+static int CheckHorizontalFill(TrayType *tp);
+static int CheckVerticalFill(TrayType *tp);
+static void LayoutTray(TrayType *tp, int *variableSize,
+ int *variableRemainder);
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeTray() {
+ trays = NULL;
+ supportingWindow = None;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void StartupTray() {
+
+ XSetWindowAttributes attr;
+ unsigned long attrMask;
+ TrayType *tp;
+ TrayComponentType *cp;
+ int variableSize;
+ int variableRemainder;
+ int width, height;
+ int xoffset, yoffset;
+
+ for(tp = trays; tp; tp = tp->next) {
+
+ LayoutTray(tp, &variableSize, &variableRemainder);
+
+ /* Create the tray window. */
+ /* The window is created larger for a border. */
+ attrMask = CWOverrideRedirect;
+ attr.override_redirect = True;
+
+ /* We can't use PointerMotionHintMask since the exact position
+ * of the mouse on the tray is important for popups. */
+ attrMask |= CWEventMask;
+ attr.event_mask
+ = ButtonPressMask
+ | SubstructureNotifyMask
+ | ExposureMask
+ | KeyPressMask
+ | EnterWindowMask
+ | PointerMotionMask;
+
+ attrMask |= CWBackPixel;
+ attr.background_pixel = colors[COLOR_TRAY_BG];
+
+ tp->window = JXCreateWindow(display, rootWindow,
+ tp->x, tp->y, tp->width, tp->height,
+ 0, rootDepth, InputOutput, rootVisual, attrMask, &attr);
+
+ SetDefaultCursor(tp->window);
+
+ /* Create and layout items on the tray. */
+ xoffset = tp->border;
+ yoffset = tp->border;
+ for(cp = tp->components; cp; cp = cp->next) {
+
+ if(cp->Create) {
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ height = tp->height - 2 * tp->border;
+ width = cp->width;
+ if(width == 0) {
+ width = variableSize;
+ if(variableRemainder) {
+ ++width;
+ --variableRemainder;
+ }
+ }
+ } else {
+ width = tp->width - 2 * tp->border;
+ height = cp->height;
+ if(height == 0) {
+ height = variableSize;
+ if(variableRemainder) {
+ ++height;
+ --variableRemainder;
+ }
+ }
+ }
+ cp->width = width;
+ cp->height = height;
+ (cp->Create)(cp);
+ }
+
+ cp->x = xoffset;
+ cp->y = yoffset;
+ cp->screenx = tp->x + xoffset;
+ cp->screeny = tp->y + yoffset;
+
+ if(cp->window != None) {
+ JXReparentWindow(display, cp->window, tp->window,
+ xoffset, yoffset);
+ }
+
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ xoffset += cp->width;
+ } else {
+ yoffset += cp->height;
+ }
+ }
+
+ /* Show the tray. */
+ JXMapWindow(display, tp->window);
+
+ }
+
+ UpdatePager();
+ UpdateTaskBar();
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShutdownTray() {
+
+ TrayType *tp;
+ TrayComponentType *cp;
+
+ for(tp = trays; tp; tp = tp->next) {
+ for(cp = tp->components; cp; cp = cp->next) {
+ if(cp->Destroy) {
+ (cp->Destroy)(cp);
+ }
+ }
+ JXDestroyWindow(display, tp->window);
+ }
+
+ if(supportingWindow != None) {
+ XDestroyWindow(display, supportingWindow);
+ supportingWindow = None;
+ }
+
+}
+
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyTray() {
+
+ TrayType *tp;
+ TrayComponentType *cp;
+
+ while(trays) {
+ tp = trays->next;
+
+ while(trays->components) {
+ cp = trays->components->next;
+ Release(trays->components);
+ trays->components = cp;
+ }
+ Release(trays);
+
+ trays = tp;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+TrayType *CreateTray() {
+
+ TrayType *tp;
+
+ tp = Allocate(sizeof(TrayType));
+
+ tp->x = 0;
+ tp->y = -1;
+ tp->requestedWidth = 0;
+ tp->requestedHeight = 0;
+ tp->width = 0;
+ tp->height = 0;
+ tp->border = 1;
+ tp->layer = DEFAULT_TRAY_LAYER;
+ tp->layout = LAYOUT_HORIZONTAL;
+ tp->valign = TALIGN_FIXED;
+ tp->halign = TALIGN_FIXED;
+
+ tp->autoHide = 0;
+ tp->hidden = 0;
+
+ tp->window = None;
+
+ tp->components = NULL;
+ tp->componentsTail = NULL;
+
+ tp->next = trays;
+ trays = tp;
+
+ return tp;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+TrayComponentType *CreateTrayComponent() {
+
+ TrayComponentType *cp;
+
+ cp = Allocate(sizeof(TrayComponentType));
+
+ cp->tray = NULL;
+ cp->object = NULL;
+
+ cp->x = 0;
+ cp->y = 0;
+ cp->requestedWidth = 0;
+ cp->requestedHeight = 0;
+ cp->width = 0;
+ cp->height = 0;
+
+ cp->window = None;
+ cp->pixmap = None;
+
+ cp->Create = NULL;
+ cp->Destroy = NULL;
+
+ cp->SetSize = NULL;
+ cp->Resize = NULL;
+
+ cp->ProcessButtonEvent = NULL;
+ cp->ProcessMotionEvent = NULL;
+
+ cp->next = NULL;
+
+ return cp;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void AddTrayComponent(TrayType *tp, TrayComponentType *cp) {
+
+ Assert(tp);
+ Assert(cp);
+
+ cp->tray = tp;
+
+ if(tp->componentsTail) {
+ tp->componentsTail->next = cp;
+ } else {
+ tp->components = cp;
+ }
+ tp->componentsTail = cp;
+ cp->next = NULL;
+
+}
+
+/***************************************************************************
+ * Compute the max component width.
+ ***************************************************************************/
+int ComputeMaxWidth(TrayType *tp) {
+
+ TrayComponentType *cp;
+ int result;
+ int temp;
+
+ result = 0;
+ for(cp = tp->components; cp; cp = cp->next) {
+ temp = cp->width;
+ if(temp > 0) {
+ temp += 2 * tp->border;
+ if(temp > result) {
+ result = temp;
+ }
+ }
+ }
+
+ return result;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ComputeTotalWidth(TrayType *tp) {
+
+ TrayComponentType *cp;
+ int result;
+
+ result = 2 * tp->border;
+ for(cp = tp->components; cp; cp = cp->next) {
+ result += cp->width;
+ }
+
+ return result;
+
+}
+
+/***************************************************************************
+ * Compute the max component height.
+ ***************************************************************************/
+int ComputeMaxHeight(TrayType *tp) {
+
+ TrayComponentType *cp;
+ int result;
+ int temp;
+
+ result = 0;
+ for(cp = tp->components; cp; cp = cp->next) {
+ temp = cp->height;
+ if(temp > 0) {
+ temp += 2 * tp->border;
+ if(temp > result) {
+ result = temp;
+ }
+ }
+ }
+
+ return result;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ComputeTotalHeight(TrayType *tp) {
+
+ TrayComponentType *cp;
+ int result;
+
+ result = 2 * tp->border;
+ for(cp = tp->components; cp; cp = cp->next) {
+ result += cp->height;
+ }
+
+ return result;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int CheckHorizontalFill(TrayType *tp) {
+
+ TrayComponentType *cp;
+
+ for(cp = tp->components; cp; cp = cp->next) {
+ if(cp->width == 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int CheckVerticalFill(TrayType *tp) {
+
+ TrayComponentType *cp;
+
+ for(cp = tp->components; cp; cp = cp->next) {
+ if(cp->height == 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ComputeTraySize(TrayType *tp) {
+
+ TrayComponentType *cp;
+
+ /* Determine the first dimension. */
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+
+ if(tp->height == 0) {
+ tp->height = ComputeMaxHeight(tp);
+ }
+
+ if(tp->height == 0) {
+ tp->height = DEFAULT_TRAY_HEIGHT;
+ }
+
+ } else {
+
+ if(tp->width == 0) {
+ tp->width = ComputeMaxWidth(tp);
+ }
+
+ if(tp->width == 0) {
+ tp->width = DEFAULT_TRAY_WIDTH;
+ }
+
+ }
+
+ /* Now at least one size is known. Inform the components. */
+ for(cp = tp->components; cp; cp = cp->next) {
+ if(cp->SetSize) {
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ (cp->SetSize)(cp, 0, tp->height - 2 * tp->border);
+ } else {
+ (cp->SetSize)(cp, tp->width - 2 * tp->border, 0);
+ }
+ }
+ }
+
+ /* Determine the missing dimension. */
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ if(tp->width == 0) {
+ if(CheckHorizontalFill(tp)) {
+ tp->width = rootWidth;
+ } else {
+ tp->width = ComputeTotalWidth(tp);
+ }
+ if(tp->width == 0) {
+ tp->width = DEFAULT_TRAY_WIDTH;
+ }
+ }
+ } else {
+ if(tp->height == 0) {
+ if(CheckVerticalFill(tp)) {
+ tp->height = rootHeight;
+ } else {
+ tp->height = ComputeTotalHeight(tp);
+ }
+ if(tp->height == 0) {
+ tp->height = DEFAULT_TRAY_HEIGHT;
+ }
+ }
+ }
+
+ /* Compute the tray location. */
+ switch(tp->valign) {
+ case TALIGN_TOP:
+ tp->y = 0;
+ break;
+ case TALIGN_BOTTOM:
+ tp->y = rootHeight - tp->height + 1;
+ break;
+ case TALIGN_CENTER:
+ tp->y = rootHeight / 2 - tp->height / 2;
+ break;
+ default:
+ if(tp->y < 0) {
+ tp->y = rootHeight + tp->y - tp->height + 1;
+ }
+ break;
+ }
+
+ switch(tp->halign) {
+ case TALIGN_LEFT:
+ tp->x = 0;
+ break;
+ case TALIGN_RIGHT:
+ tp->x = rootWidth - tp->width + 1;
+ break;
+ case TALIGN_CENTER:
+ tp->x = rootWidth / 2 - tp->width / 2;
+ break;
+ default:
+ if(tp->x < 0) {
+ tp->x = rootWidth + tp->x - tp->width + 1;
+ }
+ break;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShowTray(TrayType *tp) {
+
+ Window win1, win2;
+ int winx, winy;
+ unsigned int mask;
+ int mousex, mousey;
+
+ if(tp->hidden) {
+
+ tp->hidden = 0;
+ JXMoveWindow(display, tp->window, tp->x, tp->y);
+
+ JXQueryPointer(display, rootWindow, &win1, &win2,
+ &mousex, &mousey, &winx, &winy, &mask);
+ SetMousePosition(mousex, mousey);
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void HideTray(TrayType *tp) {
+
+ int x, y;
+
+ tp->hidden = 1;
+
+ /* Determine where to move the tray. */
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+
+ x = tp->x;
+
+ if(tp->y >= rootHeight / 2) {
+ y = rootHeight - 1;
+ } else {
+ y = 1 - tp->height;
+ }
+
+ } else {
+
+ y = tp->y;
+
+ if(tp->x >= rootWidth / 2) {
+ x = rootWidth - 1;
+ } else {
+ x = 1 - tp->width;
+ }
+
+ }
+
+ /* Move it. */
+ JXMoveWindow(display, tp->window, x, y);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ProcessTrayEvent(const XEvent *event) {
+
+ TrayType *tp;
+
+ for(tp = trays; tp; tp = tp->next) {
+ if(event->xany.window == tp->window) {
+ switch(event->type) {
+ case Expose:
+ HandleTrayExpose(tp, &event->xexpose);
+ return 1;
+ case EnterNotify:
+ HandleTrayEnterNotify(tp, &event->xcrossing);
+ return 1;
+ case ButtonPress:
+ HandleTrayButtonPress(tp, &event->xbutton);
+ return 1;
+ case MotionNotify:
+ HandleTrayMotionNotify(tp, &event->xmotion);
+ return 1;
+ default:
+ return 0;
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SignalTray(const TimeType *now, int x, int y) {
+
+ TrayType *tp;
+
+ for(tp = trays; tp; tp = tp->next) {
+ if(tp->autoHide && !tp->hidden && !menuShown) {
+ if(x < tp->x || x >= tp->x + tp->width
+ || y < tp->y || y >= tp->y + tp->height) {
+ HideTray(tp);
+ }
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void HandleTrayExpose(TrayType *tp, const XExposeEvent *event) {
+
+ DrawSpecificTray(tp);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void HandleTrayEnterNotify(TrayType *tp, const XCrossingEvent *event) {
+
+ ShowTray(tp);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void HandleTrayButtonPress(TrayType *tp, const XButtonEvent *event) {
+
+ TrayComponentType *cp;
+ int xoffset, yoffset;
+ int width, height;
+ int x, y;
+ int mask;
+
+ xoffset = tp->border;
+ yoffset = tp->border;
+ for(cp = tp->components; cp; cp = cp->next) {
+ width = cp->width;
+ height = cp->height;
+ if(event->x >= xoffset && event->x < xoffset + width) {
+ if(event->y >= yoffset && event->y < yoffset + height) {
+ if(cp->ProcessButtonEvent) {
+ x = event->x - xoffset;
+ y = event->y - yoffset;
+ mask = event->button;
+ (cp->ProcessButtonEvent)(cp, x, y, mask);
+ }
+ break;
+ }
+ }
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ xoffset += width;
+ } else {
+ yoffset += height;
+ }
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void HandleTrayMotionNotify(TrayType *tp, const XMotionEvent *event) {
+
+ TrayComponentType *cp;
+ int xoffset, yoffset;
+ int width, height;
+ int x, y;
+ int mask;
+
+ xoffset = tp->border;
+ yoffset = tp->border;
+ for(cp = tp->components; cp; cp = cp->next) {
+ width = cp->width;
+ height = cp->height;
+ if(event->x >= xoffset && event->x < xoffset + width) {
+ if(event->y >= yoffset && event->y < yoffset + height) {
+ if(cp->ProcessMotionEvent) {
+ x = event->x - xoffset;
+ y = event->y - yoffset;
+ mask = event->state;
+ (cp->ProcessMotionEvent)(cp, x, y, mask);
+ }
+ break;
+ }
+ }
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ xoffset += width;
+ } else {
+ yoffset += height;
+ }
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DrawTray() {
+
+ TrayType *tp;
+
+ if(shouldExit) {
+ return;
+ }
+
+ for(tp = trays; tp; tp = tp->next) {
+ DrawSpecificTray(tp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DrawSpecificTray(const TrayType *tp) {
+
+ TrayComponentType *cp;
+ int x;
+
+ Assert(tp);
+
+ /* Draw components. */
+ for(cp = tp->components; cp; cp = cp->next) {
+ UpdateSpecificTray(tp, cp);
+ }
+
+ /* Draw the border. */
+ for(x = 0; x < tp->border; x++) {
+
+ /* Top */
+ JXSetForeground(display, rootGC, colors[COLOR_TRAY_UP]);
+ JXDrawLine(display, tp->window, rootGC,
+ 0, x,
+ tp->width - x - 1, x);
+
+ /* Bottom */
+ JXSetForeground(display, rootGC, colors[COLOR_TRAY_DOWN]);
+ JXDrawLine(display, tp->window, rootGC,
+ x + 1, tp->height - x - 1,
+ tp->width - x - 2, tp->height - x - 1);
+
+ /* Left */
+ JXSetForeground(display, rootGC, colors[COLOR_TRAY_UP]);
+ JXDrawLine(display, tp->window, rootGC,
+ x, x,
+ x, tp->height - x - 1);
+
+ /* Right */
+ JXSetForeground(display, rootGC, colors[COLOR_TRAY_DOWN]);
+ JXDrawLine(display, tp->window, rootGC,
+ tp->width - x - 1, x + 1,
+ tp->width - x - 1, tp->height - x - 1);
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void UpdateSpecificTray(const TrayType *tp, const TrayComponentType *cp) {
+
+ if(cp->pixmap != None && !shouldExit) {
+ JXCopyArea(display, cp->pixmap, tp->window, rootGC, 0, 0,
+ cp->width, cp->height, cp->x, cp->y);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void LayoutTray(TrayType *tp, int *variableSize, int *variableRemainder) {
+
+ TrayComponentType *cp;
+ int variableCount;
+ int width, height;
+ int temp;
+
+ tp->width = tp->requestedWidth;
+ tp->height = tp->requestedHeight;
+
+ for(cp = tp->components; cp; cp = cp->next) {
+ cp->width = cp->requestedWidth;
+ cp->height = cp->requestedHeight;
+ }
+
+ ComputeTraySize(tp);
+
+ /* Get the remaining size after setting fixed size components. */
+ /* Also, keep track of the number of variable size components. */
+ width = tp->width - 2 * tp->border;
+ height = tp->height - 2 * tp->border;
+ variableCount = 0;
+ for(cp = tp->components; cp; cp = cp->next) {
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ temp = cp->width;
+ if(temp > 0) {
+ width -= temp;
+ } else {
+ ++variableCount;
+ }
+ } else {
+ temp = cp->height;
+ if(temp > 0) {
+ height -= temp;
+ } else {
+ ++variableCount;
+ }
+ }
+ }
+
+ /* Distribute excess size among variable size components.
+ * If there are no variable size components, shrink the tray.
+ * If we are out of room, just give them a size of one.
+ */
+ *variableSize = 1;
+ *variableRemainder = 0;
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ if(variableCount) {
+ if(width >= variableCount) {
+ *variableSize = width / variableCount;
+ *variableRemainder = width % variableCount;
+ }
+ } else if(width > 0) {
+ tp->width -= width;
+ }
+ } else {
+ if(variableCount) {
+ if(height >= variableCount) {
+ *variableSize = height / variableCount;
+ *variableRemainder = height % variableCount;
+ }
+ } else if(height > 0) {
+ tp->height -= height;
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ResizeTray(TrayType *tp) {
+
+ TrayComponentType *cp;
+ int variableSize;
+ int variableRemainder;
+ int xoffset, yoffset;
+ int width, height;
+
+ Assert(tp);
+
+ LayoutTray(tp, &variableSize, &variableRemainder);
+
+ /* Reposition items on the tray. */
+ xoffset = tp->border;
+ yoffset = tp->border;
+ for(cp = tp->components; cp; cp = cp->next) {
+
+ cp->x = xoffset;
+ cp->y = yoffset;
+ cp->screenx = tp->x + xoffset;
+ cp->screeny = tp->y + yoffset;
+
+ if(cp->Resize) {
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ height = tp->height - 2 * tp->border;
+ width = cp->width;
+ if(width == 0) {
+ width = variableSize;
+ if(variableRemainder) {
+ ++width;
+ --variableRemainder;
+ }
+ }
+ } else {
+ width = tp->width - 2 * tp->border;
+ height = cp->height;
+ if(height == 0) {
+ height = variableSize;
+ if(variableRemainder) {
+ ++height;
+ --variableRemainder;
+ }
+ }
+ }
+ cp->width = width;
+ cp->height = height;
+ (cp->Resize)(cp);
+ }
+
+ if(cp->window != None) {
+ JXMoveWindow(display, cp->window, xoffset, yoffset);
+ }
+
+ if(tp->layout == LAYOUT_HORIZONTAL) {
+ xoffset += cp->width;
+ } else {
+ yoffset += cp->height;
+ }
+ }
+
+ JXMoveResizeWindow(display, tp->window, tp->x, tp->y,
+ tp->width, tp->height);
+
+ UpdateTaskBar();
+ DrawSpecificTray(tp);
+
+ if(tp->hidden) {
+ HideTray(tp);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+TrayType *GetTrays() {
+ return trays;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+Window GetSupportingWindow() {
+
+ if(trays) {
+ return trays->window;
+ } else if(supportingWindow != None) {
+ return supportingWindow;
+ } else {
+ supportingWindow = JXCreateSimpleWindow(display, rootWindow,
+ 0, 0, 1, 1, 0, 0, 0);
+ return supportingWindow;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetAutoHideTray(TrayType *tp, int v) {
+ Assert(tp);
+ tp->autoHide = v;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayX(TrayType *tp, const char *str) {
+ Assert(tp);
+ Assert(str);
+ tp->x = atoi(str);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayY(TrayType *tp, const char *str) {
+ Assert(tp);
+ Assert(str);
+ tp->y = atoi(str);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayWidth(TrayType *tp, const char *str) {
+
+ int width;
+
+ Assert(tp);
+ Assert(str);
+
+ width = atoi(str);
+
+ if(width < 0) {
+ Warning("invalid tray width: %d", width);
+ } else {
+ tp->requestedWidth = width;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayHeight(TrayType *tp, const char *str) {
+
+ int height;
+
+ Assert(tp);
+ Assert(str);
+
+ height = atoi(str);
+
+ if(height < 0) {
+ Warning("invalid tray height: %d", height);
+ } else {
+ tp->requestedHeight = height;
+ }
+
+}
+
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayLayout(TrayType *tp, const char *str) {
+
+ Assert(tp);
+
+ if(!str) {
+
+ /* Compute based on requested size. */
+
+ } else if(!strcmp(str, "horizontal")) {
+
+ tp->layout = LAYOUT_HORIZONTAL;
+ return;
+
+ } else if(!strcmp(str, "vertical")) {
+
+ tp->layout = LAYOUT_VERTICAL;
+ return;
+
+ } else {
+ Warning("invalid tray layout: \"%s\"", str);
+ }
+
+ /* Prefer horizontal layout, but use vertical if
+ * width is finite and height is larger than width or infinite.
+ */
+ if(tp->requestedWidth > 0
+ && (tp->requestedHeight == 0
+ || tp->requestedHeight > tp->requestedWidth)) {
+ tp->layout = LAYOUT_VERTICAL;
+ } else {
+ tp->layout = LAYOUT_HORIZONTAL;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayLayer(TrayType *tp, const char *str) {
+
+ int temp;
+
+ Assert(tp);
+ Assert(str);
+
+ temp = atoi(str);
+ if(temp < LAYER_BOTTOM || temp > LAYER_TOP) {
+ Warning("invalid tray layer: %d", temp);
+ tp->layer = DEFAULT_TRAY_LAYER;
+ } else {
+ tp->layer = temp;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayBorder(TrayType *tp, const char *str) {
+
+ int temp;
+
+ Assert(tp);
+ Assert(str);
+
+ temp = atoi(str);
+ if(temp < MIN_TRAY_BORDER || temp > MAX_TRAY_BORDER) {
+ Warning("invalid tray border: %d", temp);
+ tp->border = DEFAULT_TRAY_BORDER;
+ } else {
+ tp->border = temp;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayHorizontalAlignment(TrayType *tp, const char *str) {
+
+ Assert(tp);
+
+ if(!str || !strcmp(str, "fixed")) {
+ tp->halign = TALIGN_FIXED;
+ } else if(!strcmp(str, "left")) {
+ tp->halign = TALIGN_LEFT;
+ } else if(!strcmp(str, "right")) {
+ tp->halign = TALIGN_RIGHT;
+ } else if(!strcmp(str, "center")) {
+ tp->halign = TALIGN_CENTER;
+ } else {
+ Warning("invalid tray horizontal alignment: \"%s\"", str);
+ tp->halign = TALIGN_FIXED;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetTrayVerticalAlignment(TrayType *tp, const char *str) {
+
+ Assert(tp);
+
+ if(!str || !strcmp(str, "fixed")) {
+ tp->valign = TALIGN_FIXED;
+ } else if(!strcmp(str, "top")) {
+ tp->valign = TALIGN_TOP;
+ } else if(!strcmp(str, "bottom")) {
+ tp->valign = TALIGN_BOTTOM;
+ } else if(!strcmp(str, "center")) {
+ tp->valign = TALIGN_CENTER;
+ } else {
+ Warning("invalid tray vertical alignment: \"%s\"", str);
+ tp->valign = TALIGN_FIXED;
+ }
+
+}
+
+
diff --git a/src/tray.h b/src/tray.h
new file mode 100644
index 0000000..bae38c5
--- /dev/null
+++ b/src/tray.h
@@ -0,0 +1,214 @@
+/**
+ * @file tray.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the tray functions.
+ *
+ */
+
+#ifndef TRAY_H
+#define TRAY_H
+
+#include "hint.h"
+
+struct TimeType;
+
+typedef enum {
+ LAYOUT_HORIZONTAL,
+ LAYOUT_VERTICAL
+} LayoutType;
+
+typedef enum {
+ TALIGN_FIXED,
+ TALIGN_LEFT,
+ TALIGN_TOP,
+ TALIGN_CENTER,
+ TALIGN_RIGHT,
+ TALIGN_BOTTOM
+} TrayAlignmentType;
+
+typedef struct TrayComponentType {
+
+ /* The tray containing the component.
+ * UpdateSpecificTray(TrayType*, TrayComponentType*) should be called
+ * when content changes.
+ */
+ struct TrayType *tray;
+
+ /* Additional information needed for the component. */
+ void *object;
+
+ /* Coordinates on the tray (valid only after Create). */
+ int x, y;
+
+ /* Coordinates on the screen (valid only after Create). */
+ int screenx, screeny;
+
+ /* Sizing is handled as follows:
+ * - The component is created via a factory method. It sets its
+ * requested size (0 for no preference).
+ * - The SetSize callback is issued with size constraints
+ * (0 for no constraint). The component should update
+ * width and height in SetSize.
+ * - The Create callback is issued with finalized size information.
+ * Resizing is handled as follows:
+ * - A component determines that it needs to change size. It updates
+ * its requested size (0 for no preference).
+ * - The component calls ResizeTray.
+ * - The SetSize callback is issued with size constraints
+ * (0 for no constraint). The component should update
+ * width and height in SetSize.
+ * - The Resize callback is issued with finalized size information.
+ */
+
+ /* Requested component size. */
+ int requestedWidth, requestedHeight;
+
+ /* Actual component size. */
+ int width, height;
+
+ /* Content. */
+ Window window;
+ Pixmap pixmap;
+
+ /* Create the component. */
+ void (*Create)(struct TrayComponentType *cp);
+
+ /* Destroy the component. */
+ void (*Destroy)(struct TrayComponentType *cp);
+
+ /* Set the size known so far for items that need width/height ratios.
+ * Either width or height may be zero.
+ * This is called before Create.
+ */
+ void (*SetSize)(struct TrayComponentType *cp, int width, int height);
+
+ /* Resize the component. */
+ void (*Resize)(struct TrayComponentType *cp);
+
+ /* Callback for mouse clicks. */
+ void (*ProcessButtonEvent)(struct TrayComponentType *cp,
+ int x, int y, int mask);
+
+ /* Callback for mouse motion. */
+ void (*ProcessMotionEvent)(struct TrayComponentType *cp,
+ int x, int y, int mask);
+
+ /* The next component in the tray. */
+ struct TrayComponentType *next;
+
+} TrayComponentType;
+
+typedef struct TrayType {
+
+ int x, y;
+ int requestedWidth, requestedHeight;
+ int width, height;
+ int border;
+ WinLayerType layer;
+ LayoutType layout;
+ TrayAlignmentType valign, halign;
+
+ int autoHide;
+ int hidden;
+
+ Window window;
+
+ struct TrayComponentType *components;
+ struct TrayComponentType *componentsTail;
+
+ struct TrayType *next;
+
+} TrayType;
+
+
+void InitializeTray();
+void StartupTray();
+void ShutdownTray();
+void DestroyTray();
+
+/** Create a new tray.
+ * @return A new, empty tray.
+ */
+TrayType *CreateTray();
+
+/** Create a tray component.
+ * @return A new tray component structure.
+ */
+TrayComponentType *CreateTrayComponent();
+
+/** Add a tray component to a tray.
+ * @param tp The tray to update.
+ * @param cp The tray component to add.
+ */
+void AddTrayComponent(TrayType *tp, TrayComponentType *cp);
+
+/** Show a tray.
+ * @param tp The tray to show.
+ */
+void ShowTray(TrayType *tp);
+
+/** Hide a tray.
+ * @param tp The tray to hide.
+ */
+void HideTray(TrayType *tp);
+
+/** Draw all trays. */
+void DrawTray();
+
+/** Draw a specific tray.
+ * @param tp The tray to draw.
+ */
+void DrawSpecificTray(const TrayType *tp);
+
+/** Update a component on a tray.
+ * @param tp The tray containing the component.
+ * @param cp The component that needs updating.
+ */
+void UpdateSpecificTray(const TrayType *tp, const TrayComponentType *cp);
+
+/** Resize a tray.
+ * @param tp The tray to resize containing the new requested size information.
+ */
+void ResizeTray(TrayType *tp);
+
+/** Get a linked list of trays.
+ * @return The trays.
+ */
+TrayType *GetTrays();
+
+/** Get a window to use as the supporting window.
+ * This is used by clients to validate that compliant window manager is
+ * running.
+ * @return The supporting window.
+ */
+Window GetSupportingWindow();
+
+/** Process an event that may be for a tray.
+ * @param event The event to process.
+ * @return 1 if this event was for a tray, 0 otherwise.
+ */
+int ProcessTrayEvent(const XEvent *event);
+
+/** Signal the trays.
+ * This function is called regularly so that autohide, etc. can take place.
+ * @param now The current time.
+ * @param x The mouse x-coordinate (root relative).
+ * @param y The mouse y-coordinate (root relative).
+ */
+void SignalTray(const struct TimeType *now, int x, int y);
+
+void SetAutoHideTray(TrayType *tp, int v);
+void SetTrayX(TrayType *tp, const char *str);
+void SetTrayY(TrayType *tp, const char *str);
+void SetTrayWidth(TrayType *tp, const char *str);
+void SetTrayHeight(TrayType *tp, const char *str);
+void SetTrayLayout(TrayType *tp, const char *str);
+void SetTrayLayer(TrayType *tp, const char *str);
+void SetTrayBorder(TrayType *tp, const char *str);
+void SetTrayHorizontalAlignment(TrayType *tp, const char *str);
+void SetTrayVerticalAlignment(TrayType *tp, const char *str);
+
+#endif
+
diff --git a/src/traybutton.c b/src/traybutton.c
new file mode 100644
index 0000000..b596ba0
--- /dev/null
+++ b/src/traybutton.c
@@ -0,0 +1,454 @@
+/***************************************************************************
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "traybutton.h"
+#include "tray.h"
+#include "icon.h"
+#include "image.h"
+#include "error.h"
+#include "root.h"
+#include "main.h"
+#include "color.h"
+#include "font.h"
+#include "button.h"
+#include "misc.h"
+#include "screen.h"
+#include "desktop.h"
+#include "popup.h"
+#include "timing.h"
+
+#define BUTTON_SIZE 4
+
+typedef struct TrayButtonType {
+
+ TrayComponentType *cp;
+
+ char *label;
+ char *popup;
+ char *iconName;
+ IconNode *icon;
+
+ char *action;
+
+ int mousex;
+ int mousey;
+ TimeType mouseTime;
+
+ struct TrayButtonType *next;
+
+} TrayButtonType;
+
+static TrayButtonType *buttons;
+
+static void CheckedCreate(TrayComponentType *cp);
+static void Create(TrayComponentType *cp);
+static void Destroy(TrayComponentType *cp);
+static void SetSize(TrayComponentType *cp, int width, int height);
+static void Resize(TrayComponentType *cp);
+
+static void ProcessButtonEvent(TrayComponentType *cp,
+ int x, int y, int mask);
+static void ProcessMotionEvent(TrayComponentType *cp,
+ int x, int y, int mask);
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeTrayButtons() {
+ buttons = NULL;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void StartupTrayButtons() {
+
+ TrayButtonType *bp;
+
+ for(bp = buttons; bp; bp = bp->next) {
+ if(bp->label) {
+ bp->cp->requestedWidth
+ = GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4;
+ bp->cp->requestedHeight
+ = GetStringHeight(FONT_TRAYBUTTON);
+ } else {
+ bp->cp->requestedWidth = 0;
+ bp->cp->requestedHeight = 0;
+ }
+ if(bp->iconName) {
+ bp->icon = LoadNamedIcon(bp->iconName);
+ if(bp->icon) {
+ bp->cp->requestedWidth += bp->icon->image->width;
+ bp->cp->requestedHeight += bp->icon->image->height;
+ } else {
+ Warning("could not load tray icon: \"%s\"", bp->iconName);
+ }
+ }
+ bp->cp->requestedWidth += 2 * BUTTON_SIZE;
+ bp->cp->requestedHeight += 2 * BUTTON_SIZE;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShutdownTrayButtons() {
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyTrayButtons() {
+
+ TrayButtonType *bp;
+
+ while(buttons) {
+ bp = buttons->next;
+ if(buttons->label) {
+ Release(buttons->label);
+ }
+ if(buttons->iconName) {
+ Release(buttons->iconName);
+ }
+ if(buttons->action) {
+ Release(buttons->action);
+ }
+ if(buttons->popup) {
+ Release(buttons->popup);
+ }
+ Release(buttons);
+ buttons = bp;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+TrayComponentType *CreateTrayButton(const char *iconName,
+ const char *label, const char *action,
+ const char *popup, int width, int height) {
+
+ TrayButtonType *bp;
+ TrayComponentType *cp;
+
+ if((label == NULL || strlen(label) == 0)
+ && (iconName == NULL || strlen(iconName) == 0)) {
+ Warning("no icon or label for TrayButton");
+ return NULL;
+ }
+
+ if(width < 0) {
+ Warning("invalid TrayButton width: %d", width);
+ width = 0;
+ }
+ if(height < 0) {
+ Warning("invalid TrayButton height: %d", height);
+ height = 0;
+ }
+
+ bp = Allocate(sizeof(TrayButtonType));
+ bp->next = buttons;
+ buttons = bp;
+
+ bp->icon = NULL;
+ if(iconName) {
+ bp->iconName = Allocate(strlen(iconName) + 1);
+ strcpy(bp->iconName, iconName);
+ } else {
+ bp->iconName = NULL;
+ }
+
+ if(label) {
+ bp->label = Allocate(strlen(label) + 1);
+ strcpy(bp->label, label);
+ } else {
+ bp->label = NULL;
+ }
+
+ if(action) {
+ bp->action = Allocate(strlen(action) + 1);
+ strcpy(bp->action, action);
+ } else {
+ bp->action = NULL;
+ }
+
+ if(popup) {
+ bp->popup = Allocate(strlen(popup) + 1);
+ strcpy(bp->popup, popup);
+ } else {
+ bp->popup = NULL;
+ }
+
+ cp = CreateTrayComponent();
+ cp->object = bp;
+ bp->cp = cp;
+ cp->requestedWidth = width;
+ cp->requestedHeight = height;
+
+ cp->Create = CheckedCreate;
+ cp->Destroy = Destroy;
+ cp->SetSize = SetSize;
+ cp->Resize = Resize;
+
+ cp->ProcessButtonEvent = ProcessButtonEvent;
+ if(popup || label) {
+ cp->ProcessMotionEvent = ProcessMotionEvent;
+ }
+
+ return cp;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetSize(TrayComponentType *cp, int width, int height) {
+
+ TrayButtonType *bp;
+ int labelWidth, labelHeight;
+ int iconWidth, iconHeight;
+ double ratio;
+
+ bp = (TrayButtonType*)cp->object;
+
+ if(bp->icon) {
+
+ if(bp->label) {
+ labelWidth = GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4;
+ labelHeight = GetStringHeight(FONT_TRAYBUTTON);
+ } else {
+ labelWidth = 0;
+ labelHeight = 0;
+ }
+
+ iconWidth = bp->icon->image->width;
+ iconHeight = bp->icon->image->height;
+ ratio = (double)iconWidth / iconHeight;
+
+ if(width > 0) {
+
+ /* Compute height from width. */
+ iconWidth = width - labelWidth - 2 * BUTTON_SIZE;
+ iconHeight = iconWidth / ratio;
+ height = Max(iconHeight, labelHeight) + 2 * BUTTON_SIZE;
+
+ } else if(height > 0) {
+
+ /* Compute width from height. */
+ iconHeight = height - 2 * BUTTON_SIZE;
+ iconWidth = iconHeight * ratio;
+ width = iconWidth + labelWidth + 2 * BUTTON_SIZE;
+
+ }
+
+ cp->width = width;
+ cp->height = height;
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void CheckedCreate(TrayComponentType *cp) {
+
+ TrayButtonType *bp;
+
+ bp = (TrayButtonType*)cp->object;
+
+ /* Validate the action for this tray button. */
+ if(bp->action && strlen(bp->action) > 0) {
+ if(!strncmp(bp->action, "exec:", 5)) {
+ /* Valid. */
+ } else if(!strncmp(bp->action, "root:", 5)) {
+ /* Valid. However, the specified root menu may not exist.
+ * This case is handled in ValidateTrayButtons.
+ */
+ } else if(!strcmp(bp->action, "showdesktop")) {
+ /* Valid. */
+ } else {
+ Warning("invalid TrayButton action: \"%s\"", bp->action);
+ }
+ } else {
+ /* Valid. However, root menu 1 may not exist.
+ * This case is handled in ValidateTrayButtons.
+ */
+ }
+
+ Create(cp);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Create(TrayComponentType *cp) {
+
+ ButtonNode button;
+ TrayButtonType *bp;
+ int labelx;
+
+ bp = (TrayButtonType*)cp->object;
+
+ cp->pixmap = JXCreatePixmap(display, rootWindow,
+ cp->width, cp->height, rootDepth);
+
+ JXSetForeground(display, rootGC, colors[COLOR_TRAYBUTTON_BG]);
+ JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, cp->width, cp->height);
+
+ ResetButton(&button, cp->pixmap, rootGC);
+ button.type = BUTTON_TASK;
+ button.width = cp->width - 3;
+ button.height = cp->height - 3;
+ button.x = 1;
+ button.y = 1;
+ DrawButton(&button);
+
+ /* Compute the offset of the text. */
+ if(bp->label) {
+ if(!bp->icon) {
+ labelx = 2 + cp->width / 2;
+ labelx -= GetStringWidth(FONT_TRAYBUTTON, bp->label) / 2;
+ } else {
+ labelx = cp->width;
+ labelx -= GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4;
+ }
+ } else {
+ labelx = cp->width;
+ }
+ labelx -= BUTTON_SIZE;
+
+ if(bp->icon) {
+ PutIcon(bp->icon, cp->pixmap, BUTTON_SIZE, BUTTON_SIZE,
+ labelx - BUTTON_SIZE, cp->height - BUTTON_SIZE * 2);
+ }
+
+ if(bp->label) {
+ RenderString(cp->pixmap, FONT_TRAYBUTTON, COLOR_TRAYBUTTON_FG,
+ labelx + 2, cp->height / 2 - GetStringHeight(FONT_TRAYBUTTON) / 2,
+ cp->width - labelx, NULL, bp->label);
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Resize(TrayComponentType *cp) {
+
+ Destroy(cp);
+ Create(cp);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void Destroy(TrayComponentType *cp) {
+ if(cp->pixmap != None) {
+ JXFreePixmap(display, cp->pixmap);
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ProcessButtonEvent(TrayComponentType *cp, int x, int y, int mask) {
+
+ const ScreenType *sp;
+ int mwidth, mheight;
+ int button;
+
+ TrayButtonType *bp = (TrayButtonType*)cp->object;
+
+ Assert(bp);
+
+ if(bp->action && strlen(bp->action) > 0) {
+ if(!strncmp(bp->action, "exec:", 5)) {
+ RunCommand(bp->action + 5);
+ return;
+ } else if(!strncmp(bp->action, "root:", 5)) {
+ button = atoi(bp->action + 5);
+ } else if(!strcmp(bp->action, "showdesktop")) {
+ ShowDesktop();
+ return;
+ } else {
+ return;
+ }
+ } else {
+ button = 1;
+ }
+
+ GetRootMenuSize(button, &mwidth, &mheight);
+
+ sp = GetCurrentScreen(cp->screenx, cp->screeny);
+
+ if(cp->tray->layout == LAYOUT_HORIZONTAL) {
+ x = cp->screenx;
+ if(cp->screeny + cp->height / 2 < sp->y + sp->height / 2) {
+ y = cp->screeny + cp->height;
+ } else {
+ y = cp->screeny - mheight;
+ }
+ } else {
+ y = cp->screeny;
+ if(cp->screenx + cp->width / 2 < sp->x + sp->width / 2) {
+ x = cp->screenx + cp->width;
+ } else {
+ x = cp->screenx - mwidth;
+ }
+ }
+
+ ShowRootMenu(button, x, y);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ProcessMotionEvent(TrayComponentType *cp, int x, int y, int mask) {
+
+ TrayButtonType *bp = (TrayButtonType*)cp->object;
+
+ bp->mousex = cp->screenx + x;
+ bp->mousey = cp->screeny + y;
+ GetCurrentTime(&bp->mouseTime);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SignalTrayButton(const TimeType *now, int x, int y) {
+
+ TrayButtonType *bp;
+ const char *popup;
+
+ for(bp = buttons; bp; bp = bp->next) {
+ if(bp->popup) {
+ popup = bp->popup;
+ } else if(bp->label) {
+ popup = bp->label;
+ } else {
+ continue;
+ }
+ if(abs(bp->mousex - x) < POPUP_DELTA
+ && abs(bp->mousey - y) < POPUP_DELTA) {
+ if(GetTimeDifference(now, &bp->mouseTime) >= popupDelay) {
+ ShowPopup(x, y, popup);
+ }
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ValidateTrayButtons() {
+
+ TrayButtonType *bp;
+ int bindex;
+
+ for(bp = buttons; bp; bp = bp->next) {
+ if(bp->action && !strncmp(bp->action, "root:", 5)) {
+ bindex = atoi(bp->action + 5);
+ if(!IsRootMenuDefined(bindex)) {
+ Warning("tray button: root menu %d not defined", bindex);
+ }
+ }
+ }
+
+}
+
diff --git a/src/traybutton.h b/src/traybutton.h
new file mode 100644
index 0000000..bd22494
--- /dev/null
+++ b/src/traybutton.h
@@ -0,0 +1,51 @@
+/**
+ * @file traybutton.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Tray button tray component.
+ *
+ */
+
+#ifndef TRAY_BUTTON_H
+#define TRAY_BUTTON_H
+
+struct TrayComponentType;
+struct TimeType;
+
+/*@{*/
+void InitializeTrayButtons();
+void StartupTrayButtons();
+void ShutdownTrayButtons();
+void DestroyTrayButtons();
+/*@}*/
+
+/** Create a tray button component.
+ * @param iconName The name of the icon to use for the button.
+ * @param label The label to use for the button.
+ * @param action The action to take when the button is clicked.
+ * @param popup Text to display in a popup window.
+ * @param width The width to use for the button (0 for default).
+ * @param height The height to use for the button (0 for default).
+ * @return A new tray button component.
+ */
+struct TrayComponentType *CreateTrayButton(
+ const char *iconName, const char *label, const char *action,
+ const char *popup, int width, int height);
+
+/** Signal a tray button.
+ * @param now The current time.
+ * @param x The x-coordinate of the mouse (root relative).
+ * @param y The y-coordinate of the mouse (root relative).
+ */
+void SignalTrayButton(const struct TimeType *now, int x, int y);
+
+/** Validate the tray buttons and print a warning if something is wrong.
+ * This is called after parsing the configuration file(s) to determine
+ * if a root menu is defined for each each tray button that specifies
+ * a root menu.
+ */
+void ValidateTrayButtons();
+
+#endif
+
diff --git a/src/winmenu.c b/src/winmenu.c
new file mode 100644
index 0000000..7b537cc
--- /dev/null
+++ b/src/winmenu.c
@@ -0,0 +1,327 @@
+/****************************************************************************
+ * Functions for handling window menus.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ****************************************************************************/
+
+#include "jwm.h"
+#include "winmenu.h"
+#include "client.h"
+#include "menu.h"
+#include "main.h"
+#include "desktop.h"
+#include "move.h"
+#include "resize.h"
+#include "event.h"
+#include "cursor.h"
+#include "misc.h"
+
+static const char *SENDTO_TEXT = "Send To";
+static const char *LAYER_TEXT = "Layer";
+
+static Menu *CreateWindowMenu();
+static void RunWindowCommand(const MenuAction *action);
+
+static void CreateWindowLayerMenu(Menu *menu);
+static void CreateWindowSendToMenu(Menu *menu);
+static void AddWindowMenuItem(Menu *menu, const char *name,
+ MenuActionType type, int value);
+
+static ClientNode *client = NULL;
+
+/****************************************************************************
+ ****************************************************************************/
+void GetWindowMenuSize(ClientNode *np, int *width, int *height) {
+
+ Menu *menu;
+
+ client = np;
+ menu = CreateWindowMenu();
+ InitializeMenu(menu);
+ *width = menu->width;
+ *height = menu->height;
+ DestroyMenu(menu);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ShowWindowMenu(ClientNode *np, int x, int y) {
+
+ Menu *menu;
+
+ client = np;
+ menu = CreateWindowMenu();
+
+ InitializeMenu(menu);
+
+ ShowMenu(menu, RunWindowCommand, x, y);
+
+ DestroyMenu(menu);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+Menu *CreateWindowMenu() {
+
+ Menu *menu;
+
+ menu = Allocate(sizeof(Menu));
+ menu->itemHeight = 0;
+ menu->items = NULL;
+ menu->label = NULL;
+
+ /* Note that items are added in reverse order of display. */
+
+ if(!(client->state.status & STAT_WMDIALOG)) {
+ AddWindowMenuItem(menu, "Close", MA_CLOSE, 0);
+ AddWindowMenuItem(menu, "Kill", MA_KILL, 0);
+ AddWindowMenuItem(menu, NULL, MA_NONE, 0);
+ }
+
+ if(client->state.status & (STAT_MAPPED | STAT_SHADED)) {
+ if(client->state.border & BORDER_RESIZE) {
+ AddWindowMenuItem(menu, "Resize", MA_RESIZE, 0);
+ }
+ if(client->state.border & BORDER_MOVE) {
+ AddWindowMenuItem(menu, "Move", MA_MOVE, 0);
+ }
+ }
+
+ if(client->state.border & BORDER_MIN) {
+
+ if(client->state.status & STAT_MINIMIZED) {
+ AddWindowMenuItem(menu, "Restore", MA_RESTORE, 0);
+ } else {
+ if(client->state.status & STAT_SHADED) {
+ AddWindowMenuItem(menu, "Unshade", MA_SHADE, 0);
+ } else {
+ AddWindowMenuItem(menu, "Shade", MA_SHADE, 0);
+ }
+ AddWindowMenuItem(menu, "Minimize", MA_MINIMIZE, 0);
+ }
+
+ }
+
+ if((client->state.border & BORDER_MAX)
+ && (client->state.status & STAT_MAPPED)) {
+
+ AddWindowMenuItem(menu, "Maximize", MA_MAXIMIZE, 0);
+ }
+
+ if(!(client->state.status & STAT_WMDIALOG)) {
+
+ if(client->state.status & STAT_STICKY) {
+ AddWindowMenuItem(menu, "Unstick", MA_STICK, 0);
+ } else {
+ AddWindowMenuItem(menu, "Stick", MA_STICK, 0);
+ }
+
+ CreateWindowLayerMenu(menu);
+
+ if(!(client->state.status & STAT_STICKY)) {
+ CreateWindowSendToMenu(menu);
+ }
+
+ }
+
+ return menu;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void CreateWindowLayerMenu(Menu *menu) {
+
+ Menu *submenu;
+ MenuItem *item;
+ char str[10];
+ unsigned int x;
+
+ item = Allocate(sizeof(MenuItem));
+ item->type = MENU_ITEM_SUBMENU;
+ item->name = CopyString(LAYER_TEXT);
+ item->action.type = MA_NONE;
+ item->action.data.str = NULL;
+ item->iconName = NULL;
+
+ item->next = menu->items;
+ menu->items = item;
+
+ submenu = Allocate(sizeof(Menu));
+ item->submenu = submenu;
+ submenu->itemHeight = 0;
+ submenu->items = NULL;
+ submenu->label = NULL;
+
+ if(client->state.layer == LAYER_TOP) {
+ AddWindowMenuItem(submenu, "[Top]", MA_LAYER, LAYER_TOP);
+ } else {
+ AddWindowMenuItem(submenu, "Top", MA_LAYER, LAYER_TOP);
+ }
+
+ str[4] = 0;
+ for(x = LAYER_TOP - 1; x > LAYER_BOTTOM; x--) {
+ if(x == LAYER_NORMAL) {
+ if(client->state.layer == x) {
+ AddWindowMenuItem(submenu, "[Normal]", MA_LAYER, x);
+ } else {
+ AddWindowMenuItem(submenu, "Normal", MA_LAYER, x);
+ }
+ } else {
+ if(client->state.layer == x) {
+ str[0] = '[';
+ str[3] = ']';
+ } else {
+ str[0] = ' ';
+ str[3] = ' ';
+ }
+ if(x < 10) {
+ str[1] = ' ';
+ } else {
+ str[1] = (x / 10) + '0';
+ }
+ str[2] = (x % 10) + '0';
+ AddWindowMenuItem(submenu, str, MA_LAYER, x);
+ }
+ }
+
+ if(client->state.layer == LAYER_BOTTOM) {
+ AddWindowMenuItem(submenu, "[Bottom]", MA_LAYER, LAYER_BOTTOM);
+ } else {
+ AddWindowMenuItem(submenu, "Bottom", MA_LAYER, LAYER_BOTTOM);
+ }
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void CreateWindowSendToMenu(Menu *menu) {
+
+ unsigned int mask;
+ unsigned int x;
+
+ mask = 0;
+ for(x = 0; x < desktopCount; x++) {
+ if(client->state.desktop == x
+ || (client->state.status & STAT_STICKY)) {
+ mask |= 1 << x;
+ }
+ }
+
+ AddWindowMenuItem(menu, SENDTO_TEXT, MA_NONE, 0);
+
+ /* Now the first item in the menu is for the desktop list. */
+ menu->items->submenu = CreateDesktopMenu(mask);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void AddWindowMenuItem(Menu *menu, const char *name,
+ MenuActionType type, int value) {
+
+ MenuItem *item;
+
+ item = Allocate(sizeof(MenuItem));
+ if(name) {
+ item->type = MENU_ITEM_NORMAL;
+ } else {
+ item->type = MENU_ITEM_SEPARATOR;
+ }
+ item->name = CopyString(name);
+ item->action.type = type;
+ item->action.data.i = value;
+ item->iconName = NULL;
+ item->submenu = NULL;
+
+ item->next = menu->items;
+ menu->items = item;
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void ChooseWindow(const MenuAction *action) {
+
+ XEvent event;
+ ClientNode *np;
+
+ GrabMouseForChoose();
+
+ for(;;) {
+
+ WaitForEvent(&event);
+
+ if(event.type == ButtonPress) {
+ if(event.xbutton.button == Button1) {
+ np = FindClientByWindow(event.xbutton.subwindow);
+ if(np) {
+ client = np;
+ RunWindowCommand(action);
+ }
+ }
+ break;
+ } else if(event.type == KeyPress) {
+ break;
+ }
+
+ }
+
+ JXUngrabPointer(display, CurrentTime);
+
+}
+
+/****************************************************************************
+ ****************************************************************************/
+void RunWindowCommand(const MenuAction *action) {
+
+ switch(action->type) {
+ case MA_STICK:
+ if(client->state.status & STAT_STICKY) {
+ SetClientSticky(client, 0);
+ } else {
+ SetClientSticky(client, 1);
+ }
+ break;
+ case MA_MAXIMIZE:
+ MaximizeClient(client);
+ break;
+ case MA_MINIMIZE:
+ MinimizeClient(client);
+ break;
+ case MA_RESTORE:
+ RestoreClient(client, 1);
+ break;
+ case MA_CLOSE:
+ DeleteClient(client);
+ break;
+ case MA_SENDTO:
+ case MA_DESKTOP:
+ SetClientDesktop(client, action->data.i);
+ break;
+ case MA_SHADE:
+ if(client->state.status & STAT_SHADED) {
+ UnshadeClient(client);
+ } else {
+ ShadeClient(client);
+ }
+ break;
+ case MA_MOVE:
+ MoveClientKeyboard(client);
+ break;
+ case MA_RESIZE:
+ ResizeClientKeyboard(client);
+ break;
+ case MA_KILL:
+ KillClient(client);
+ break;
+ case MA_LAYER:
+ SetClientLayer(client, action->data.i);
+ break;
+ default:
+ Debug("unknown window command: %d", action->type);
+ break;
+ }
+
+}
+
diff --git a/src/winmenu.h b/src/winmenu.h
new file mode 100644
index 0000000..a7b1f6e
--- /dev/null
+++ b/src/winmenu.h
@@ -0,0 +1,37 @@
+/**
+ * @file tray.h
+ * @author Joe Wingbermuehle
+ * @date 2004-2006
+ *
+ * @brief Header for the window menu functions.
+ *
+ */
+
+#ifndef WINMENU_H
+#define WINMENU_H
+
+#include "menu.h"
+
+struct ClientNode;
+
+/** Get the size of a window menu.
+ * @param np The client for the window menu.
+ * @param width The width return.
+ * @param heigth The height return.
+ */
+void GetWindowMenuSize(struct ClientNode *np, int *width, int *height);
+
+/** Show a window menu.
+ * @param np The client for the window menu.
+ * @param x The x-coordinate of the menu (root relative).
+ * @param y The y-coordinate of the menu (root relative).
+ */
+void ShowWindowMenu(struct ClientNode *np, int x, int y);
+
+/** Grab the mouse to select a window.
+ * @param action The action to perform when a window is selected.
+ */
+void ChooseWindow(const MenuAction *action);
+
+#endif
+
diff --git a/src/x.xpm b/src/x.xpm
new file mode 100644
index 0000000..1e9376c
--- /dev/null
+++ b/src/x.xpm
@@ -0,0 +1,32 @@
+static char *x_xpm[]={
+"28 28 2 1",
+". c None",
+"# c #ff0000",
+"............................",
+"............................",
+"............................",
+"............................",
+".########..............##...",
+"..########............##....",
+"...########..........##.....",
+"....########........##......",
+".....########......##.......",
+"......########....##........",
+".......########..##.........",
+"........######..##..........",
+".........####..##...........",
+"..........##..####..........",
+".........##..######.........",
+"........##..########........",
+".......##....########.......",
+"......##......########......",
+".....##........########.....",
+"....##..........########....",
+"...##............########...",
+"..##..............########..",
+".##................########.",
+"............................",
+"............................",
+"............................",
+"............................",
+"............................"};