/** * @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); } /** Move the cursor to the middle of the client. **/ void MoveMouseToClient(ClientNode *np) { int height, width, x, y; 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; } MoveMouse(rootWindow, x + width / 2, y + height / 2); } /** 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); } } }