aboutsummaryrefslogtreecommitdiff
path: root/src/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/client.c')
-rw-r--r--src/client.c1423
1 files changed, 1423 insertions, 0 deletions
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);
+ }
+
+ }
+
+}
+