/*-
* Copyright (c) 2001 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"
/* spawn a child */
pid_t action_exec(int screen, char *cmd) {
char buff[256];
char number[10];
char *ptr;
pid_t pid;
if ((pid = fork()) == 0) {
snprintf(buff, sizeof(buff), "DISPLAY=%s", XDisplayName(NULL));
ptr = strchr(strchr(buff, ':'), '.');
if (ptr) *ptr = '\0';
snprintf(number, sizeof(number), "%i", screen);
strncat(buff, ".", sizeof(buff) - strlen(buff));
strncat(buff, number, sizeof(buff) - strlen(buff));
putenv(buff);
execl(_PATH_BSHELL, "sh", "-c", cmd, NULL);
exit(1);
}
return pid;
}
/*
* send a synthetic ConfigureNotify event to a client (as on a move). the
* ICCCM requires that these fake configurenotify events get sent in a
* reparenting windowmanager whenever the windowmanager moves the client's
* parent window (without also resizing the client window).
*/
void action_send_config(client_t *client) {
XEvent event;
event.type = ConfigureNotify;
event.xconfigure.display = display;
event.xconfigure.event = client->window;
event.xconfigure.window = client->window;
event.xconfigure.x = client->x + client->dgroup->left_space;
event.xconfigure.y = client->y + client->dgroup->top_space;
event.xconfigure.width = client->width;
event.xconfigure.height = client->height;
event.xconfigure.border_width = 0;
event.xconfigure.above = client->frame;
event.xconfigure.override_redirect = 0;
XSendEvent(display, client->window, 0, StructureNotifyMask, &event);
}
/* send a ICCCM Client Message */
void action_sendcmesg(Window w, Atom a, Time timestamp) {
XClientMessageEvent e;
e.type = ClientMessage;
e.window = w;
e.message_type = WM_PROTOCOLS;
e.format = 32;
e.data.l[0] = a;
e.data.l[1] = timestamp;
XSendEvent(display, w, 0, 0, (XEvent *) &e);
}
/* pointer motion handling for window movement */
static __inline void movemotion(XMotionEvent *e, long eventmask, client_t *client,
point_t *win, point_t *oldwin, point_t *ptr) {
win->y += e->y_root - ptr->y;
win->x += e->x_root - ptr->x;
ptr->y = e->y_root;
ptr->x = e->x_root;
/* either move the window or redraw the winbox */
if (!options.opaquemove) {
draw_winbox(client->screen, client, oldwin->x,
oldwin->y, client->width, client->height);
draw_winbox(client->screen, client, win->x, win->y,
client->width, client->height);
} else
XMoveWindow(display, client->frame, win->x, win->y);
/* update oldwin point */
oldwin->x = win->x;
oldwin->y = win->y;
}
/* button release handling for window movement */
static __inline void moverelease(client_t *client, point_t *win,
point_t *oldwin) {
/*
* release the mouse pointer; in nonopaque mode, erase
* the current winbox and let go of the server.
*/
XUngrabPointer(display, CurrentTime);
if (!options.opaquemove) {
draw_winbox(client->screen, client, oldwin->x,
oldwin->y, client->width, client->height);
stacking_raise(client);
XUngrabServer(display);
}
/*
* if the window position changed, move the window to
* it's final destination, send it a synthetic config
* and tell plugins that it moved.
*/
if (win->x != client->x || win->y != client->y) {
client->x = win->x;
client->y = win->y;
XMoveWindow(display, client->frame, win->x, win->y);
action_send_config(client);
plugin_geometry_change(client);
}
}
/* window movement X event loop */
static __inline void moveloop(client_t *client, long eventmask) {
XEvent event;
point_t win, oldwin, ptr;
Window dumwin;
int dumint;
/*
* get the mouse pointer position, window position, and
* set old window position values to the current ones.
*/
XQueryPointer(display, client->screen->root, &dumwin, &dumwin,
&ptr.x, &ptr.y, &dumint, &dumint, &dumint);
oldwin.x = win.x = client->x;
oldwin.y = win.y = client->y;
/* in nonopaque mode, draw the first winbox now */
if (!options.opaquemove)
draw_winbox(client->screen, client, win.x, win.y,
client->width, client->height);
/* handle movement loop events */
while (XMaskEvent(display, eventmask, &event), 1)
switch (event.type) {
case Expose:
event_handle(&event);
break;
case MotionNotify:
movemotion(&event.xmotion, eventmask, client,
&win, &oldwin, &ptr);
break;
case ButtonRelease:
moverelease(client, &win, &oldwin);
return;
}
}
/*
* interactively move a window; handle both opaque and nonopaque
* movement styles. the client window in question becomes bound
* to pointer movements until the pointer button is released,
* at which point the window is left at that final destination
* location and send a synthetic configure event.
*/
void action_move(client_t *client) {
long eventmask;
/*
* movement isn't allowed on nomove clients; ungrab
* the passive grab we hold on entry to this func and
* return
*/
if (client->flags.nomove) {
XUngrabPointer(display, CurrentTime);
return;
}
/* make grab for moving the window */
eventmask = PointerMotionMask | ButtonMotionMask
| ButtonReleaseMask;
if (XGrabPointer(display, client->screen->root, 0, eventmask,
GrabModeAsync, GrabModeAsync, client->screen->root,
cursor_move, CurrentTime) != GrabSuccess)
return;
/*
* prepare according to opaque or nonopaque style move;
* opaque style requires that we handle exposures.
*/
if (options.opaquemove) {
stacking_raise(client);
eventmask |= ExposureMask;
} else
XGrabServer(display);
/* call window movement loop */
moveloop(client, eventmask);
}
/*
* set width of client for resize, only move the bound
* edge.
*/
static __inline void setwidth(client_t *client, int width,
rect_t *win, int **dx, int **dy) {
if (*dx == &win->x1)
win->x1 = win->x2 - width - DWIDTH(client);
else if (*dx == &win->x2)
win->x2 = win->x1 + width + DWIDTH(client);
}
/*
* set height of client for resize, only move the
* bound edge.
*/
static __inline void setheight(client_t *client, int height,
rect_t *win, int **dx, int **dy) {
if (*dy == &win->y1)
win->y1 = win->y2 - height - DHEIGHT(client);
else if (*dy == &win->y2)
win->y2 = win->y1 + height + DHEIGHT(client);
}
/*
* check resizing against constraints specified by XNormalHints
* given to us by the client.
*/
static __inline void resizelimit(client_t *client, rect_t *win,
int **dx, int **dy) {
int basew, baseh, minw, minh;
/*
* get minimum and base height/width values for
* this client. according to the ICCCM, if the
* base size is specified and minsize isn't, minsize
* should be base size; if base size is not specified
* and minsize is, use minsize for basesize.
*/
if (client->normalhints.flags & PBaseSize) {
basew = client->normalhints.base_width;
baseh = client->normalhints.base_height;
if (client->normalhints.flags & PMinSize) {
minw = client->normalhints.min_width;
minh = client->normalhints.min_height;
} else {
minw = basew;
minh = baseh;
}
} else if (client->normalhints.flags & PMinSize) {
minw = basew = client->normalhints.min_width;
minh = baseh = client->normalhints.min_height;
} else
baseh = basew = minw = minh = 1;
/*
* keep resizing to increments as specified
* in the normal hints.
*/
if (client->normalhints.flags & PResizeInc) {
int wid, hei;
wid = win->x2 - win->x1 - DWIDTH(client) - basew;
hei = win->y2 - win->y1 - DHEIGHT(client) - baseh;
wid -= wid % client->normalhints.width_inc;
hei -= hei % client->normalhints.height_inc;
setwidth(client, wid + basew, win, dx, dy);
setheight(client, hei + baseh, win, dx, dy);
}
/*
* if a maxmimum size was specified, make sure the client
* dimensions do not exceed it.
*/
if (client->normalhints.flags & PMaxSize) {
if (win->x2 - win->x1 - DWIDTH(client) > client->normalhints.max_width)
setwidth(client, client->normalhints.max_width, win, dx, dy);
if (win->y2 - win->y1 - DHEIGHT(client) > client->normalhints.max_height)
setheight(client, client->normalhints.max_height, win, dx, dy);
}
/*
* handle window minimum size; even if no minimum size hint
* was specified we enforce a minimum size of 1x1.
*/
if (win->x2 - win->x1 - DWIDTH(client) < minw)
setwidth(client, minw, win, dx, dy);
if (win->y2 - win->y1 - DHEIGHT(client) < minh)
setheight(client, minh, win, dx, dy);
}
/* pointer motion handling for window resizing */
static __inline void resizemotion(XMotionEvent *e, client_t *client, rect_t *win,
rect_t *oldwin, point_t *ptr, int **dx, int **dy, point_t *reloff) {
ptr->x = e->x_root;
ptr->y = e->y_root;
/*
* rebind edges to the mouse in the x direction, if
* an edge was crossed.
*/
if (ptr->x > client->x + FULLWIDTH(client) && *dx != &win->x2) {
win->x1 = client->x;
*dx = &win->x2;
reloff->x = reloff->y = 0;
} else if (ptr->x < client->x && *dx != &win->x1) {
win->x2 = client->x + FULLWIDTH(client);
*dx = &win->x1;
reloff->x = reloff->y = 0;
}
/*
* rebind edges to the mouse if edges were crossed
* in the y direction.
*/
if (ptr->y > client->y + FULLHEIGHT(client) && *dy != &win->y2) {
win->y1 = client->y;
*dy = &win->y2;
reloff->x = reloff->y = 0;
} else if (ptr->y < client->y && *dy != &win->y1) {
win->y2 = client->y + FULLHEIGHT(client);
*dy = &win->y1;
reloff->x = reloff->y = 0;
}
/* move the bound edges */
if (*dx)
**dx = ptr->x + reloff->x;
if (*dy)
**dy = ptr->y + reloff->y;
/* constrain based on normalhints */
resizelimit(client, win, dx, dy);
/*
* erase the old winbox and draw a new one if the
* window rectangle changed.
*/
if (memcmp(win, oldwin, sizeof(rect_t))) {
draw_winbox(client->screen, client, oldwin->x1, oldwin->y1,
oldwin->x2 - oldwin->x1 - DWIDTH(client),
oldwin->y2 - oldwin->y1 - DHEIGHT(client));
draw_winbox(client->screen, client, win->x1, win->y1,
win->x2 - win->x1 - DWIDTH(client),
win->y2 - win->y1 - DHEIGHT(client));
memcpy(oldwin, win, sizeof(rect_t));
}
}
/* button release handling for window resizing */
static __inline void resizerelease(client_t *client, rect_t *win,
rect_t *oldwin) {
/*
* erase the last window box, release the mouse pointer
* and release the server.
*/
draw_winbox(client->screen, client, oldwin->x1, oldwin->y1,
oldwin->x2 - oldwin->x1 - DWIDTH(client),
oldwin->y2 - oldwin->y1 - DHEIGHT(client));
XUngrabPointer(display, CurrentTime);
XUngrabServer(display);
/*
* if the window geometry has changed, resize the client
* window and notify plugins.
*/
if (win->x1 != client->x || win->y1 != client->y
|| win->x2 != client->x + FULLWIDTH(client)
|| win->y2 != client->y + FULLHEIGHT(client)) {
client->x = win->x1;
client->y = win->y1;
client->width = win->x2 - win->x1 - DWIDTH(client);
client->height = win->y2 - win->y1 - DHEIGHT(client);
client_sizeframe(client);
plugin_geometry_change(client);
}
}
/* window resizing X event loop */
static __inline void resizeloop(client_t *client, long eventmask) {
XEvent event;
rect_t win, oldwin;
point_t ptr, reloff;
Window dumwin;
int dumint;
int *dx, *dy;
/*
* get the mouse pointer position, window rectangle, and
* set old window rectangle values to the current ones.
*/
XQueryPointer(display, client->screen->root, &dumwin, &dumwin,
&ptr.x, &ptr.y, &dumint, &dumint, &dumint);
oldwin.x1 = win.x1 = client->x;
oldwin.y1 = win.y1 = client->y;
oldwin.x2 = win.x2 = win.x1 + FULLWIDTH(client);
oldwin.y2 = win.y2 = win.y1 + FULLHEIGHT(client);
/*
* in relative_resize, we start with dx and dy bound to
* the corner that the mouse is nearest to. otherwise
* dx and dy start unbound
*/
if (options.relative_resize) {
if (ptr.x - client->x < FULLWIDTH(client) / 2) {
reloff.x = -(ptr.x - client->x);
dx = &win.x1;
} else {
reloff.x = FULLWIDTH(client) - (ptr.x - client->x);
dx = &win.x2;
}
if (ptr.y - client->y < FULLHEIGHT(client) / 2) {
reloff.y = -(ptr.y - client->y);
dy = &win.y1;
} else {
reloff.y = FULLHEIGHT(client) - (ptr.y - client->y);
dy = &win.y2;
}
} else {
reloff.x = reloff.y = 0;
dx = dy = NULL;
}
/* draw winbox of the first window position */
draw_winbox(client->screen, client, win.x1, win.y1,
win.x2 - win.x1 - DWIDTH(client),
win.y2 - win.y1 - DHEIGHT(client));
/* handle resize loop events */
while (XMaskEvent(display, eventmask, &event), 1)
switch (event.type) {
case MotionNotify:
resizemotion(&event.xmotion, client, &win,
&oldwin, &ptr, &dx, &dy, &reloff);
break;
case ButtonRelease:
resizerelease(client, &win, &oldwin);
return;
}
}
/*
* interactively resize a window. when the mouse crosses a
* particular edge, that edge becomes bound to mouse movements.
* moving the mouse across an opposite edite will unbind the
* currently bound edge in favor of the new one. the user
* may elect to use a relative resize option (options.relative_resize),
* in which case the mouse pointer starts bound to the corner that
* it is closest too, and maintains that binding till opposite edges
* get crossed.
*
* during resizing it is also neccesary to make sure that the
* dimensions of the window do not contradict limits specified
* through the XNormalHints structure in the client_t.
*/
void action_resize(client_t *client) {
long eventmask;
/*
* resizing isn't allowed for noresize clients; ungrab
* the grab we have from earlier passive grab and
* return
*/
if (client->flags.noresize) {
XUngrabPointer(display, CurrentTime);
return;
}
/* grab for resizing the window */
eventmask = ButtonReleaseMask | PointerMotionMask
| ButtonMotionMask;
if (XGrabPointer(display, client->screen->root, 0, eventmask,
GrabModeAsync, GrabModeAsync, client->screen->root,
cursor_move, CurrentTime) != GrabSuccess)
return;
/* prepare for resizing */
XGrabServer(display);
stacking_raise(client);
/* call the window resizing loop */
resizeloop(client, eventmask);
}
/* move a client window to the icon'd window list */
void action_iconify(client_t *client) {
if (client->flags.noiconify)
return;
/* let plugs know, and then unmap it to iconic */
XUnmapWindow(display, client->frame);
client_setstate(client, IconicState);
plugin_iconify_notify(client);
/* get it out of the workspace */
desktop_rm_client(client);
workspace_rm_client(client);
}
/* restore an iconified client window */
void action_restore(client_t *client) {
if (client->state != IconicState)
return;
/* set state to normal, and add it to the current workspace */
client_setstate(client, NormalState);
workspace_add_client(client->screen->desktop->current_space, client);
desktop_add_client(client);
plugin_restore_notify(client);
XMapWindow(display, client->frame);
stacking_raise(client);
focus_setfocused(client);
}
/* zoom (maximize) or unzoom a window */
void action_zoom(client_t *client) {
int tmpint;
/* zooming counts as both resizing and moving */
if (client->flags.noresize || client->flags.nomove)
return;
/*
* when zooming a client we save it's position in the
* save_ members that match with geometry/dgroup
* information for the client in it's unzoomed state.
* when unzooming we save the zoomed state into the
* save variables so it will be accessable to plugins
* in plugin_unzoom_notify.
*/
if (client->flags.zoomed) {
client->flags.zoomed = 0;
swap(tmpint, client->save_x, client->x);
swap(tmpint, client->save_y, client->y);
swap(tmpint, client->save_width, client->width);
swap(tmpint, client->save_height, client->height);
if (options.fullscreen_zoom) {
dgroup_t *dgrouptmp = client->dgroup;
dgroup_switch(client, client->save_dgroup);
client->save_dgroup = dgrouptmp;
}
} else {
client->flags.zoomed = 1;
client->save_x = client->x;
client->save_y = client->y;
client->save_width = client->width;
client->save_height = client->height;
if (options.fullscreen_zoom) {
client->save_dgroup = client->dgroup;
dgroup_switch(client, &dgroup_empty);
}
if (xinerama_zoom(client)) {
client->x = 0;
client->y = 0;
client->width = client->screen->width - DWIDTH(client);
client->height = client->screen->height - DHEIGHT(client);
}
stacking_raise(client);
focus_setfocused(client);
}
/* notify plugins */
if (client->flags.zoomed)
plugin_zoom_notify(client);
else
plugin_unzoom_notify(client);
/*
* It is neccesary to send a synth configure notify if we didn't
* resize the client, but only moved it, because it wont recieve
* a real one in that case.
*/
client_sizeframe(client);
if (client->width == client->save_width && client->height == client->save_height)
if (client->x != client->save_x || client->y != client->save_y)
action_send_config(client);
}
syntax highlighted by Code2HTML, v. 0.9.1