From a041d9898e6d699bd8c0c25482ec574feb03c547 Mon Sep 17 00:00:00 2001 From: "John Ankarstr\\xf6m" Date: Sat, 29 May 2021 12:54:47 +0200 Subject: First commit This is the original state of the released tarball for JWM 1.8, which will serve as my starting point for further modifications. --- src/Makefile.in | 38 ++ src/border.c | 753 ++++++++++++++++++++++++++ src/border.h | 80 +++ src/button.c | 212 ++++++++ src/button.h | 63 +++ src/client.c | 1423 +++++++++++++++++++++++++++++++++++++++++++++++++ src/client.h | 285 ++++++++++ src/clock.c | 332 ++++++++++++ src/clock.h | 41 ++ src/color.c | 606 +++++++++++++++++++++ src/color.h | 91 ++++ src/command.c | 124 +++++ src/command.h | 36 ++ src/confirm.c | 412 +++++++++++++++ src/confirm.h | 36 ++ src/cursor.c | 313 +++++++++++ src/cursor.h | 44 ++ src/debug.c | 396 ++++++++++++++ src/debug.h | 104 ++++ src/desktop.c | 239 +++++++++ src/desktop.h | 60 +++ src/dock.c | 602 +++++++++++++++++++++ src/dock.h | 43 ++ src/error.c | 108 ++++ src/error.h | 38 ++ src/event.c | 1138 +++++++++++++++++++++++++++++++++++++++ src/event.h | 28 + src/font.c | 295 +++++++++++ src/font.h | 43 ++ src/group.c | 299 +++++++++++ src/group.h | 48 ++ src/help.c | 84 +++ src/help.h | 26 + src/hint.c | 970 ++++++++++++++++++++++++++++++++++ src/hint.h | 173 ++++++ src/icon.c | 726 +++++++++++++++++++++++++ src/icon.h | 110 ++++ src/image.c | 362 +++++++++++++ src/image.h | 47 ++ src/jwm.h | 128 +++++ src/jxlib.h | 420 +++++++++++++++ src/key.c | 452 ++++++++++++++++ src/key.h | 57 ++ src/lex.c | 572 ++++++++++++++++++++ src/lex.h | 115 ++++ src/main.c | 507 ++++++++++++++++++ src/main.h | 56 ++ src/match.c | 80 +++ src/match.h | 21 + src/menu.c | 799 ++++++++++++++++++++++++++++ src/menu.h | 89 ++++ src/misc.c | 196 +++++++ src/misc.h | 37 ++ src/move.c | 729 +++++++++++++++++++++++++ src/move.h | 61 +++ src/outline.c | 73 +++ src/outline.h | 32 ++ src/pager.c | 331 ++++++++++++ src/pager.h | 27 + src/parse.c | 1551 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.h | 19 + src/place.c | 647 +++++++++++++++++++++++ src/place.h | 34 ++ src/popup.c | 232 ++++++++ src/popup.h | 35 ++ src/render.c | 226 ++++++++ src/render.h | 25 + src/resize.c | 491 +++++++++++++++++ src/resize.h | 42 ++ src/root.c | 315 +++++++++++ src/root.h | 65 +++ src/screen.c | 138 +++++ src/screen.h | 53 ++ src/status.c | 274 ++++++++++ src/status.h | 55 ++ src/swallow.c | 274 ++++++++++ src/swallow.h | 43 ++ src/taskbar.c | 936 ++++++++++++++++++++++++++++++++ src/taskbar.h | 64 +++ src/theme.c | 88 ++++ src/theme.h | 31 ++ src/timing.c | 71 +++ src/timing.h | 43 ++ src/tray.c | 1104 ++++++++++++++++++++++++++++++++++++++ src/tray.h | 214 ++++++++ src/traybutton.c | 454 ++++++++++++++++ src/traybutton.h | 51 ++ src/winmenu.c | 327 ++++++++++++ src/winmenu.h | 37 ++ src/x.xpm | 32 ++ 90 files changed, 23581 insertions(+) create mode 100644 src/Makefile.in create mode 100644 src/border.c create mode 100644 src/border.h create mode 100644 src/button.c create mode 100644 src/button.h create mode 100644 src/client.c create mode 100644 src/client.h create mode 100644 src/clock.c create mode 100644 src/clock.h create mode 100644 src/color.c create mode 100644 src/color.h create mode 100644 src/command.c create mode 100644 src/command.h create mode 100644 src/confirm.c create mode 100644 src/confirm.h create mode 100644 src/cursor.c create mode 100644 src/cursor.h create mode 100644 src/debug.c create mode 100644 src/debug.h create mode 100644 src/desktop.c create mode 100644 src/desktop.h create mode 100644 src/dock.c create mode 100644 src/dock.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/event.c create mode 100644 src/event.h create mode 100644 src/font.c create mode 100644 src/font.h create mode 100644 src/group.c create mode 100644 src/group.h create mode 100644 src/help.c create mode 100644 src/help.h create mode 100644 src/hint.c create mode 100644 src/hint.h create mode 100644 src/icon.c create mode 100644 src/icon.h create mode 100644 src/image.c create mode 100644 src/image.h create mode 100644 src/jwm.h create mode 100644 src/jxlib.h create mode 100644 src/key.c create mode 100644 src/key.h create mode 100644 src/lex.c create mode 100644 src/lex.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/match.c create mode 100644 src/match.h create mode 100644 src/menu.c create mode 100644 src/menu.h create mode 100644 src/misc.c create mode 100644 src/misc.h create mode 100644 src/move.c create mode 100644 src/move.h create mode 100644 src/outline.c create mode 100644 src/outline.h create mode 100644 src/pager.c create mode 100644 src/pager.h create mode 100644 src/parse.c create mode 100644 src/parse.h create mode 100644 src/place.c create mode 100644 src/place.h create mode 100644 src/popup.c create mode 100644 src/popup.h create mode 100644 src/render.c create mode 100644 src/render.h create mode 100644 src/resize.c create mode 100644 src/resize.h create mode 100644 src/root.c create mode 100644 src/root.h create mode 100644 src/screen.c create mode 100644 src/screen.h create mode 100644 src/status.c create mode 100644 src/status.h create mode 100644 src/swallow.c create mode 100644 src/swallow.h create mode 100644 src/taskbar.c create mode 100644 src/taskbar.h create mode 100644 src/theme.c create mode 100644 src/theme.h create mode 100644 src/timing.c create mode 100644 src/timing.h create mode 100644 src/tray.c create mode 100644 src/tray.h create mode 100644 src/traybutton.c create mode 100644 src/traybutton.h create mode 100644 src/winmenu.c create mode 100644 src/winmenu.h create mode 100644 src/x.xpm (limited to 'src') 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 = 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 +#include +#include +#include +#ifdef HAVE_ALLOCA_H +# include +#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 +#include +#include +#include + +#ifdef HAVE_STDARG_H +# include +#endif +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_TIME_H +# include +#endif +#ifdef HAVE_SYS_WAIT_H +# include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_SELECT_H +# include +#endif + +#include +#ifdef HAVE_X11_XUTIL_H +# include +#endif +#ifdef HAVE_X11_XRESOURCE_H +# include +#endif +#ifdef HAVE_X11_CURSORFONT_H +# include +#endif +#ifdef HAVE_X11_XPROTO_H +# include +#endif +#ifdef HAVE_X11_XATOM_H +# include +#endif +#ifdef HAVE_X11_KEYSYM_H +# include +#endif + +#ifdef USE_XPM +# include +#endif +#ifdef USE_PNG +# include +#endif +#ifdef USE_SHAPE +# include +#endif +#ifdef USE_XINERAMA +# include +#endif +#ifdef USE_XFT +# ifdef HAVE_FT2BUILD_H +# include +# endif +# include +#endif +#ifdef USE_XRENDER +# include +#endif +#ifdef USE_FRIBIDI +# include +# include +#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)) { + x += 2; + break; + } + ++x; + } + } + + while(line[x]) { + + do { + + while(IsSpace(line[x], &lineNumber)) ++x; + + /* Skip comments */ + found = 0; + 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(""", entity, 6)) { + *ch = '\"'; + return 6; + } else if(!strncmp("<", entity, 4)) { + *ch = '<'; + return 4; + } else if(!strncmp(">", entity, 4)) { + *ch = '>'; + return 4; + } else if(!strncmp("&", entity, 5)) { + *ch = '&'; + return 5; + } else if(!strncmp("'", 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", +"............................", +"............................", +"............................", +"............................", +".########..............##...", +"..########............##....", +"...########..........##.....", +"....########........##......", +".....########......##.......", +"......########....##........", +".......########..##.........", +"........######..##..........", +".........####..##...........", +"..........##..####..........", +".........##..######.........", +"........##..########........", +".......##....########.......", +"......##......########......", +".....##........########.....", +"....##..........########....", +"...##............########...", +"..##..............########..", +".##................########.", +"............................", +"............................", +"............................", +"............................", +"............................"}; -- cgit v1.2.3