/*- * Copyright (c) 2001, 2002 Jordan DeLong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "wm.h" /* list of clients */ clientlist_t client_list = LIST_HEAD_INITIALIZER(&client_list); /* currently focused client */ client_t *client_focused; /* * shutdown client stuff; unload all client_t's and reparent all clients * back to the root window. */ void client_shutdown() { client_t *client; /* remove all clients */ while (!LIST_EMPTY(&client_list)) { client = LIST_FIRST(&client_list); if (!client->flags.internal) { XReparentWindow(display, client->window, client->screen->root, client->x, client->y); /* * XXX: should save their initial border * instead of just setting it back to 1. */ XSetWindowBorderWidth(display, client->window, 1); } client_rm(client); } } /* * XWMHints structure gets handled here; startup state and * if the client wants to be able to recieve input focus. */ static __inline void client_init_wmhints(client_t *client) { Atom type; u_long items, bytes; long *state; int fmt; /* * start it up in the state it thinks it's in, or use the state hint * in the XWMHints structure. */ XGetWindowProperty(display, client->window, WM_STATE, 0, 1, 0, WM_STATE, &type, &fmt, &items, &bytes, (u_char **) &state); if (type == WM_STATE) { client->state = *state; XFree(state); } else if (client->wmhints && client->wmhints->flags & StateHint) client->state = client->wmhints->initial_state; else client->state = WithdrawnState; /* flag noinput windows as nofocus */ if (client->wmhints && client->wmhints->flags & InputHint) if (!client->wmhints->input) client->flags.nofocus = 1; } /* add decoration for a client during client_add */ static __inline void client_decorate(client_t *client, dgroup_t *dgroup) { XSetWindowAttributes attr; /* * figure out what decoration group to use, unless * a hints handler (from a plugin) set it already */ if (!client->dgroup) { if (dgroup) client->dgroup = dgroup; else if (client->flags.transient) client->dgroup = options.dgroup_trans; else if (client->flags.internal) client->dgroup = options.dgroup_internal; else client->dgroup = options.dgroup_default; } attr.override_redirect = 1; attr.background_pixel = BlackPixel(display, client->screen->num); client->frame = XCreateWindow(display, client->screen->root, client->x, client->y, FULLWIDTH(client), FULLHEIGHT(client), 0, CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect | CWBackPixel, &attr); XSetWindowBorderWidth(display, client->window, 0); /* actual decor window creation (decor_decorate) posponed 'till later in add */ decor_decorate(client); client_shape(client); } /* grab input on our decoration for the client */ static __inline void client_dograbs(client_t *client) { XSetWindowAttributes attr; long mask; mask = SubstructureNotifyMask | SubstructureRedirectMask; switch (options.focus) { case FOCUS_CLICK: if (!client->flags.nofocus) XGrabButton(display, AnyButton, AnyModifier, client->frame, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); else buttongrabhack(client->frame, AnyButton, options.mouse_modifier); break; case FOCUS_POINTER: mask |= LeaveWindowMask; case FOCUS_SLOPPY: mask |= EnterWindowMask; buttongrabhack(client->frame, AnyButton, options.mouse_modifier); break; } XSelectInput(display, client->frame, mask); XShapeSelectInput(display, client->window, ShapeNotifyMask); /* prevent us from recieving some unneccessary events */ attr.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask; mask = CWDontPropagate; XChangeWindowAttributes(display, client->window, mask, &attr); } /* * add a client. * flags is the flags for the client, or NULL if we use defaults. * not all of the flags passed in will be honored: we decide here * if it's a transient, etc. if plugin is not NULL and it's an internal * it means this should be associated with that plugin as an internal * window. dgroup is the dgroup to use, otherwise we will try to figure * out for ourselves and use dgroups based on if it is a transient/internal, * and so forth. */ client_t *client_add(screen_t *screen, Window w, clientflags_t *flags, dgroup_t *dgroup) { XWindowAttributes win_attr; Window dumwin; client_t *client = NULL; long supplied; #ifdef I18N XTextProperty text_prop; char **cl; int n; #endif /* * we need to do a grab so that it's not possible for the window * we are about to manage to disappear halfway through the process * of us preparing to manage it. */ XGrabServer(display); /* if we can't get window attributes, give up */ if (!XGetWindowAttributes(display, w, &win_attr)) goto done; /* get mem and set flags */ client = calloc(1, sizeof(client_t)); if (!client) goto done; if (flags) memcpy(&client->flags, flags, sizeof(clientflags_t)); /* fill in client_t stuff */ client->window = w; client->screen = screen; client->x = win_attr.x; client->y = win_attr.y; client->last_width = client->width = win_attr.width; client->last_height = client->height = win_attr.height; /* default stacking layer to normal */ client->stacklayer = STACKLAYER_NORMAL; /* get PropertyChange information */ if (!client->flags.internal) XSelectInput(display, client->window, PropertyChangeMask); /* get hints and do stuff that they require */ client->wmhints = XGetWMHints(display, client->window); XGetWMNormalHints(display, client->window, &client->normalhints, &supplied); XGetClassHint(display, client->window, &client->classhint); #ifdef I18N if (XGetWMName(display, client->window, &text_prop) != 0) { if (text_prop.encoding == XA_STRING) { client->store_name = (char *)text_prop.value; } else { XmbTextPropertyToTextList(display, &text_prop, &cl, &n); if (cl) { client->store_name = strdup(cl[0]); XFreeStringList(cl); } else { client->store_name = strdup("NoName"); } } } else { client->store_name = strdup("NoName"); } #else XFetchName(display, client->window, &client->store_name); #endif if (XGetTransientForHint(display, client->window, &dumwin)) client->flags.transient = 1; /* do window manager hints, and decorate */ client_init_wmhints(client); plugin_init_hints(client); client_decorate(client, dgroup); /* * put this client_t into client_context for this * client's window and frame. */ XSaveContext(display, client->window, client_context, (XPointer) client); XSaveContext(display, client->frame, client_context, (XPointer) client); /* select input and then reparent the client to it's frame window */ client_dograbs(client); XReparentWindow(display, client->window, client->frame, client->dgroup->left_space, client->dgroup->top_space); client_gravitate(client); /* lower the client in the frame window so decoration can cover it */ XLowerWindow(display, client->window); /* link it up to the all-clients list */ LIST_INSERT_HEAD(&client_list, client, c_list); /* save non internals */ if (!client->flags.internal) XAddToSaveSet(display, client->window); done: /* ungrab the server */ XUngrabServer(display); return client; } /* remove a client */ void client_rm(client_t *client) { /* * eliminate decoration windows and set dgroup to dgroup_empty * before removing from the workspace/desktop because if this * window is focused, removing it from the desktop will attempt * to unfocus all of it's decoration windows (change their pixmaps * to the nonfocus ones) which is a waste of time. */ decor_undecor(client); client->dgroup = &dgroup_empty; /* free stuff allocated by Xlib for this client */ if (client->wmhints) XFree(client->wmhints); if (client->classhint.res_name) XFree(client->classhint.res_name); if (client->classhint.res_class) XFree(client->classhint.res_class); if (client->store_name) XFree(client->store_name); /* kill the frame window and remove entries in client_context */ XDeleteContext(display, client->frame, client_context); XDeleteContext(display, client->window, client_context); XDestroyWindow(display, client->frame); /* remove from workspace and desktop */ if (client->workspace) { desktop_rm_client(client); workspace_rm_client(client); } /* free managed client memory */ LIST_REMOVE(client, c_list); free(client); } /* called by map_request and by manage_existing_windows (screen.c) */ void client_map(client_t *client, int allowplace) { /* * no matter what state, we map it's * top window */ XMapWindow(display, client->window); /* * to prevent losing clients if you restart after * changing desktop dims */ if (client->x + FULLWIDTH(client) < 0 || client->y + FULLHEIGHT(client) < 0 || client->x >= (client->screen->desktop->width - client->screen->desktop->viewx) * client->screen->width || client->y >= (client->screen->desktop->height - client->screen->desktop->viewy) * client->screen->width) { client->y = client->x = 0; client_sizeframe(client); } /* put it into the state it wants to be in */ switch (client->state) { case IconicState: /* client starts as an icon */ client_setstate(client, IconicState); plugin_iconify_notify(client); break; case WithdrawnState: case NormalState: default: if (allowplace) { placement_place(client); client_sizeframe(client); } else { /* * if placement is not allowed, it means that we are * probably running on a window that existed before * the window manager was started; this means the * unmap_from_reparent flag will still be set. * If the flag isn't set, we still do a birth * animation. */ if (!client->flags.unmap_from_reparent) plugin_anim_birth(client); } /* * add to the workspace appropriate for the client's * position on the screen. */ workspace_add_bypos(client->screen->desktop, client); /* the window gets focus in map_notify */ XMapWindow(display, client->frame); stacking_raise(client); client_setstate(client, NormalState); /* * send configure to the client to make sure it * knows where it is, and notify plugins. */ action_send_config(client); plugin_window_birth(client); break; } } /* shape a client */ void client_shape(client_t *client) { XRectangle *rect = NULL; XRectangle r1; int n, order; rect = XShapeGetRectangles(display, client->window, ShapeBounding, &n, &order); if (n > 1) { r1.x = 0; r1.y = 0; r1.width = client->width; r1.height = client->height; XShapeCombineRectangles(display, client->frame, ShapeBounding, client->dgroup->left_space, client->dgroup->top_space, &r1, 1, ShapeSubtract, Unsorted); XShapeCombineShape(display, client->frame, ShapeBounding, client->dgroup->left_space, client->dgroup->top_space, client->window, ShapeBounding, ShapeUnion); client->flags.shaped = 1; } else if (client->flags.shaped) { r1.x = 0; r1.y = 0; r1.width = client->width; r1.height = client->height; XShapeCombineRectangles(display, client->frame, ShapeBounding, client->dgroup->left_space, client->dgroup->top_space, &r1, 1, ShapeUnion, Unsorted); client->flags.shaped = 0; } XFree(rect); } /* * adjust client geometry according to the gravity settings * specified in the client normalhints. */ void client_gravitate(client_t *client) { static point_t gravoffs[] = { { 0, 0}, /* ForgetGravity */ { -1, -1}, /* NorthWestGravity */ { 0, -1}, /* NorthGravity */ { 1, -1}, /* NorthEastGravity */ { -1, 0}, /* WestGravity */ { 0, 0}, /* CenterGravity */ { 1, 0}, /* EastGravity */ { -1, 1}, /* SouthWestGravity */ { 0, 1}, /* SouthGravity */ { 1, 1}, /* SouthEastGravity */ { 0, 0} /* StaticGravity */ }; point_t *offs; /* make sure we can adjust this for gravity */ if (!(client->normalhints.flags & PWinGravity)) return; if (client->normalhints.win_gravity < 0 || client->normalhints.win_gravity > StaticGravity) return; /* adjust the geometry to correspond to gravity settings */ offs = &gravoffs[client->normalhints.win_gravity]; if (offs->x > 0) client->x -= DWIDTH(client); else if (offs->x == 0) client->x -= client->dgroup->left_space; if (offs->y > 0) client->y -= DHEIGHT(client); else if (offs->y == 0) client->y -= client->dgroup->top_space; } /* resize the decoration and such for a client: reshape, etc. */ void client_sizeframe(client_t *client) { dgroup_t *dgroup; if (client->width != client->last_width || client->height != client->last_height) { dgroup = client->dgroup; XMoveResizeWindow(display, client->frame, client->x, client->y, FULLWIDTH(client), FULLHEIGHT(client)); XResizeWindow(display, client->window, client->width, client->height); decor_shapesize(client); if (client->flags.shaped) client_shape(client); client->last_width = client->width; client->last_height = client->height; } else XMoveWindow(display, client->frame, client->x, client->y); } /* set the state for a window (our var and the WM_STATE) */ void client_setstate(client_t *client, int state) { u_long data[2]; data[0] = state; data[1] = None; XChangeProperty(display, client->window, WM_STATE, WM_STATE, 32, PropModeReplace, (u_char *) data, 2); client->state = state; XSync(display, 0); }