/*- * 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); }