/*- * 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; } } }