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/client.c | 1423 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1423 insertions(+) create mode 100644 src/client.c (limited to 'src/client.c') 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); + } + + } + +} + -- cgit v1.2.3