/*-
* 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);
}
syntax highlighted by Code2HTML, v. 0.9.1