/**************************************************************************** * Client placement functions. * Copyright (C) 2005 Joe Wingbermuehle ****************************************************************************/ #include "jwm.h" #include "place.h" #include "client.h" #include "screen.h" #include "border.h" #include "move.h" #include "tray.h" #include "timing.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 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() { } /**************************************************************************** ****************************************************************************/ void ShutdownPlacement() { Strut *sp; 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) { static int prevx = -1; static int prevy = -1; static TimeType last = ZERO_TIME; BoundingBox box; const ScreenType *sp; int height, offset, overflow, width, winx, winy, x, y; int north, south, east, west; TimeType now; unsigned int mask; Window rootReturn, childReturn; 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); /* Check cursor location. */ JXQueryPointer(display, rootWindow, &rootReturn, &childReturn, &x, &y, &winx, &winy, &mask); /* Set offset if needed. */ if(prevx > -1 && prevy > -1 && (abs(x - prevx) <= borderWidth || abs(y - prevy) <= borderWidth)) offset = borderWidth + titleHeight; else offset = 0; /* Unset offset if too late. */ GetCurrentTime(&now); if (GetTimeDifference(&now, &last) > 500) offset = 0; last = now; /* Show window at cursor. */ 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; } x -= width / 2; y -= height / 2; np->x = x; np->y = y; /* Correct for overflow. */ ReturnToBorder(np, north, west); np->x += offset; np->y += offset; MoveMouseToClient(np); /* Save cursor location. */ JXQueryPointer(display, rootWindow, &rootReturn, &childReturn, &x, &y, &winx, &winy, &mask); prevx = x; prevy = y; } 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; } }