/*-
* Copyright (c) 2001 Jordan DeLong
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "wm.h"
/* font for titles */
#ifdef I18N
XFontSet titlefont;
#else
XFontStruct *titlefont;
#endif
/* list of decoration units */
static SLIST_HEAD(, decor) decor_list = SLIST_HEAD_INITIALIZER(&decor_list);
/* get our titlebar font */
void decor_init() {
char *deffont = "fixed";
#ifdef I18N
titlefont = XLoadQueryFontSet(display, options.title_font ? options.title_font : deffont);
#else
titlefont = XLoadQueryFont(display, options.title_font ? options.title_font : deffont);
#endif
if (!titlefont) {
if (options.title_font) {
#ifdef I18N
titlefont = XLoadQueryFontSet(display, deffont);
#else
titlefont = XLoadQueryFont(display, deffont);
#endif
if (!titlefont)
errx(1, "unable to load fallback titlefont 'fixed'");
} else
errx(1, "unable to load titlefont 'fixed'");
}
}
/* free memory used for decor stuff */
void decor_shutdown() {
decor_t *decor;
/* free the title font */
#ifdef I18N
XFreeFontSet(display, titlefont);
#else
XFreeFont(display, titlefont);
#endif
/* free the list of decor_t */
while (!SLIST_EMPTY(&decor_list)) {
decor = SLIST_FIRST(&decor_list);
SLIST_REMOVE_HEAD(&decor_list, d_list);
free(decor->ident);
free(decor);
}
}
/* function to add a decoration */
void decor_add(decor_t *decor) {
SLIST_INSERT_HEAD(&decor_list, decor, d_list);
}
/* find a decoration unit by name; used during rcfile parser */
decor_t *decor_ident(char *ident) {
decor_t *decor;
SLIST_FOREACH(decor, &decor_list, d_list)
if (strcmp(decor->ident, ident) == 0)
return decor;
return NULL;
}
/* get pos of a decoration unit based on it's type/edge and modifiers */
static void decor_getdims(client_t *client, decor_t *decor, int *x, int *y,
int *pwidth, int *pheight) {
dgroup_t *dgroup;
int width = 0, height = 0;
int vert = 0;
dgroup = client->dgroup;
switch (decor->edge) {
case DE_TOP:
*y = dgroup->top_space - decor->pixmap->height;
break;
case DE_LEFT:
*x = dgroup->left_space - decor->pixmap->width;
vert = 1;
break;
case DE_RIGHT:
*x = dgroup->left_space + client->width;
vert = 1;
break;
case DE_BOTTOM:
*y = dgroup->top_space + client->height;
break;
}
switch (decor->type) {
case DA_FULL:
if (vert) {
width = decor->pixmap->width;
height = client->height;
*y = dgroup->top_space;
} else {
width = dgroup->left_space + client->width + dgroup->right_space;
height = decor->pixmap->height;
*x = 0;
}
break;
case DA_NEAR:
if (vert)
*y = dgroup->top_space;
else
*x = 0;
break;
case DA_FAR:
if (vert)
*y = dgroup->top_space + client->height - decor->pixmap->height;
else
*x = dgroup->left_space + client->width
+ dgroup->right_space - decor->pixmap->width;
break;
}
if (!width || !height) {
*pwidth = decor->pixmap->width;
*pheight = decor->pixmap->height;
} else {
*pwidth = width;
*pheight = height;
}
if (vert) {
*y += decor->offset;
*pheight += decor->lenmod;
*y += client->height * decor->offsetmult;
*pheight += client->height * decor->lenmult;
*x += decor->soffset;
} else {
*x += decor->offset;
*pwidth += decor->lenmod;
*x += client->width * decor->offsetmult;
*pwidth += client->width * decor->lenmult;
*y += decor->soffset;
}
/* finally, make sure that the width/height are valid */
if (*pwidth < 1) *pwidth = 1;
if (*pheight < 1) *pheight = 1;
}
/* shape as a rect, not w/ mask combines (shapeunit) */
#define RECTSHAPE() do { \
rect[0].x = x; rect[0].y = y; \
rect[0].width = width; \
rect[0].height = height; \
XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect, \
1, ShapeUnion, Unsorted); \
} while (0)
/* shape a decoration unit onto this client's frame */
static void decor_shapeunit(client_t *client, decor_t *decor, int idx) {
XRectangle rect[1];
int x, y, width, height;
int num;
decor_getdims(client, decor, &x, &y, &width, &height);
XMoveResizeWindow(display, client->decorwin[idx], x, y, width ? width : 1, height ? height : 1);
/* shape; if the width/height aren't the same as the decor we have a DA_FULL */
if (width == decor->pixmap->width && height == decor->pixmap->height) {
if (decor->flags.shaped)
XShapeCombineMask(display, client->frame, ShapeBounding, x, y,
decor->pixmap->shapemasks[client->screen->num], ShapeUnion);
else
RECTSHAPE();
} else if (height != decor->pixmap->height) {
if (decor->flags.shaped) {
for (num = y; num <= y + height; num += decor->pixmap->height) {
/* we also need to reshape the window itself for these */
XShapeCombineMask(display, client->decorwin[idx], ShapeBounding, 0, num - y,
decor->pixmap->shapemasks[client->screen->num], num == y ? ShapeSet : ShapeUnion);
XShapeCombineMask(display, client->frame, ShapeBounding, x, num,
decor->pixmap->shapemasks[client->screen->num], ShapeUnion);
}
} else {
RECTSHAPE();
}
} else if (width != decor->pixmap->width) {
if (decor->flags.shaped) {
for (num = x; num <= x + width; num += decor->pixmap->width) {
/* we also need to reshape the window itself for these */
XShapeCombineMask(display, client->decorwin[idx], ShapeBounding, num - x, 0,
decor->pixmap->shapemasks[client->screen->num], num == x ? ShapeSet : ShapeUnion);
XShapeCombineMask(display, client->frame, ShapeBounding, num, y,
decor->pixmap->shapemasks[client->screen->num], ShapeUnion);
}
} else {
RECTSHAPE();
}
}
}
#undef RECTSHAPE
/* called on resizes and such to shape/size the client decor, etc */
void decor_shapesize(client_t *client) {
XRectangle rect[4];
dgroup_t *dgroup;
int cnt = 0;
int i;
dgroup = client->dgroup;
/* fill in old chops on right and bottom */
if (client->width > client->last_width) {
rect[cnt].x = dgroup->left_space + client->last_width;
rect[cnt].y = 0;
rect[cnt].width = client->width - client->last_width;
rect[cnt].height = dgroup->top_space + client->height;
cnt++;
}
if (client->height > client->last_height) {
rect[cnt].x = 0;
rect[cnt].y = dgroup->top_space + client->last_height;
rect[cnt].width = dgroup->left_space + client->width;
rect[cnt].height = client->height - client->last_height;
cnt++;
}
if (cnt == 2) {
rect[cnt].x = dgroup->left_space + client->last_width;
rect[cnt].y = dgroup->top_space + client->last_height;
rect[cnt].width = client->width - client->last_width;
rect[cnt].height = client->height - client->last_height;
}
XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect,
cnt, ShapeUnion, Unsorted);
/* chop off the decoration area */
rect[0].x = 0;
rect[0].y = 0;
rect[0].width = dgroup->left_space + client->width + dgroup->right_space;
rect[0].height = dgroup->top_space;
rect[1].x = 0;
rect[1].y = dgroup->top_space;
rect[1].width = dgroup->left_space;
rect[1].height = client->height;
rect[2].x = dgroup->left_space + client->width;
rect[2].y = dgroup->top_space;
rect[2].width = dgroup->right_space;
rect[2].height = client->height;
rect[3].x = 0;
rect[3].y = dgroup->top_space + client->height;
rect[3].width = dgroup->left_space + client->width + dgroup->right_space;
rect[3].height = dgroup->bottom_space;
XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect,
4, ShapeSubtract, Unsorted);
for (i = 0; i < client->dgroup->decor_count; i++)
decor_shapeunit(client, client->dgroup->decor[i], i);
}
/* the initial shaping of a client's decoration, etc */
static void decor_firstshape(client_t *client) {
XRectangle rect[4];
dgroup_t *dgroup;
int i;
dgroup = client->dgroup;
/* chop off the decoration area */
rect[0].x = 0;
rect[0].y = 0;
rect[0].width = dgroup->left_space + client->width + dgroup->right_space;
rect[0].height = dgroup->top_space;
rect[1].x = 0;
rect[1].y = dgroup->top_space;
rect[1].width = dgroup->left_space;
rect[1].height = client->height;
rect[2].x = dgroup->left_space + client->width;
rect[2].y = dgroup->top_space;
rect[2].width = dgroup->right_space;
rect[2].height = client->height;
rect[3].x = 0;
rect[3].y = dgroup->top_space + client->height;
rect[3].width = dgroup->left_space + client->width + dgroup->right_space;
rect[3].height = dgroup->bottom_space;
XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect,
4, ShapeSubtract, Unsorted);
for (i = 0; i < client->dgroup->decor_count; i++)
decor_shapeunit(client, client->dgroup->decor[i], i);
}
/* create a decoration window according to it's tyoe and edge */
static void decor_makewin(client_t *client, decor_t *decor, int idx) {
XSetWindowAttributes attr;
int mask = ButtonPressMask;
int x, y;
int width, height;
decor_getdims(client, decor, &x, &y, &width, &height);
attr.background_pixmap = decor->pixmap->pixmaps[client->screen->num];
client->decorwin[idx] = XCreateWindow(display, client->frame, x, y,
width, height, 0, CopyFromParent, CopyFromParent, CopyFromParent,
CWBackPixmap, &attr);
if (decor->flags.shaped)
XShapeCombineMask(display, client->decorwin[idx], ShapeBounding, 0, 0,
decor->pixmap->shapemasks[client->screen->num], ShapeSet);
if (decor->flags.button)
mask |= ButtonReleaseMask;
if (client->dgroup->title == decor)
mask |= ExposureMask;
XSaveContext(display, client->decorwin[idx], client_context, (XPointer) client);
XSaveContext(display, client->decorwin[idx], decor_context, (XPointer) decor);
XSelectInput(display, client->decorwin[idx], mask);
XMapWindow(display, client->decorwin[idx]);
}
/* called during client_add to add our decoration to a client */
void decor_decorate(client_t *client) {
int i;
/*
* alloc the amount of windows neccessary for this dgroup,
* if there are any.
*/
if (!client->dgroup->decor_count)
return;
client->decorwin = calloc(client->dgroup->decor_count, sizeof(Window));
for (i = 0; i < client->dgroup->decor_count; i++)
decor_makewin(client, client->dgroup->decor[i], i);
decor_firstshape(client);
}
/* undecorate this client: kill the decor wins and free mem */
void decor_undecor(client_t *client) {
int i;
/*
* kill every decoration window we added, if we
* added any.
*/
if (!client->dgroup->decor_count)
return;
for (i = 0; i < client->dgroup->decor_count; i++) {
XDeleteContext(display, client->decorwin[i], decor_context);
XDeleteContext(display, client->decorwin[i], client_context);
XDestroyWindow(display, client->decorwin[i]);
}
free(client->decorwin);
}
/* perform the operation specified by act */
static void decor_act(client_t *client, int act) {
switch (act) {
case ACT_NONE:
break;
case ACT_MOVE:
action_move(client);
break;
case ACT_RESIZE:
action_resize(client);
break;
case ACT_DELETE:
if (!client->flags.nodelete)
action_sendcmesg(client->window, WM_DELETE_WINDOW, CurrentTime);
break;
case ACT_ICONIFY:
action_iconify(client);
break;
case ACT_ZOOM:
action_zoom(client);
break;
}
}
/* handle a press on a button */
static void decor_buttonpress(client_t *client, decor_t *decor, int action) {
XEvent event;
int idx;
/* get the decor index */
for (idx = 0; idx < client->dgroup->decor_count; idx++)
if (client->dgroup->decor[idx] == decor)
break;
/* change the pixmap */
if (decor->pressedmap) {
XSetWindowBackgroundPixmap(display, client->decorwin[idx],
decor->pressedmap->pixmaps[client->screen->num]);
XClearWindow(display, client->decorwin[idx]);
}
/* wait for button release */
while (1) {
XMaskEvent(display, ButtonReleaseMask, &event);
if (event.type == ButtonRelease) {
if (decor->pressedmap) {
XSetWindowBackgroundPixmap(display, client->decorwin[idx],
decor->focpixmap->pixmaps[client->screen->num]);
XClearWindow(display, client->decorwin[idx]);
}
if (event.xbutton.x > 0 && event.xbutton.y > 0 && event.xbutton.x < decor->pixmap->width
&& event.xbutton.y < decor->pixmap->height
&& event.xbutton.root == client->screen->root)
decor_act(client, action);
return;
}
}
}
/* handle a press on a piece of decoration for a client */
void decor_handlepress(client_t *client, decor_t *decor, XButtonEvent *e) {
int action;
if (e->button == Button3)
action = decor->rclick_action;
else if (e->button == Button2)
action = decor->mclick_action;
else
action = decor->lclick_action;
if (decor->flags.button)
decor_buttonpress(client, decor, action);
else
decor_act(client, action);
}
/* modify decoration pixmaps to indicate a client is focused */
void decor_focus(client_t *client) {
int i;
for (i = 0; i < client->dgroup->decor_count; i++) {
if (client->dgroup->decor[i]->focpixmap != client->dgroup->decor[i]->pixmap) {
XSetWindowBackgroundPixmap(display, client->decorwin[i],
client->dgroup->decor[i]->focpixmap->pixmaps[client->screen->num]);
XClearWindow(display, client->decorwin[i]);
if (client->dgroup->title == client->dgroup->decor[i])
decor_expose(client, client->dgroup->decor[i], NULL);
}
}
}
/* modify decoration pixmaps to indicate a client is unfocused (normal) */
void decor_unfocus(client_t *client) {
int i;
for (i = 0; i < client->dgroup->decor_count; i++) {
if (client->dgroup->decor[i]->focpixmap != client->dgroup->decor[i]->pixmap) {
XSetWindowBackgroundPixmap(display, client->decorwin[i],
client->dgroup->decor[i]->pixmap->pixmaps[client->screen->num]);
XClearWindow(display, client->decorwin[i]);
if (client->dgroup->title == client->dgroup->decor[i])
decor_expose(client, client->dgroup->decor[i], NULL);
}
}
}
/* handle exposes on decoration */
void decor_expose(client_t *client, decor_t *decor, XExposeEvent *e) {
int idx;
#ifdef I18N
XFontSetExtents *extent;
extent = XExtentsOfFontSet(titlefont);
#endif
/* we are just drawing the whole thing, so we only use count == 0 */
if (e && e->count > 0)
return;
/* get the decor index */
for (idx = 0; idx < client->dgroup->decor_count; idx++)
if (client->dgroup->decor[idx] == decor)
break;
/*
* since we're just drawing a line of text, optimizing which characters we
* actually need to draw is kinda more trouble than it's worth.
*/
if (client->store_name)
#ifdef I18N
XmbDrawString(display, client->decorwin[idx], titlefont, client->screen->titlegc, client->dgroup->title_x,
client->dgroup->title_y + (extent->max_logical_extent.height * 4 / 5), client->store_name, strlen(client->store_name));
#else
XDrawString(display, client->decorwin[idx], client->screen->titlegc, client->dgroup->title_x,
client->dgroup->title_y + titlefont->ascent, client->store_name, strlen(client->store_name));
#endif
}
/* if a window's title (store_name) changed, we hear about it here */
void decor_titlechange(client_t *client) {
int i;
for (i = 0; i < client->dgroup->decor_count; i++) {
if (client->dgroup->decor[i] == client->dgroup->title) {
XClearWindow(display, client->decorwin[i]);
decor_expose(client, client->dgroup->decor[i], NULL);
break;
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1