diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/clickety.c | 244 | ||||
-rw-r--r-- | src/clickety.h | 12 | ||||
-rw-r--r-- | src/colors.def (renamed from colors.def) | 0 | ||||
-rw-r--r-- | src/composite.c | 331 | ||||
-rw-r--r-- | src/composite.h | 24 | ||||
-rw-r--r-- | src/context.c | 97 | ||||
-rw-r--r-- | src/context.h | 14 | ||||
-rw-r--r-- | src/desktop.c | 158 | ||||
-rw-r--r-- | src/desktop.h | 25 | ||||
-rw-r--r-- | src/key.c | 371 | ||||
-rw-r--r-- | src/key.h | 11 | ||||
-rw-r--r-- | src/launch.c | 45 | ||||
-rw-r--r-- | src/launch.h | 13 | ||||
-rw-r--r-- | src/launchers.def (renamed from launchers.def) | 0 | ||||
-rw-r--r-- | src/libvmon/LICENSE (renamed from libvmon/LICENSE) | 0 | ||||
-rw-r--r-- | src/libvmon/bitmap.h (renamed from libvmon/bitmap.h) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/_begin.def (renamed from libvmon/defs/_begin.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/_end.def (renamed from libvmon/defs/_end.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/proc_files.def (renamed from libvmon/defs/proc_files.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/proc_io.def (renamed from libvmon/defs/proc_io.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/proc_stat.def (renamed from libvmon/defs/proc_stat.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/proc_vm.def (renamed from libvmon/defs/proc_vm.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/proc_wants.def (renamed from libvmon/defs/proc_wants.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/sys_stat.def (renamed from libvmon/defs/sys_stat.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/sys_vm.def (renamed from libvmon/defs/sys_vm.def) | 0 | ||||
-rw-r--r-- | src/libvmon/defs/sys_wants.def (renamed from libvmon/defs/sys_wants.def) | 0 | ||||
-rw-r--r-- | src/libvmon/vmon.c (renamed from libvmon/vmon.c) | 0 | ||||
-rw-r--r-- | src/libvmon/vmon.h (renamed from libvmon/vmon.h) | 0 | ||||
-rw-r--r-- | src/list.h (renamed from list.h) | 0 | ||||
-rw-r--r-- | src/logo.c | 66 | ||||
-rw-r--r-- | src/logo.h | 8 | ||||
-rw-r--r-- | src/overlay.c | 960 | ||||
-rw-r--r-- | src/overlay.h | 36 | ||||
-rw-r--r-- | src/screen.c | 152 | ||||
-rw-r--r-- | src/screen.h | 19 | ||||
-rw-r--r-- | src/vwm.c | 291 | ||||
-rw-r--r-- | src/vwm.h | 79 | ||||
-rw-r--r-- | src/window.c | 468 | ||||
-rw-r--r-- | src/window.h | 81 | ||||
-rw-r--r-- | src/xevent.c | 269 | ||||
-rw-r--r-- | src/xevent.h | 18 | ||||
-rw-r--r-- | src/xwindow.c | 247 | ||||
-rw-r--r-- | src/xwindow.h | 52 | ||||
-rw-r--r-- | vwm.c | 3285 | ||||
-rw-r--r-- | vwm.h | 84 |
46 files changed, 4092 insertions, 3369 deletions
@@ -1,2 +1,3 @@ *.o vwm +*.swp diff --git a/src/clickety.c b/src/clickety.c new file mode 100644 index 0000000..a64e6d7 --- /dev/null +++ b/src/clickety.c @@ -0,0 +1,244 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + /* input event handling stuff */ + +#include <X11/Xlib.h> + +#include "clickety.h" +#include "desktop.h" +#include "vwm.h" +#include "window.h" +#include "xwindow.h" + +/* simple little state machine for managing mouse click/drag/release sequences so we don't need another event loop */ +typedef enum _vwm_adjust_mode_t { + VWM_ADJUST_RESIZE, + VWM_ADJUST_MOVE +} vwm_adjust_mode_t; + +typedef struct _vwm_clickety_t { + vwm_window_t *vwin; + vwm_adjust_mode_t mode; + XWindowAttributes orig, lastrect; + int impetus_x, impetus_y, impetus_x_root, impetus_y_root; +} vwm_clickety_t; + +static vwm_clickety_t clickety; + +/* helper function for resizing the window, how the motion is applied depends on where in the window the impetus event occurred */ +static void compute_resize(XEvent *terminus, XWindowAttributes *new) +{ + vwm_window_t *vwin; + int dw = (clickety.orig.width / 2); + int dh = (clickety.orig.height / 2); + int xdelta = (terminus->xbutton.x_root - clickety.impetus_x_root); + int ydelta = (terminus->xbutton.y_root - clickety.impetus_y_root); + int min_width = 0, min_height = 0; + int width_inc = 1, height_inc = 1; + + /* TODO: there's a problem here WRT border width, I should be considering it, I just haven't bothered to fix it since it doesn't seem to matter */ + if ((vwin = clickety.vwin) && vwin->hints) { + if ((vwin->hints_supplied & PMinSize)) { + min_width = vwin->hints->min_width; + min_height = vwin->hints->min_height; + VWM_TRACE("window size hints exist and minimum sizes are w=%i h=%i", min_width, min_height); + } + + if ((vwin->hints_supplied & PResizeInc)) { + width_inc = vwin->hints->width_inc; + height_inc = vwin->hints->height_inc; + VWM_TRACE("window size hints exist and resize increments are w=%i h=%i", width_inc, height_inc); + if (!width_inc) width_inc = 1; + if (!height_inc) height_inc = 1; + } + } + + xdelta = xdelta / width_inc * width_inc; + ydelta = ydelta / height_inc * height_inc; + + if (clickety.impetus_x < dw && clickety.impetus_y < dh) { + /* grabbed top left */ + new->x = clickety.orig.x + xdelta; + new->y = clickety.orig.y + ydelta; + new->width = clickety.orig.width - xdelta; + new->height = clickety.orig.height - ydelta; + } else if (clickety.impetus_x > dw && clickety.impetus_y < dh) { + /* grabbed top right */ + new->x = clickety.orig.x; + new->y = clickety.orig.y + ydelta; + new->width = clickety.orig.width + xdelta; + new->height = clickety.orig.height - ydelta; + } else if (clickety.impetus_x < dw && clickety.impetus_y > dh) { + /* grabbed bottom left */ + new->x = clickety.orig.x + xdelta; + new->y = clickety.orig.y; + new->width = clickety.orig.width - xdelta; + new->height = clickety.orig.height + ydelta; + } else if (clickety.impetus_x > dw && clickety.impetus_y > dh) { + /* grabbed bottom right */ + new->x = clickety.orig.x; + new->y = clickety.orig.y; + new->width = clickety.orig.width + xdelta; + new->height = clickety.orig.height + ydelta; + } + + /* constrain the width and height of the window according to the minimums */ + if (new->width < min_width) { + if (clickety.orig.x != new->x) new->x -= (min_width - new->width); + new->width = min_width; + } + + if (new->height < min_height) { + if (clickety.orig.y != new->y) new->y -= (min_height - new->height); + new->height = min_height; + } +} + + +void vwm_clickety_motion(vwm_t *vwm, Window win, XMotionEvent *motion) +{ + XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; + + if (!clickety.vwin) return; + + /* TODO: verify win matches clickety.vwin? */ + switch (clickety.mode) { + case VWM_ADJUST_MOVE: + changes.x = clickety.orig.x + (motion->x_root - clickety.impetus_x_root); + changes.y = clickety.orig.y + (motion->y_root - clickety.impetus_y_root); + XConfigureWindow(vwm->display, win, CWX | CWY | CWBorderWidth, &changes); + break; + + case VWM_ADJUST_RESIZE: { + XWindowAttributes resized; + + /* XXX: it just so happens the XMotionEvent structure is identical to XButtonEvent in the fields + * needed by compute_resize... */ + compute_resize((XEvent *)motion, &resized); + /* TODO: this is probably broken with compositing active, but it doesn't seem to be too messed up in practice */ + /* erase the last rectangle */ + XDrawRectangle(vwm->display, VWM_XROOT(vwm), vwm->gc, clickety.lastrect.x, clickety.lastrect.y, clickety.lastrect.width, clickety.lastrect.height); + /* draw a frame @ resized coordinates */ + XDrawRectangle(vwm->display, VWM_XROOT(vwm), vwm->gc, resized.x, resized.y, resized.width, resized.height); + /* remember the last rectangle */ + clickety.lastrect = resized; + break; + } + + default: + break; + } +} + + +void vwm_clickety_released(vwm_t *vwm, Window win, XButtonPressedEvent *terminus) +{ + XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; + + if (!clickety.vwin) return; + + switch (clickety.mode) { + case VWM_ADJUST_MOVE: + changes.x = clickety.orig.x + (terminus->x_root - clickety.impetus_x_root); + changes.y = clickety.orig.y + (terminus->y_root - clickety.impetus_y_root); + XConfigureWindow(vwm->display, win, CWX | CWY | CWBorderWidth, &changes); + break; + + case VWM_ADJUST_RESIZE: { + XWindowAttributes resized; + compute_resize((XEvent *)terminus, &resized); + /* move and resize the window @ resized */ + XDrawRectangle(vwm->display, VWM_XROOT(vwm), vwm->gc, clickety.lastrect.x, clickety.lastrect.y, clickety.lastrect.width, clickety.lastrect.height); + changes.x = resized.x; + changes.y = resized.y; + changes.width = resized.width; + changes.height = resized.height; + XConfigureWindow(vwm->display, win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); + XUngrabServer(vwm->display); + break; + } + + default: + break; + } + /* once you manipulate the window it's no longer fullscreened, simply hitting Mod1+Return once will restore fullscreened mode */ + clickety.vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; + + clickety.vwin = NULL; /* reset clickety */ + + XFlush(vwm->display); + XUngrabPointer(vwm->display, CurrentTime); +} + + +/* on pointer buttonpress we initiate a clickety sequence; setup clickety with the window and impetus coordinates.. */ +int vwm_clickety_pressed(vwm_t *vwm, Window win, XButtonPressedEvent *impetus) +{ + vwm_window_t *vwin; + + /* verify the window still exists */ + if (!XGetWindowAttributes(vwm->display, win, &clickety.orig)) goto _fail; + + if (!(vwin = vwm_win_lookup(vwm, win))) goto _fail; + + if (impetus->state & WM_GRAB_MODIFIER) { + + /* always set the input focus to the clicked window, note if we allow this to happen on the root window, it enters sloppy focus mode + * until a non-root window is clicked, which is an interesting hybrid but not how I prefer it. */ + if (vwin != vwm->focused_desktop->focused_window && vwin->xwindow->id != VWM_XROOT(vwm)) { + vwm_win_focus(vwm, vwin); + vwm_win_mru(vwm, vwin); + } + + switch (impetus->button) { + case Button1: + /* immediately raise the window if we're relocating, + * resizes are supported without raising (which also enables NULL resizes to focus without raising) */ + clickety.mode = VWM_ADJUST_MOVE; + XRaiseWindow(vwm->display, win); + break; + + case Button3: + /* grab the server on resize for the xor rubber-banding's sake */ + XGrabServer(vwm->display); + XSync(vwm->display, False); + + /* FIXME: none of the resize DrawRectangle() calls consider the window border. */ + XDrawRectangle(vwm->display, VWM_XROOT(vwm), vwm->gc, clickety.orig.x, clickety.orig.y, clickety.orig.width, clickety.orig.height); + clickety.lastrect = clickety.orig; + + clickety.mode = VWM_ADJUST_RESIZE; + break; + + default: + goto _fail; + } + clickety.vwin = vwin; + clickety.impetus_x_root = impetus->x_root; + clickety.impetus_y_root = impetus->y_root; + clickety.impetus_x = impetus->x; + clickety.impetus_y = impetus->y; + } + + return 1; + +_fail: + XUngrabPointer(vwm->display, CurrentTime); + + return 0; +} diff --git a/src/clickety.h b/src/clickety.h new file mode 100644 index 0000000..b2b51f1 --- /dev/null +++ b/src/clickety.h @@ -0,0 +1,12 @@ +#ifndef _CLICKETY_H +#define _CLICKETY_H + +#include <X11/Xlib.h> + +#include "vwm.h" + +void vwm_clickety_motion(vwm_t *vwm, Window win, XMotionEvent *motion); +void vwm_clickety_released(vwm_t *vwm, Window win, XButtonPressedEvent *terminus); +int vwm_clickety_pressed(vwm_t *vwm, Window win, XButtonPressedEvent *impetus); + +#endif diff --git a/colors.def b/src/colors.def index 97c0dc9..97c0dc9 100644 --- a/colors.def +++ b/src/colors.def diff --git a/src/composite.c b/src/composite.c new file mode 100644 index 0000000..afa4add --- /dev/null +++ b/src/composite.c @@ -0,0 +1,331 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* The compositing code is heavily influenced by Keith Packard's xcompmgr. + */ + +#include <X11/Xlib.h> +#include <X11/extensions/Xcomposite.h> +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/Xrender.h> + +#include "xwindow.h" +#include "vwm.h" + + /* compositing manager stuff */ +typedef enum _vwm_compositing_mode_t { + VWM_COMPOSITING_OFF = 0, /* non-composited, no redirected windows, most efficient */ + VWM_COMPOSITING_MONITORS = 1 /* composited process monitoring overlays, slower but really useful. */ +} vwm_compositing_mode_t; + +static vwm_compositing_mode_t compositing_mode = VWM_COMPOSITING_OFF; /* current compositing mode */ +static XserverRegion combined_damage = None; +static Picture root_picture = None, root_buffer = None; /* compositing gets double buffered */ +static XWindowAttributes root_attrs; +static XRenderPictureAttributes pa_inferiors = { .subwindow_mode = IncludeInferiors }; +static int repaint_needed; + +/* bind the window to a "namewindowpixmap" and create a picture from it (compositing) */ +static void bind_namewindow(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + xwin->pixmap = XCompositeNameWindowPixmap(vwm->display, xwin->id); + xwin->picture = XRenderCreatePicture(vwm->display, xwin->pixmap, + XRenderFindVisualFormat(vwm->display, xwin->attrs.visual), + CPSubwindowMode, &pa_inferiors); + XFreePixmap(vwm->display, xwin->pixmap); +} + +/* free the window's picture for accessing its redirected contents (compositing) */ +static void unbind_namewindow(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + XRenderFreePicture(vwm->display, xwin->picture); +} + +void vwm_composite_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + if (!compositing_mode) return; + + bind_namewindow(vwm, xwin); + xwin->damage = XDamageCreate(vwm->display, xwin->id, XDamageReportNonEmpty); +} + +void vwm_composite_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + if (!compositing_mode) return; + + unbind_namewindow(vwm, xwin); + XDamageDestroy(vwm->display, xwin->damage); +} + +/* add damage to the global combined damage queue where we accumulate damage between calls to paint_all() */ +void vwm_composite_damage_add(vwm_t *vwm, XserverRegion damage) +{ + if (combined_damage != None) { + XFixesUnionRegion(vwm->display, combined_damage, combined_damage, damage); + XFixesDestroyRegion(vwm->display, damage); + } else { + combined_damage = damage; + } +} + +/* helper to damage an entire window including the border */ +void vwm_composite_damage_win(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + XserverRegion region; + XRectangle rect = { xwin->attrs.x, + xwin->attrs.y, + xwin->attrs.width + xwin->attrs.border_width * 2, + xwin->attrs.height + xwin->attrs.border_width * 2 }; + + if (!compositing_mode) return; + + region = XFixesCreateRegion(vwm->display, &rect, 1); + vwm_composite_damage_add(vwm, region); +} + + +void vwm_composite_handle_configure(vwm_t *vwm, vwm_xwindow_t *xwin, XWindowAttributes *new_attrs) { + if (!compositing_mode) return; + + /* damage the old and new window areas */ + XserverRegion region; + XRectangle rects[2] = { { xwin->attrs.x, + xwin->attrs.y, + xwin->attrs.width + xwin->attrs.border_width * 2, + xwin->attrs.height + xwin->attrs.border_width * 2 }, + { new_attrs->x, + new_attrs->y, + new_attrs->width + new_attrs->border_width * 2, + new_attrs->height + new_attrs->border_width * 2 } }; + + region = XFixesCreateRegion(vwm->display, rects, 2); + vwm_composite_damage_add(vwm, region); + unbind_namewindow(vwm, xwin); + bind_namewindow(vwm, xwin); +} + + +void vwm_composite_handle_map(vwm_t *vwm, vwm_xwindow_t *xwin) { + if (!compositing_mode) return; + + vwm_composite_damage_win(vwm, xwin); + unbind_namewindow(vwm, xwin); + bind_namewindow(vwm, xwin); +} + +/* take what regions of the damaged window have been damaged, subtract them from the per-window damage object, and add them to the combined damage */ +void vwm_composite_damage_event(vwm_t *vwm, XDamageNotifyEvent *ev) +{ + XserverRegion region; + vwm_xwindow_t *xwin; + + xwin = vwm_xwin_lookup(vwm, ev->drawable); + if (!xwin) { + VWM_ERROR("damaged unknown drawable %x", (unsigned int)ev->drawable); + return; + } + + region = XFixesCreateRegion(vwm->display, NULL, 0); + XDamageSubtract(vwm->display, xwin->damage, None, region); + XFixesTranslateRegion(vwm->display, region, xwin->attrs.x + xwin->attrs.border_width, xwin->attrs.y + xwin->attrs.border_width); + vwm_composite_damage_add(vwm, region); +} + + +/* throw away our double buffered root pictures so they get recreated on the next paint_all() */ +/* used in response to screen configuration changes... */ +void vwm_composite_invalidate_root(vwm_t *vwm) +{ + if (!compositing_mode) return; + + if (root_picture) XRenderFreePicture(vwm->display, root_picture); + root_picture = None; + if (root_buffer) XRenderFreePicture(vwm->display, root_picture); + root_buffer = None; +} + +void vwm_composite_repaint_needed(vwm_t *vwm) +{ + if (!compositing_mode) return; + + repaint_needed = 1; +} + +/* consume combined_damage by iterating the xwindows list from the top down, drawing visible windows as encountered, subtracting their area from combined_damage */ +/* when compositing is active this is the sole function responsible for making things show up on the screen */ +void vwm_composite_paint_all(vwm_t *vwm) +{ + vwm_xwindow_t *xwin; + XRenderColor bgcolor = {0x0000, 0x00, 0x00, 0xffff}; + Region occluded = XCreateRegion(); + static XserverRegion undamage_region = None; + + /* if there's no damage to repaint, short-circuit, this happens when compositing for overlays is disabled. */ + if (!compositing_mode || (combined_damage == None && !repaint_needed)) return; + + repaint_needed = 0; + + if (!undamage_region) undamage_region = XFixesCreateRegion(vwm->display, NULL, 0); + + /* (re)create the root picture from the root window and allocate a double buffer for the off-screen composition of the damaged contents */ + if (root_picture == None) { + Pixmap root_pixmap; + + XGetWindowAttributes(vwm->display, VWM_XROOT(vwm), &root_attrs); + root_picture = XRenderCreatePicture(vwm->display, VWM_XROOT(vwm), + XRenderFindVisualFormat(vwm->display, VWM_XVISUAL(vwm)), + CPSubwindowMode, &pa_inferiors); + root_pixmap = XCreatePixmap(vwm->display, VWM_XROOT(vwm), root_attrs.width, root_attrs.height, VWM_XDEPTH(vwm)); + root_buffer = XRenderCreatePicture(vwm->display, root_pixmap, XRenderFindVisualFormat(vwm->display, VWM_XVISUAL(vwm)), 0, 0); + XFreePixmap(vwm->display, root_pixmap); + } + + /* compose overlays for all visible windows up front in a separate pass (kind of lame, but it's simpler since compose_overlay() adds to combined_damage) */ + list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { + XRectangle r; + + if (!vwm_xwin_is_mapped(vwm, xwin)) continue; /* if !mapped skip */ + + /* Everything mapped next goes through an occlusion check. + * Since the composite extension stops delivery of VisibilityNotify events for redirected windows, + * (it assumes redirected windows should be treated as part of a potentially transparent composite, and provides no api to alter this assumption) + * we can't simply select the VisibilityNotify events on all windows and cache their visibility state in vwm_xwindow_t then skip + * xwin->state==VisibilityFullyObscured windows here to avoid the cost of pointlessly composing overlays and rendering fully obscured windows :(. + * + * Instead we accumulate an occluded region (starting empty) of painted windows from the top-down on every paint_all(). + * Before we compose_overlay() a window, we check if the window's rectangle fits entirely within the occluded region. + * If it does, no compose_overlay() is performed. + * If it doesn't, compose_overlay() is called, and the window's rect is added to the occluded region. + * The occluded knowledge is also cached for the XRenderComposite() loop immediately following, where we skip the rendering of + * occluded windows as well. + * This does technically break SHAPE windows (xeyes, xmms), but only when monitoring is enabled which covers them with rectangular overlays anyways. + */ + r.x = xwin->attrs.x; + r.y = xwin->attrs.y; + r.width = xwin->attrs.width + xwin->attrs.border_width * 2; + r.height = xwin->attrs.height + xwin->attrs.border_width * 2; + if (XRectInRegion(occluded, r.x, r.y, r.width, r.height) != RectangleIn) { + /* the window isn't fully occluded, compose it and add it to occluded */ + if (xwin->monitor && !xwin->attrs.override_redirect) vwm_overlay_xwin_compose(vwm, xwin); + XUnionRectWithRegion(&r, occluded, occluded); + xwin->occluded = 0; + } else { + xwin->occluded = 1; + VWM_TRACE("window %#x occluded, skipping compose_overlay()", (int)xwin->id); + } + } + XDestroyRegion(occluded); + + /* start with the clip regions set to the damaged area accumulated since the previous paint_all() */ + XFixesSetPictureClipRegion(vwm->display, root_buffer, 0, 0, combined_damage); /* this is the double buffer where the in-flight screen contents are staged */ + XFixesSetPictureClipRegion(vwm->display, root_picture, 0, 0, combined_damage); /* this is the visible root window */ + + /* since translucent windows aren't supported in vwm, I can do this more efficiently */ + list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { + XRectangle r; + + if (!vwm_xwin_is_mapped(vwm, xwin) || xwin->occluded) continue; /* if !mapped or occluded skip */ + + /* these coordinates + dimensions incorporate the border (since XCompositeNameWindowPixmap is being used) */ + r.x = xwin->attrs.x; + r.y = xwin->attrs.y; + r.width = xwin->attrs.width + xwin->attrs.border_width * 2; + r.height = xwin->attrs.height + xwin->attrs.border_width * 2; + + /* render the redirected window contents into root_buffer, note root_buffer has the remaining combined_damage set as the clip region */ + XRenderComposite(vwm->display, PictOpSrc, xwin->picture, None, root_buffer, + 0, 0, 0, 0, /* src x, y, mask x, y */ + r.x, r.y, /* dst x, y */ + r.width, r.height); + + if (xwin->monitor && !xwin->attrs.override_redirect && xwin->overlay.width) { + /* draw the monitoring overlay atop the window, note we stay within the window borders here. */ + XRenderComposite(vwm->display, PictOpOver, xwin->overlay.picture, None, root_buffer, + 0, 0, 0, 0, /* src x,y, maxk x, y */ + xwin->attrs.x + xwin->attrs.border_width, /* dst x */ + xwin->attrs.y + xwin->attrs.border_width, /* dst y */ + xwin->attrs.width, vwm_overlay_xwin_composed_height(vwm, xwin)); /* w, h */ + } + + /* subtract the region of the window from the combined damage and update the root_buffer clip region to reflect the remaining damage */ + XFixesSetRegion(vwm->display, undamage_region, &r, 1); + XFixesSubtractRegion(vwm->display, combined_damage, combined_damage, undamage_region); + XFixesSetPictureClipRegion(vwm->display, root_buffer, 0, 0, combined_damage); + } + + /* at this point all of the visible windows have been subtracted from the clip region, so paint any root window damage (draw background) */ + XRenderFillRectangle(vwm->display, PictOpSrc, root_buffer, &bgcolor, 0, 0, root_attrs.width, root_attrs.height); + + /* discard the root_buffer clip region and copy root_buffer to root_picture, root_picture still has the combined damage as its clip region */ + XFixesSetPictureClipRegion(vwm->display, root_buffer, 0, 0, None); + XRenderComposite(vwm->display, PictOpSrc, root_buffer, None, root_picture, 0, 0, 0, 0, 0, 0, root_attrs.width, root_attrs.height); + + /* fin */ + XFixesDestroyRegion(vwm->display, combined_damage); + combined_damage = None; + XSync(vwm->display, False); +} + + +/* toggle compositing/monitoring overlays on/off */ +void vwm_composite_toggle(vwm_t *vwm) +{ + vwm_xwindow_t *xwin; + + XGrabServer(vwm->display); + XSync(vwm->display, False); + + switch (compositing_mode) { + case VWM_COMPOSITING_OFF: + VWM_TRACE("enabling compositing"); + compositing_mode = VWM_COMPOSITING_MONITORS; + XCompositeRedirectSubwindows(vwm->display, VWM_XROOT(vwm), CompositeRedirectManual); + list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { + bind_namewindow(vwm, xwin); + xwin->damage = XDamageCreate(vwm->display, xwin->id, XDamageReportNonEmpty); + } + /* damage everything */ + /* TODO: keep a list of rects reflecting all the current screens and create a region from that... */ + vwm_composite_damage_add(vwm, XFixesCreateRegionFromWindow(vwm->display, VWM_XROOT(vwm), WindowRegionBounding)); + break; + + case VWM_COMPOSITING_MONITORS: { + XEvent ev; + + VWM_TRACE("disabling compositing"); + compositing_mode = VWM_COMPOSITING_OFF; + list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { + unbind_namewindow(vwm, xwin); + XDamageDestroy(vwm->display, xwin->damage); + } + XCompositeUnredirectSubwindows(vwm->display, VWM_XROOT(vwm), CompositeRedirectManual); + vwm_composite_invalidate_root(vwm); + + /* if there's any damage queued up discard it so we don't enter paint_all() until compositing is reenabled again. */ + if (combined_damage) { + XFixesDestroyRegion(vwm->display, combined_damage); + combined_damage = None; + } + while (XCheckTypedEvent(vwm->display, vwm->damage_event + XDamageNotify, &ev) != False); + break; + } + } + + XUngrabServer(vwm->display); +} diff --git a/src/composite.h b/src/composite.h new file mode 100644 index 0000000..fb4f623 --- /dev/null +++ b/src/composite.h @@ -0,0 +1,24 @@ +#ifndef _COMPOSITE_H +#define _COMPOSITE_H + +#include <X11/Xlib.h> +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xfixes.h> + +typedef struct _vwm_t vwm_t; +typedef struct _vwm_xwindow_t vwm_xwindow_t; + +void vwm_composite_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_composite_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_composite_damage_add(vwm_t *vwm, XserverRegion damage); +void vwm_composite_damage_win(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_composite_handle_configure(vwm_t *vwm, vwm_xwindow_t *xwin, XWindowAttributes *new_attrs); +void vwm_composite_handle_map(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_composite_damage_event(vwm_t *vwm, XDamageNotifyEvent *ev); +void vwm_composite_damage_win(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_composite_paint_all(vwm_t *vwm); +void vwm_composite_invalidate_root(vwm_t *vwm); +void vwm_composite_repaint_needed(vwm_t *vwm); +void vwm_composite_toggle(vwm_t *vwm); + +#endif diff --git a/src/context.c b/src/context.c new file mode 100644 index 0000000..cede8a9 --- /dev/null +++ b/src/context.c @@ -0,0 +1,97 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + /* desktop/shelf context handling */ + +#include <X11/Xlib.h> + +#include "context.h" +#include "desktop.h" +#include "vwm.h" +#include "xwindow.h" +#include "window.h" + +/* switch to the desired context if it isn't already the focused one, inform caller if anything happened */ +int vwm_context_focus(vwm_t *vwm, vwm_context_t desired_context) +{ + vwm_context_t entry_context = vwm->focused_context; + + switch (vwm->focused_context) { + vwm_xwindow_t *xwin; + vwm_window_t *vwin; + + case VWM_CONTEXT_SHELF: + if (desired_context == VWM_CONTEXT_SHELF) break; + + /* desired == DESKTOP && focused == SHELF */ + + VWM_TRACE("unmapping shelf window \"%s\"", vwm->focused_shelf->xwindow->name); + vwm_win_unmap(vwm, vwm->focused_shelf); + XFlush(vwm->display); /* for a more responsive feel */ + + /* map the focused desktop, from the top of the stack down */ + list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { + if (!(vwin = xwin->managed)) continue; + if (vwin->desktop == vwm->focused_desktop && !vwin->shelved) { + VWM_TRACE("Mapping desktop window \"%s\"", xwin->name); + vwm_win_map(vwm, vwin); + } + } + + if (vwm->focused_desktop->focused_window) { + VWM_TRACE("Focusing \"%s\"", vwm->focused_desktop->focused_window->xwindow->name); + XSetInputFocus(vwm->display, vwm->focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); + } + + vwm->focused_context = VWM_CONTEXT_DESKTOP; + break; + + case VWM_CONTEXT_DESKTOP: + /* unmap everything, map the shelf */ + if (desired_context == VWM_CONTEXT_DESKTOP) break; + + /* desired == SHELF && focused == DESKTOP */ + + /* there should be a focused shelf if the shelf contains any windows, we NOOP the switch if the shelf is empty. */ + if (vwm->focused_shelf) { + /* unmap everything on the current desktop */ + list_for_each_entry(xwin, &vwm->xwindows, xwindows) { + if (!(vwin = xwin->managed)) continue; + if (vwin->desktop == vwm->focused_desktop) { + VWM_TRACE("Unmapping desktop window \"%s\"", xwin->name); + vwm_win_unmap(vwm, vwin); + } + } + + XFlush(vwm->display); /* for a more responsive feel */ + + VWM_TRACE("Mapping shelf window \"%s\"", vwm->focused_shelf->xwindow->name); + vwm_win_map(vwm, vwm->focused_shelf); + vwm_win_focus(vwm, vwm->focused_shelf); + + vwm->focused_context = VWM_CONTEXT_SHELF; + } + break; + + default: + VWM_BUG("unexpected focused context %x", vwm->focused_context); + break; + } + + /* return if the context has been changed, the caller may need to branch differently if nothing happened */ + return (vwm->focused_context != entry_context); +} diff --git a/src/context.h b/src/context.h new file mode 100644 index 0000000..1604bd3 --- /dev/null +++ b/src/context.h @@ -0,0 +1,14 @@ +#ifndef _CONTEXT_H +#define _CONTEXT_H + +typedef struct _vwm_t vwm_t; + +typedef enum _vwm_context_t { + VWM_CONTEXT_DESKTOP = 0, /* focus the desktop context */ + VWM_CONTEXT_SHELF, /* focus the shelf context */ + VWM_CONTEXT_OTHER /* focus the other context relative to the current one */ +} vwm_context_t; + +int vwm_context_focus(vwm_t *vwm, vwm_context_t desired_context); + +#endif diff --git a/src/desktop.c b/src/desktop.c new file mode 100644 index 0000000..d3fdd13 --- /dev/null +++ b/src/desktop.c @@ -0,0 +1,158 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + /* virtual desktops */ + +#include <X11/Xlib.h> +#include <stdlib.h> + +#include "list.h" +#include "context.h" +#include "desktop.h" +#include "vwm.h" +#include "xwindow.h" + +/* make the specified desktop the most recently used one */ +void vwm_desktop_mru(vwm_t *vwm, vwm_desktop_t *desktop) +{ + VWM_TRACE("MRU desktop: %p", desktop); + list_move(&desktop->desktops_mru, &vwm->desktops_mru); +} + + +/* focus a virtual desktop */ +/* this switches to the desktop context if necessary, maps and unmaps windows accordingly if necessary */ +int vwm_desktop_focus(vwm_t *vwm, vwm_desktop_t *desktop) +{ + XGrabServer(vwm->display); + XSync(vwm->display, False); + + /* if the context switched and the focused desktop is the desired desktop there's nothing else to do */ + if ((vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP) && vwm->focused_desktop != desktop) || vwm->focused_desktop != desktop) { + vwm_xwindow_t *xwin; + vwm_window_t *vwin; + + /* unmap the windows on the currently focused desktop, map those on the newly focused one */ + list_for_each_entry(xwin, &vwm->xwindows, xwindows) { + if (!(vwin = xwin->managed) || vwin->shelved) continue; + if (vwin->desktop == vwm->focused_desktop) vwm_win_unmap(vwm, vwin); + } + + XFlush(vwm->display); + + list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { + if (!(vwin = xwin->managed) || vwin->shelved) continue; + if (vwin->desktop == desktop) vwm_win_map(vwm, vwin); + } + + vwm->focused_desktop = desktop; + } + + /* directly focus the desktop's focused window if there is one, we don't use vwm_win_focus() intentionally XXX */ + if (vwm->focused_desktop->focused_window) { + VWM_TRACE("Focusing \"%s\"", vwm->focused_desktop->focused_window->xwindow->name); + XSetInputFocus(vwm->display, vwm->focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); + } + + XUngrabServer(vwm->display); + + return 1; +} + +/* return next MRU desktop relative to the supplied desktop */ +vwm_desktop_t * vwm_desktop_next_mru(vwm_t *vwm, vwm_desktop_t *desktop) { + list_head_t *next; + + /* this dance is necessary because the list head @ vwm->desktops_mru has no vwm_desktop_t container, + * and we're exploiting the circular nature of the doubly linked lists, so we need to take care to skip + * past the container-less head. + */ + if (desktop->desktops_mru.next == &vwm->desktops_mru) { + next = desktop->desktops_mru.next->next; + } else { + next = desktop->desktops_mru.next; + } + + return list_entry(next, vwm_desktop_t, desktops_mru); +} + +/* return next desktop spatially relative to the supplied desktop, no wrap-around */ +vwm_desktop_t * vwm_desktop_next(vwm_t *vwm, vwm_desktop_t *desktop) { + if (desktop->desktops.next != &vwm->desktops) { + desktop = list_entry(desktop->desktops.next, vwm_desktop_t, desktops); + } + + return desktop; +} + + +/* return previous desktop spatially relative to the supplied desktop, no wrap-around */ +vwm_desktop_t * vwm_desktop_prev(vwm_t *vwm, vwm_desktop_t *desktop) { + if (desktop->desktops.prev != &vwm->desktops) { + desktop = list_entry(desktop->desktops.prev, vwm_desktop_t, desktops); + } + + return desktop; +} + +/* create a virtual desktop */ +vwm_desktop_t * vwm_desktop_create(vwm_t *vwm, char *name) +{ + vwm_desktop_t *desktop; + + desktop = malloc(sizeof(vwm_desktop_t)); + if (desktop == NULL) { + VWM_PERROR("Failed to allocate desktop"); + goto _fail; + } + + desktop->name = name == NULL ? name : strdup(name); + desktop->focused_window = NULL; + + list_add_tail(&desktop->desktops, &vwm->desktops); + list_add_tail(&desktop->desktops_mru, &vwm->desktops_mru); + + return desktop; + +_fail: + return NULL; +} + + +/* destroy a virtual desktop */ +void vwm_desktop_destroy(vwm_t *vwm, vwm_desktop_t *desktop) +{ + /* silently refuse to destroy a desktop having windows (for now) */ + /* there's _always_ a focused window on a desktop having mapped windows */ + /* also silently refuse to destroy the last desktop (for now) */ + if (desktop->focused_window || (desktop->desktops.next == desktop->desktops.prev)) return; + + /* focus the MRU desktop that isn't this one if we're the focused desktop */ + if (desktop == vwm->focused_desktop) { + vwm_desktop_t *next_desktop; + + list_for_each_entry(next_desktop, &vwm->desktops_mru, desktops_mru) { + if (next_desktop != desktop) { + vwm_desktop_focus(vwm, next_desktop); + break; + } + } + } + + list_del(&desktop->desktops); + list_del(&desktop->desktops_mru); +} diff --git a/src/desktop.h b/src/desktop.h new file mode 100644 index 0000000..cc6df38 --- /dev/null +++ b/src/desktop.h @@ -0,0 +1,25 @@ +#ifndef _DESKTOP_H +#define _DESKTOP_H + +#include "list.h" +#include "window.h" + +typedef struct _vwm_t vwm_t; +typedef struct _vwm_window_t vwm_window_t; + +typedef struct _vwm_desktop_t { + list_head_t desktops; /* global list of (virtual) desktops */ + list_head_t desktops_mru; /* global list of (virtual) desktops in MRU order */ + char *name; /* name of the desktop (TODO) */ + vwm_window_t *focused_window; /* the focused window on this virtual desktop */ +} vwm_desktop_t; + +void vwm_desktop_mru(vwm_t *vwm, vwm_desktop_t *desktop); +int vwm_desktop_focus(vwm_t *vwm, vwm_desktop_t *desktop); +vwm_desktop_t * vwm_desktop_create(vwm_t *vwm, char *name); +void vwm_desktop_destroy(vwm_t *vwm, vwm_desktop_t *desktop); +vwm_desktop_t * vwm_desktop_next_mru(vwm_t *vwm, vwm_desktop_t *desktop); +vwm_desktop_t * vwm_desktop_next(vwm_t *vwm, vwm_desktop_t *desktop); +vwm_desktop_t * vwm_desktop_prev(vwm_t *vwm, vwm_desktop_t *desktop); + +#endif diff --git a/src/key.c b/src/key.c new file mode 100644 index 0000000..7a15ed0 --- /dev/null +++ b/src/key.c @@ -0,0 +1,371 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <stdlib.h> + +#include "composite.h" +#include "desktop.h" +#include "launch.h" +#include "list.h" +#include "vwm.h" +#include "window.h" +#include "xwindow.h" + +static int key_is_grabbed; /* flag for tracking keyboard grab state */ + +/* Poll the keyboard state to see if _any_ keys are pressed */ +static int keys_pressed(vwm_t *vwm) { + int i; + char state[32]; + + XQueryKeymap(vwm->display, state); + + for (i = 0; i < sizeof(state); i++) { + if (state[i]) return 1; + } + + return 0; +} + +/* Called in response to KeyRelease events, for now only interesting for detecting when Mod1 is released termintaing + * window cycling for application of MRU on the focused window */ +void vwm_key_released(vwm_t *vwm, Window win, XKeyReleasedEvent *keyrelease) +{ + vwm_window_t *vwin; + KeySym sym; + + switch ((sym = XLookupKeysym(keyrelease, 0))) { + case XK_Alt_R: + case XK_Alt_L: /* TODO: actually use the modifier mapping, for me XK_Alt_[LR] is Mod1. XGetModifierMapping()... */ + VWM_TRACE("XK_Alt_[LR] released"); + + /* aborted? try restore focused_origin */ + if (key_is_grabbed > 1 && vwm->focused_origin) { + VWM_TRACE("restoring %p on %p", vwm->focused_origin, vwm->focused_origin->desktop); + vwm_desktop_focus(vwm, vwm->focused_origin->desktop); + vwm_win_focus(vwm, vwm->focused_origin); + } + + /* make the focused window the most recently used */ + if ((vwin = vwm_win_focused(vwm))) vwm_win_mru(vwm, vwin); + + /* make the focused desktop the most recently used */ + if (vwm->focused_context == VWM_CONTEXT_DESKTOP && vwm->focused_desktop) vwm_desktop_mru(vwm, vwm->focused_desktop); + + break; + + default: + VWM_TRACE("Unhandled keycode: %x", (unsigned int)sym); + break; + } + + if (key_is_grabbed && !keys_pressed(vwm)) { + XUngrabKeyboard(vwm->display, CurrentTime); + XFlush(vwm->display); + key_is_grabbed = 0; + vwm->fence_mask = 0; /* reset the fence mask on release for VWM_FENCE_MASKED_VIOLATE */ + } +} + + +/* Called in response to KeyPress events, I currenly only grab Mod1 keypress events */ +void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress) +{ + vwm_window_t *vwin; + KeySym sym; + static KeySym last_sym; + static typeof(keypress->state) last_state; + static int repeat_cnt = 0; + int do_grab = 0; + char *quit_console_args[] = {"/bin/sh", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X quit", NULL}; + + sym = XLookupKeysym(keypress, 0); + + /* detect repeaters, note repeaters do not span interrupted Mod1 sequences! */ + if (key_is_grabbed && sym == last_sym && keypress->state == last_state) { + repeat_cnt++; + } else { + repeat_cnt = 0; + } + + vwin = vwm_win_focused(vwm); + + switch (sym) { + +#define launcher(_sym, _label, _argv)\ + case _sym: \ + { \ + char *args[] = {"/bin/sh", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X screen /bin/sh -i -x -c \"" _argv " || sleep 86400\"", NULL};\ + vwm_launch(vwm, args, VWM_LAUNCH_MODE_BG);\ + break; \ + } +#include "launchers.def" +#undef launcher + case XK_Alt_L: /* transaction abort */ + case XK_Alt_R: + if (key_is_grabbed) key_is_grabbed++; + VWM_TRACE("aborting with origin %p", vwm->focused_origin); + break; + + case XK_grave: /* toggle shelf visibility */ + vwm_context_focus(vwm, VWM_CONTEXT_OTHER); + break; + + case XK_Tab: /* cycle focused window */ + do_grab = 1; /* update MRU window on commit (Mod1 release) */ + + /* focus the next window, note this doesn't affect MRU yet, that happens on Mod1 release */ + if (vwin) { + if (keypress->state & ShiftMask) { + vwm_win_focus_next(vwm, vwin, VWM_FENCE_MASKED_VIOLATE); + } else { + vwm_win_focus_next(vwm, vwin, VWM_FENCE_RESPECT); + } + } + break; + + case XK_space: { /* cycle focused desktop utilizing MRU */ + vwm_desktop_t *next_desktop = vwm_desktop_next_mru(vwm, vwm->focused_desktop); + + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + + if (keypress->state & ShiftMask) { + /* migrate the focused window with the desktop focus to the most recently used desktop */ + if (vwin) vwm_win_migrate(vwm, vwin, next_desktop); + } else { + vwm_desktop_focus(vwm, next_desktop); + } + break; + } + + case XK_d: /* destroy focused */ + if (vwin) { + if (keypress->state & ShiftMask) { /* brutally destroy the focused window */ + XKillClient(vwm->display, vwin->xwindow->id); + } else { /* kindly destroy the focused window */ + vwm_xwin_message(vwm, vwin->xwindow, vwm->wm_protocols_atom, vwm->wm_delete_atom); + } + } else if (vwm->focused_context == VWM_CONTEXT_DESKTOP) { + /* destroy the focused desktop when destroy occurs without any windows */ + vwm_desktop_destroy(vwm, vwm->focused_desktop); + } + break; + + case XK_Escape: /* leave VWM rudely, after triple press */ + do_grab = 1; + + if (repeat_cnt == 2) { + vwm_launch(vwm, quit_console_args, VWM_LAUNCH_MODE_FG); + exit(42); + } + break; + + case XK_v: /* instantiate (and focus) a new (potentially empty, unless migrating) virtual desktop */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + + if (keypress->state & ShiftMask) { + if (vwin) { + /* migrate the focused window to a newly created virtual desktop, focusing the new desktop simultaneously */ + vwm_win_migrate(vwm, vwin, vwm_desktop_create(vwm, NULL)); + } + } else { + vwm_desktop_focus(vwm, vwm_desktop_create(vwm, NULL)); + vwm_desktop_mru(vwm, vwm->focused_desktop); + } + break; + + case XK_h: /* previous virtual desktop, if we're in the shelf context this will simply switch to desktop context */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + + if (keypress->state & ShiftMask) { + if (vwin) { + /* migrate the focused window with the desktop focus to the previous desktop */ + vwm_win_migrate(vwm, vwin, vwm_desktop_prev(vwm, vwin->desktop)); + } + } else { + if (vwm->focused_context == VWM_CONTEXT_SHELF) { + /* focus the focused desktop instead of the shelf */ + vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP); + } else { + /* focus the previous desktop */ + vwm_desktop_focus(vwm, vwm_desktop_prev(vwm, vwm->focused_desktop)); + } + } + break; + + case XK_l: /* next virtual desktop, if we're in the shelf context this will simply switch to desktop context */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + + if (keypress->state & ShiftMask) { + if (vwin) { + /* migrate the focused window with the desktop focus to the next desktop */ + vwm_win_migrate(vwm, vwin, vwm_desktop_next(vwm, vwin->desktop)); + } + } else { + if (vwm->focused_context == VWM_CONTEXT_SHELF) { + /* focus the focused desktop instead of the shelf */ + vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP); + } else { + /* focus the next desktop */ + vwm_desktop_focus(vwm, vwm_desktop_next(vwm, vwm->focused_desktop)); + } + } + break; + + case XK_k: /* raise or shelve the focused window */ + if (vwin) { + if (keypress->state & ShiftMask) { /* shelf the window and focus the shelf */ + if (vwm->focused_context != VWM_CONTEXT_SHELF) { + /* shelve the focused window while focusing the shelf */ + vwm_win_shelve(vwm, vwin); + vwm_context_focus(vwm, VWM_CONTEXT_SHELF); + } + } else { + do_grab = 1; + + XRaiseWindow(vwm->display, vwin->xwindow->id); + + if (repeat_cnt == 1) { + /* double: reraise & fullscreen */ + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); + } else if (repeat_cnt == 2) { + /* triple: reraise & fullscreen w/borders obscured by screen perimiter */ + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_ALL); + } else if (vwm->xinerama_screens_cnt > 1) { + if (repeat_cnt == 3) { + /* triple: reraise & fullscreen across all screens */ + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_FULL); + } else if (repeat_cnt == 4) { + /* quadruple: reraise & fullscreen w/borders obscured by screen perimiter */ + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_ALL); + } + } + XFlush(vwm->display); + } + } + break; + + case XK_j: /* lower or unshelve the focused window */ + if (vwin) { + if (keypress->state & ShiftMask) { /* unshelf the window to the focused desktop, and focus the desktop */ + if (vwm->focused_context == VWM_CONTEXT_SHELF) { + /* unshelve the focused window, focus the desktop it went to */ + vwm_win_migrate(vwm, vwin, vwm->focused_desktop); + } + } else { + if (vwin->autoconfigured == VWM_WIN_AUTOCONF_ALL) { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); + } else { + XLowerWindow(vwm->display, vwin->xwindow->id); + } + XFlush(vwm->display); + } + } + break; + + case XK_Return: /* (full-screen / restore) focused window */ + if (vwin) { + if (vwin->autoconfigured) { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_NONE); + } else { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); + } + } + break; + + case XK_s: /* shelve focused window */ + if (vwin && !vwin->shelved) vwm_win_shelve(vwm, vwin); + break; + + case XK_bracketleft: /* reconfigure the focused window to occupy the left or top half of the screen or left quarters on repeat */ + if (vwin) { + do_grab = 1; + + if (keypress->state & ShiftMask) { + if (!repeat_cnt) { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_TOP); + } else { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_LEFT); + } + } else { + if (!repeat_cnt) { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_LEFT); + } else { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_LEFT); + } + } + } + break; + + case XK_bracketright: /* reconfigure the focused window to occupy the right or bottom half of the screen or right quarters on repeat */ + if (vwin) { + do_grab = 1; + + if (keypress->state & ShiftMask) { + if (!repeat_cnt) { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_BOTTOM); + } else { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_RIGHT); + } + } else { + if (!repeat_cnt) { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_RIGHT); + } else { + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_RIGHT); + } + } + } + break; + + case XK_semicolon: /* toggle composited overlays */ + vwm_composite_toggle(vwm); + break; + + case XK_apostrophe: /* reset snowflakes of the focused window, suppressed when not compositing */ + if (vwin) { + vwm_overlay_xwin_reset_snowflakes(vwm, vwin->xwindow); + } + break; + + case XK_Right: /* increase sampling frequency */ + vwm_overlay_rate_increase(vwm); + break; + + case XK_Left: /* decrease sampling frequency */ + vwm_overlay_rate_decrease(vwm); + break; + + default: + VWM_TRACE("Unhandled keycode: %x", (unsigned int)sym); + break; + } + + /* if what we're doing requests a grab, if not already grabbed, grab keyboard */ + if (!key_is_grabbed && do_grab) { + VWM_TRACE("saving focused_origin of %p", vwin); + vwm->focused_origin = vwin; /* for returning to on abort */ + XGrabKeyboard(vwm->display, VWM_XROOT(vwm), False, GrabModeAsync, GrabModeAsync, CurrentTime); + key_is_grabbed = 1; + } + + /* remember the symbol for repeater detection */ + last_sym = sym; + last_state = keypress->state; +} diff --git a/src/key.h b/src/key.h new file mode 100644 index 0000000..d0bbb15 --- /dev/null +++ b/src/key.h @@ -0,0 +1,11 @@ +#ifndef _KEY_H +#define _KEY_H + +#include <X11/Xlib.h> + +typedef struct _vwm_t vwm_t; + +void vwm_key_released(vwm_t *vwm, Window win, XKeyReleasedEvent *keyrelease); +void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress); + +#endif diff --git a/src/launch.c b/src/launch.c new file mode 100644 index 0000000..123e850 --- /dev/null +++ b/src/launch.c @@ -0,0 +1,45 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + /* launching of external processes / X clients */ + +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "launch.h" +#include "vwm.h" + +#define LAUNCHED_RELATIVE_PRIORITY 10 /* the wm priority plus this is used as the priority of launched processes */ + +/* launch a child command specified in argv, mode decides if we wait for the child to exit before returning. */ +void vwm_launch(vwm_t *vwm, char **argv, vwm_launch_mode_t mode) +{ + /* XXX: in BG mode I double fork and let init inherit the orphan so I don't have to collect the return status */ + if (mode == VWM_LAUNCH_MODE_FG || !fork()) { + if (!fork()) { + /* child */ + setpriority(PRIO_PROCESS, getpid(), vwm->priority + LAUNCHED_RELATIVE_PRIORITY); + execvp(argv[0], argv); + } + if (mode == VWM_LAUNCH_MODE_BG) exit(0); + } + wait(NULL); /* TODO: could wait for the specific pid, particularly in FG mode ... */ +} diff --git a/src/launch.h b/src/launch.h new file mode 100644 index 0000000..0e279b0 --- /dev/null +++ b/src/launch.h @@ -0,0 +1,13 @@ +#ifndef _LAUNCH_H +#define _LAUNCH_H + +typedef struct _vwm_t vwm_t; + +typedef enum _vwm_launch_mode_t { + VWM_LAUNCH_MODE_FG, + VWM_LAUNCH_MODE_BG, +} vwm_launch_mode_t; + +void vwm_launch(vwm_t *vwm, char **argv, vwm_launch_mode_t mode); + +#endif diff --git a/launchers.def b/src/launchers.def index 2c63f96..2c63f96 100644 --- a/launchers.def +++ b/src/launchers.def diff --git a/libvmon/LICENSE b/src/libvmon/LICENSE index 94a9ed0..94a9ed0 100644 --- a/libvmon/LICENSE +++ b/src/libvmon/LICENSE diff --git a/libvmon/bitmap.h b/src/libvmon/bitmap.h index eacb97d..eacb97d 100644 --- a/libvmon/bitmap.h +++ b/src/libvmon/bitmap.h diff --git a/libvmon/defs/_begin.def b/src/libvmon/defs/_begin.def index 047fd4d..047fd4d 100644 --- a/libvmon/defs/_begin.def +++ b/src/libvmon/defs/_begin.def diff --git a/libvmon/defs/_end.def b/src/libvmon/defs/_end.def index 9243349..9243349 100644 --- a/libvmon/defs/_end.def +++ b/src/libvmon/defs/_end.def diff --git a/libvmon/defs/proc_files.def b/src/libvmon/defs/proc_files.def index 6e3db98..6e3db98 100644 --- a/libvmon/defs/proc_files.def +++ b/src/libvmon/defs/proc_files.def diff --git a/libvmon/defs/proc_io.def b/src/libvmon/defs/proc_io.def index 0e1776d..0e1776d 100644 --- a/libvmon/defs/proc_io.def +++ b/src/libvmon/defs/proc_io.def diff --git a/libvmon/defs/proc_stat.def b/src/libvmon/defs/proc_stat.def index db704e9..db704e9 100644 --- a/libvmon/defs/proc_stat.def +++ b/src/libvmon/defs/proc_stat.def diff --git a/libvmon/defs/proc_vm.def b/src/libvmon/defs/proc_vm.def index 9028272..9028272 100644 --- a/libvmon/defs/proc_vm.def +++ b/src/libvmon/defs/proc_vm.def diff --git a/libvmon/defs/proc_wants.def b/src/libvmon/defs/proc_wants.def index 7f02602..7f02602 100644 --- a/libvmon/defs/proc_wants.def +++ b/src/libvmon/defs/proc_wants.def diff --git a/libvmon/defs/sys_stat.def b/src/libvmon/defs/sys_stat.def index ef8b9a7..ef8b9a7 100644 --- a/libvmon/defs/sys_stat.def +++ b/src/libvmon/defs/sys_stat.def diff --git a/libvmon/defs/sys_vm.def b/src/libvmon/defs/sys_vm.def index 33b4d3f..33b4d3f 100644 --- a/libvmon/defs/sys_vm.def +++ b/src/libvmon/defs/sys_vm.def diff --git a/libvmon/defs/sys_wants.def b/src/libvmon/defs/sys_wants.def index 750d7bb..750d7bb 100644 --- a/libvmon/defs/sys_wants.def +++ b/src/libvmon/defs/sys_wants.def diff --git a/libvmon/vmon.c b/src/libvmon/vmon.c index 7d2b2a0..7d2b2a0 100644 --- a/libvmon/vmon.c +++ b/src/libvmon/vmon.c diff --git a/libvmon/vmon.h b/src/libvmon/vmon.h index 06a062b..06a062b 100644 --- a/libvmon/vmon.h +++ b/src/libvmon/vmon.h diff --git a/src/logo.c b/src/logo.c new file mode 100644 index 0000000..8fe7291 --- /dev/null +++ b/src/logo.c @@ -0,0 +1,66 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <unistd.h> +#include <X11/Xlib.h> + +#include "screen.h" +#include "vwm.h" + + /* startup logo */ + +/* animated \/\/\ logo done with simple XOR'd lines, a display of the WM being started and ready */ +#define VWM_LOGO_POINTS 6 +void vwm_draw_logo(vwm_t *vwm) +{ + int i; + unsigned int width, height, yoff, xoff; + XPoint points[VWM_LOGO_POINTS]; + const vwm_screen_t *scr = vwm_screen_find(vwm, VWM_SCREEN_REL_POINTER); + + XGrabServer(vwm->display); + + /* use the dimensions of the pointer-containing screen */ + width = scr->width; + height = scr->height; + xoff = scr->x_org; + yoff = scr->y_org + ((float)height * .333); + height /= 3; + + /* the logo gets shrunken vertically until it's essentially a flat line */ + while (height -= 2) { + /* scale and center the points to the screen size */ + for (i = 0; i < VWM_LOGO_POINTS; i++) { + points[i].x = xoff + (i * .2 * (float)width); + points[i].y = (i % 2 * (float)height) + yoff; + } + + XDrawLines(vwm->display, VWM_XROOT(vwm), vwm->gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); + XFlush(vwm->display); + usleep(3333); + XDrawLines(vwm->display, VWM_XROOT(vwm), vwm->gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); + XFlush(vwm->display); + + /* the width is shrunken as well, but only by as much as it is tall */ + yoff++; + width -= 4; + xoff += 2; + } + + XUngrabServer(vwm->display); +} diff --git a/src/logo.h b/src/logo.h new file mode 100644 index 0000000..51995c9 --- /dev/null +++ b/src/logo.h @@ -0,0 +1,8 @@ +#ifndef _LOGO_H +#define _LOGO_H + +typedef struct _vwm_t vwm_t; + +void vwm_draw_logo(vwm_t *vwm); + +#endif diff --git a/src/overlay.c b/src/overlay.c new file mode 100644 index 0000000..aeffd8e --- /dev/null +++ b/src/overlay.c @@ -0,0 +1,960 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* libvmon integration, warning: this gets a little crazy especially in the rendering. */ + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/Xrender.h> +#include <stdlib.h> +#include <sys/time.h> + +#include "composite.h" +#include "libvmon/vmon.h" +#include "list.h" +#include "overlay.h" +#include "vwm.h" +#include "xwindow.h" + +/* TODO: move to overlay.h */ +#define OVERLAY_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */ +#define OVERLAY_MASK_FORMAT PictStandardA8 +#define OVERLAY_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1" +#define OVERLAY_ROW_HEIGHT 15 /* this should always be larger than the font height */ +#define OVERLAY_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */ +#define OVERLAY_GRAPH_MIN_HEIGHT (4 * OVERLAY_ROW_HEIGHT) +#define OVERLAY_ISTHREAD_ARGV "~" /* use this string to mark threads in the argv field */ +#define OVERLAY_NOCOMM_ARGV "#missed it!" /* use this string to substitute the command when missing in argv field */ + + +/* libvmon */ +static struct timeval maybe_sample, last_sample, this_sample = {0,0}; +static typeof(((vmon_sys_stat_t *)0)->user) last_user_cpu; +static typeof(((vmon_sys_stat_t *)0)->system) last_system_cpu; +static unsigned long long last_total, this_total, total_delta; +static unsigned long long last_idle, last_iowait, idle_delta, iowait_delta; +static vmon_t vmon; + +static float sampling_intervals[] = { + 1, /* ~1Hz */ + .1, /* ~10Hz */ + .05, /* ~20Hz */ + .025, /* ~40Hz */ + .01666}; /* ~60Hz */ +static int prev_sampling_interval = 1, sampling_interval = 1; + +/* space we need for every process being monitored */ +typedef struct _vwm_perproc_ctxt_t { + typeof(vmon.generation) generation; + typeof(((vmon_proc_stat_t *)0)->utime) last_utime; + typeof(((vmon_proc_stat_t *)0)->stime) last_stime; + typeof(((vmon_proc_stat_t *)0)->utime) utime_delta; + typeof(((vmon_proc_stat_t *)0)->stime) stime_delta; +} vwm_perproc_ctxt_t; + +/* Compositing / Overlays */ +static XFontStruct *overlay_font; +static GC text_gc; +static XRenderPictureAttributes pa_repeat = { .repeat = 1 }; +static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 }; +static Picture overlay_shadow_fill, /* TODO: the repetition here smells like an XMacro waiting to happen */ + overlay_text_fill, + overlay_bg_fill, + overlay_snowflakes_text_fill, + overlay_grapha_fill, + overlay_graphb_fill, + overlay_finish_fill; +static XRenderColor overlay_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff }, + overlay_shadow_color = { 0x0000, 0x0000, 0x0000, 0x8800}, + overlay_bg_color = { 0x0, 0x1000, 0x0, 0x9000}, + overlay_div_color = { 0x2000, 0x3000, 0x2000, 0x9000}, + overlay_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 }, + overlay_trans_color = {0x00, 0x00, 0x00, 0x00}, + overlay_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */ + overlay_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */ + + +/* we need a copy of this pointer for the vmon callback :( */ +static vwm_t *vwm_ptr; + +/* moves what's below a given row up above it if specified, the row becoming discarded */ +static void snowflake_row(vwm_t *vwm, vwm_xwindow_t *xwin, Picture pic, int copy, int row) +{ + VWM_TRACE("pid=%i xwin=%p row=%i copy=%i heirarhcy_end=%i", xwin->monitor->pid, xwin, row, copy, xwin->overlay.heirarchy_end); + + if (copy) { + /* copy row to tmp */ + XRenderComposite(vwm->display, PictOpSrc, pic, None, xwin->overlay.tmp_picture, + 0, row * OVERLAY_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, 0, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ + } + + /* shift up */ + XRenderChangePicture(vwm->display, pic, CPRepeat, &pa_no_repeat); + XRenderComposite(vwm->display, PictOpSrc, pic, None, pic, + 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, row * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, (1 + xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ + XRenderChangePicture(vwm->display, pic, CPRepeat, &pa_repeat); + + if (copy) { + /* copy tmp to top of snowflakes */ + XRenderComposite(vwm->display, PictOpSrc, xwin->overlay.tmp_picture, None, pic, + 0, 0, /* src */ + 0, 0, /* mask */ + 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ + } else { + /* clear the snowflake row */ + XRenderFillRectangle(vwm->display, PictOpSrc, pic, &overlay_trans_color, + 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ + } +} + +/* XXX TODO libvmon automagic children following races with explicit X client pid monitoring with different outcomes, it should be irrelevant which wins, + * currently the only visible difference is the snowflakes gap (heirarchy_end) varies, which is why I haven't bothered to fix it, I barely even notice. + */ + +/* shifts what's below a given row down a row, and clears the row, preparing it for populating */ +static void allocate_row(vwm_t *vwm, vwm_xwindow_t *xwin, Picture pic, int row) +{ + VWM_TRACE("pid=%i xwin=%p row=%i", xwin->monitor->pid, xwin, row); + + /* shift everything below the row down */ + XRenderComposite(vwm->display, PictOpSrc, pic, None, pic, + 0, row * OVERLAY_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, xwin->overlay.height - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ + /* fill the space created with transparent pixels */ + XRenderFillRectangle(vwm->display, PictOpSrc, pic, &overlay_trans_color, + 0, row * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ +} + + +/* shadow a row from the text layer in the shadow layer */ +static void shadow_row(vwm_t *vwm, vwm_xwindow_t *xwin, int row) +{ + /* the current technique for creating the shadow is to simply render the text at +1/-1 pixel offsets on both axis in translucent black */ + XRenderComposite(vwm->display, PictOpSrc, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + -1, row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); + + XRenderComposite(vwm->display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + 0, -1 + row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); + + XRenderComposite(vwm->display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + 1, row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); + + XRenderComposite(vwm->display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + 0, 1 + row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); +} + + +/* simple helper to map the vmon per-proc argv array into an XTextItem array, deals with threads vs. processes and the possibility of the comm field not getting read in before the process exited... */ +static void argv2xtext(vmon_proc_t *proc, XTextItem *items, int *nr_items) /* XXX TODO: reallocate items when too small... */ +{ + int i; + int nr = 0; + + if (proc->is_thread) { /* stick the thread marker at the start of threads */ + items[0].nchars = sizeof(OVERLAY_ISTHREAD_ARGV) - 1; + items[0].chars = OVERLAY_ISTHREAD_ARGV; + items[0].delta = 4; + items[0].font = None; + nr++; + } + + if (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len) { + items[nr].nchars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1; + items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array; + } else { + /* sometimes a process is so ephemeral we don't manage to sample its comm, XXX TODO: we always have a pid, stringify it? */ + items[nr].nchars = sizeof(OVERLAY_NOCOMM_ARGV) - 1; + items[nr].chars = OVERLAY_NOCOMM_ARGV; + } + items[nr].delta = 4; + items[nr].font = None; + nr++; + + for (i = 1; i < ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argc; nr++, i++) { + items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]; + items[nr].nchars = strlen(((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]); /* TODO: libvmon should inform us of the length */ + items[nr].delta = 4; + items[nr].font = None; + } + + (*nr_items) = nr; +} + + +/* helper for counting number of existing descendants subtrees */ +static int count_rows(vmon_proc_t *proc) { + int count = 1; /* XXX maybe suppress proc->is_new? */ + vmon_proc_t *child; + + if (!proc->is_thread) { + list_for_each_entry(child, &proc->threads, threads) { + count += count_rows(child); + } + } + + list_for_each_entry(child, &proc->children, siblings) { + count += count_rows(child); + } + + return count; +} + + +/* recursive draw function for the consolidated version of the overlay rendering which also implements snowflakes */ +static void draw_overlay(vwm_t *vwm, vwm_xwindow_t *xwin, vmon_proc_t *proc, int *depth, int *row) +{ + vmon_proc_t *child; + vwm_perproc_ctxt_t *proc_ctxt = proc->foo; + vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; + + /* graph variables */ + int a_height, b_height; + double utime_delta, stime_delta; + + /* text variables */ + char str[256]; + int str_len; + XTextItem items[1024]; /* XXX TODO: dynamically allocate this and just keep it at the high water mark.. create a struct to encapsulate this, nr_items, and alloc_items... */ + int nr_items; + int direction, ascent, descent; + XCharStruct charstruct; + + if ((*row)) { /* except row 0 (Idle/IOWait graph), handle any stale and new processes/threads */ + if (proc->is_stale) { + /* what to do when a process (subtree) has gone away */ + static int in_stale = 0; + int in_stale_entrypoint = 0; + + /* I snowflake the stale processes from the leaves up for a more intuitive snowflake order... + * (I expect the command at the root of the subtree to appear at the top of the snowflakes...) */ + /* This does require that I do a separate forward recursion to determine the number of rows + * so I can correctly snowflake in reverse */ + if (!in_stale) { + VWM_TRACE("entered stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); + in_stale_entrypoint = in_stale = 1; + (*row) += count_rows(proc) - 1; + } + + (*depth)++; + list_for_each_entry_prev(child, &proc->children, siblings) { + draw_overlay(vwm, xwin, child, depth, row); + (*row)--; + } + + if (!proc->is_thread) { + list_for_each_entry_prev(child, &proc->threads, threads) { + draw_overlay(vwm, xwin, child, depth, row); + (*row)--; + } + } + (*depth)--; + + VWM_TRACE("%i (%.*s) is stale @ depth %i row %i is_thread=%i", proc->pid, + ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1, + ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array, + (*depth), (*row), proc->is_thread); + + /* stamp the graphs with the finish line */ + XRenderComposite(vwm->display, PictOpSrc, overlay_finish_fill, None, xwin->overlay.grapha_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ + 1, OVERLAY_ROW_HEIGHT - 1); + XRenderComposite(vwm->display, PictOpSrc, overlay_finish_fill, None, xwin->overlay.graphb_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ + 1, OVERLAY_ROW_HEIGHT - 1); + + /* extract the row from the various layers */ + snowflake_row(vwm, xwin, xwin->overlay.grapha_picture, 1, (*row)); + snowflake_row(vwm, xwin, xwin->overlay.graphb_picture, 1, (*row)); + snowflake_row(vwm, xwin, xwin->overlay.text_picture, 0, (*row)); + snowflake_row(vwm, xwin, xwin->overlay.shadow_picture, 0, (*row)); + xwin->overlay.snowflakes_cnt++; + + /* stamp the name (and whatever else we include) into overlay.text_picture */ + argv2xtext(proc, items, &nr_items); + XDrawText(vwm->display, xwin->overlay.text_pixmap, text_gc, + 5, (xwin->overlay.heirarchy_end + 1) * OVERLAY_ROW_HEIGHT - 3,/* dst x, y */ + items, nr_items); + shadow_row(vwm, xwin, xwin->overlay.heirarchy_end); + + xwin->overlay.heirarchy_end--; + + if (in_stale_entrypoint) { + VWM_TRACE("exited stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); + in_stale = 0; + } + + return; + } else if (proc->is_new) { + /* what to do when a process has been introduced */ + VWM_TRACE("%i is new", proc->pid); + + allocate_row(vwm, xwin, xwin->overlay.grapha_picture, (*row)); + allocate_row(vwm, xwin, xwin->overlay.graphb_picture, (*row)); + allocate_row(vwm, xwin, xwin->overlay.text_picture, (*row)); + allocate_row(vwm, xwin, xwin->overlay.shadow_picture, (*row)); + + xwin->overlay.heirarchy_end++; + } + } + +/* CPU utilization graphs */ + if (!(*row)) { + /* XXX: sortof kludged in IOWait and Idle % @ row 0 */ + stime_delta = iowait_delta; + utime_delta = idle_delta; + } else { + /* use the generation number to avoid recomputing this stuff for callbacks recurring on the same process in the same sample */ + if (proc_ctxt->generation != vmon.generation) { + proc_ctxt->stime_delta = proc_stat->stime - proc_ctxt->last_stime; + proc_ctxt->utime_delta = proc_stat->utime - proc_ctxt->last_utime; + proc_ctxt->last_utime = proc_stat->utime; + proc_ctxt->last_stime = proc_stat->stime; + + proc_ctxt->generation = vmon.generation; + } + + if (proc->is_new) { + /* we need a minimum of two samples before we can compute a delta to plot, + * so we suppress that and instead mark the start of monitoring with an impossible 100% of both graph contexts, a starting line. */ + stime_delta = utime_delta = total_delta; + } else { + stime_delta = proc_ctxt->stime_delta; + utime_delta = proc_ctxt->utime_delta; + } + } + + /* compute the bar heights for this sample */ + a_height = (stime_delta / total_delta * (double)(OVERLAY_ROW_HEIGHT - 1)); /* give up 1 pixel for the div */ + b_height = (utime_delta / total_delta * (double)(OVERLAY_ROW_HEIGHT - 1)); + + /* round up to 1 pixel when the scaled result is a fraction less than 1, + * I want to at least see 1 pixel blips for the slightest cpu utilization */ + if (stime_delta && !a_height) a_height = 1; + if (utime_delta && !b_height) b_height = 1; + + /* draw the two bars for this sample at the current phase in the graphs, note the first is ceiling-based, second floor-based */ + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_visible_color, + xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ + 1, a_height); /* dst w, h */ + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_visible_color, + xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT + (OVERLAY_ROW_HEIGHT - b_height) - 1, /* dst x, y */ + 1, b_height); /* dst w, h */ + + if (!(*row)) { + /* here's where the Idle/IOWait row drawing concludes */ + if (1 /* FIXME TODO compositing_mode*/) { + snprintf(str, sizeof(str), "\\/\\/\\ %2iHz %n", (int)(sampling_interval < 0 ? 0 : 1 / sampling_intervals[sampling_interval]), &str_len); + /* TODO: I clear and redraw this row every time, which is unnecessary, small optimization would be to only do so when: + * - overlay resized, and then constrain the clear to the affected width + * - Hz changed + */ + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, + 0, 0, /* dst x, y */ + xwin->attrs.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ + XTextExtents(overlay_font, str, str_len, &direction, &ascent, &descent, &charstruct); + XDrawString(vwm->display, xwin->overlay.text_pixmap, text_gc, + xwin->attrs.width - charstruct.width, OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ + str, str_len); + shadow_row(vwm, xwin, 0); + } + (*row)++; + draw_overlay(vwm, xwin, proc, depth, row); + return; + } + +/* process heirarchy text and accompanying per-process details like wchan/pid/state... */ + if (1 /* FIXME TODO compositing_mode */) { /* this stuff can be skipped when monitors aren't visible */ + /* TODO: make the columns interactively configurable @ runtime */ + if (!proc->is_new) { + /* XXX for now always clear the row, this should be capable of being optimized in the future (if the datums driving the text haven't changed...) */ + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, + 0, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ + } + + /* put the process' wchan, state, and PID columns @ the far right */ + if (proc->is_thread || list_empty(&proc->threads)) { /* only threads or non-threaded processes include the wchan and state */ + snprintf(str, sizeof(str), " %.*s %5i %c %n", + proc_stat->wchan.len, + proc_stat->wchan.len == 1 && proc_stat->wchan.array[0] == '0' ? "-" : proc_stat->wchan.array, + proc->pid, + proc_stat->state, + &str_len); + } else { /* we're a process having threads, suppress the wchan and state, as they will be displayed for the thread of same pid */ + snprintf(str, sizeof(str), " %5i %n", proc->pid, &str_len); + } + + XTextExtents(overlay_font, str, str_len, &direction, &ascent, &descent, &charstruct); + + /* the process' comm label indented according to depth, followed with their respective argv's */ + argv2xtext(proc, items, &nr_items); + XDrawText(vwm->display, xwin->overlay.text_pixmap, text_gc, + (*depth) * (OVERLAY_ROW_HEIGHT / 2), ((*row) + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ + items, nr_items); + + /* ensure the area for the rest of the stuff is cleared, we don't put much text into thread rows so skip it for those. */ + if (!proc->is_thread) { + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, + xwin->attrs.width - charstruct.width, (*row) * OVERLAY_ROW_HEIGHT, /* dst x,y */ + xwin->overlay.width - (xwin->attrs.width - charstruct.width), OVERLAY_ROW_HEIGHT); /* dst w,h */ + } + + XDrawString(vwm->display, xwin->overlay.text_pixmap, text_gc, + xwin->attrs.width - charstruct.width, ((*row) + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ + str, str_len); + + /* only if this process isn't the root process @ the window shall we consider all relational drawing conditions */ + if (proc != xwin->monitor) { + vmon_proc_t *ancestor, *sibling, *last_sibling = NULL; + struct list_head *rem; + int needs_tee = 0; + int bar_x = 0, bar_y = 0; + int sub; + + /* XXX: everything done in this code block only dirties _this_ process' row in the rendered overlay output */ + + /* walk up the ancestors until reaching xwin->monitor, any ancestors we encounter which have more siblings we draw a vertical bar for */ + /* this draws the |'s in something like: | | | | comm */ + for (sub = 1, ancestor = proc->parent; ancestor && ancestor != xwin->monitor; ancestor = ancestor->parent) { + sub++; + bar_x = ((*depth) - sub) * (OVERLAY_ROW_HEIGHT / 2) + 4; + bar_y = ((*row) + 1) * OVERLAY_ROW_HEIGHT; + + /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ + for (rem = ancestor->siblings.next; rem != &ancestor->parent->children; rem = rem->next) { + if (!(list_entry(rem, vmon_proc_t, siblings)->is_stale)) { + XDrawLine(vwm->display, xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ + bar_x, bar_y); /* dst x2, y2 (vertical line) */ + break; /* stop looking for more siblings at this ancestor when we find one that isn't stale */ + } + } + } + + /* determine if _any_ of our siblings have children requiring us to draw a tee immediately before our comm string. + * The only sibling which doesn't cause this to happen is the last one in the children list, if it has children it has no impact on its remaining + * siblings, as there are none. + * + * This draws the + in something like: | | | | +comm + */ + + /* find the last sibling (this has to be done due to the potential for stale siblings at the tail, and we'd rather not repeatedly check for it) */ + list_for_each_entry(sibling, &proc->parent->children, siblings) { + if (!sibling->is_stale) last_sibling = sibling; + } + + /* now look for siblings with non-stale children to determine if a tee is needed, ignoring the last sibling */ + list_for_each_entry(sibling, &proc->parent->children, siblings) { + /* skip stale siblings, they aren't interesting as they're invisible, and the last sibling has no bearing on wether we tee or not. */ + if (sibling->is_stale || sibling == last_sibling) continue; + + /* if any of the other siblings have children which are not stale, put a tee in front of our name, but ignore stale children */ + list_for_each_entry(child, &sibling->children, siblings) { + if (!child->is_stale) { + needs_tee = 1; + break; + } + } + + /* if we still don't think we need a tee, check if there are threads */ + if (!needs_tee) { + list_for_each_entry(child, &sibling->threads, threads) { + if (!child->is_stale) { + needs_tee = 1; + break; + } + } + } + + /* found a tee is necessary, all that's left is to determine if the tee is a corner and draw it accordingly, stopping the search. */ + if (needs_tee) { + bar_x = ((*depth) - 1) * (OVERLAY_ROW_HEIGHT / 2) + 4; + + /* if we're the last sibling, corner the tee by shortening the vbar */ + if (proc == last_sibling) { + XDrawLine(vwm->display, xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ + bar_x, bar_y - 4); /* dst x2, y2 (vertical bar) */ + } else { + XDrawLine(vwm->display, xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ + bar_x, bar_y); /* dst x2, y2 (vertical bar) */ + } + + XDrawLine(vwm->display, xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - 4, /* dst x1, y1 */ + bar_x + 2, bar_y - 4); /* dst x2, y2 (horizontal bar) */ + + /* terminate the outer sibling loop upon drawing the tee... */ + break; + } + } + } + shadow_row(vwm, xwin, (*row)); + } + + (*row)++; + + /* recur any threads first, then any children processes */ + (*depth)++; + if (!proc->is_thread) { /* XXX: the threads member serves as the list head only when not a thread */ + list_for_each_entry(child, &proc->threads, threads) { + draw_overlay(vwm, xwin, child, depth, row); + } + } + + list_for_each_entry(child, &proc->children, siblings) { + draw_overlay(vwm, xwin, child, depth, row); + } + (*depth)--; +} + + +/* consolidated version of overlay text and graph rendering, makes snowflakes integration cleaner, this always gets called regadless of the overlays mode */ +static void maintain_overlay(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + int row = 0, depth = 0; + + if (!xwin->monitor || !xwin->monitor->stores[VMON_STORE_PROC_STAT]) return; + + /* TODO: + * I side effect of responding to window resizes in this function is there's a latency proportional to the current sample_interval. + * Something to fix is to resize the overlays when the window resizes. + * However, simply resizing the overlays is insufficient. Their contents need to be redrawn in the new dimensions, this is where it + * gets annoying. The current maintain/draw_overlay makes assumptions about being run from the periodic vmon per-process callback. + * There needs to be a redraw mode added where draw_overlay is just reconstructing the current state, which requires that we suppress + * the phase advance and in maintain_overlay() and just enter draw_overlay() to redraw everything for the same generation. + * So this probably requires some tweaking of draw_overlay() as well as maintain_overlay(). I want to be able tocall mainta_overlays() + * from anywhere, and have it detect if it's being called on the same generation or if the generation has advanced. + * For now, the monitors will just be a little latent in window resizes which is pretty harmless artifact. + */ + + /* if the window is larger than the overlays currently are, enlarge them */ + if (xwin->attrs.width > xwin->overlay.width || xwin->attrs.height > xwin->overlay.height) { + vwm_overlay_t existing; + Pixmap pixmap; + + existing = xwin->overlay; + + xwin->overlay.width = MAX(xwin->overlay.width, MAX(xwin->attrs.width, OVERLAY_GRAPH_MIN_WIDTH)); + xwin->overlay.height = MAX(xwin->overlay.height, MAX(xwin->attrs.height, OVERLAY_GRAPH_MIN_HEIGHT)); + + pixmap = XCreatePixmap(vwm->display, VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.grapha_picture = XRenderCreatePicture(vwm->display, pixmap, XRenderFindStandardFormat(vwm->display, OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); + XFreePixmap(vwm->display, pixmap); + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(vwm->display, VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.graphb_picture = XRenderCreatePicture(vwm->display, pixmap, XRenderFindStandardFormat(vwm->display, OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); + XFreePixmap(vwm->display, pixmap); + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(vwm->display, VWM_XROOT(vwm), xwin->overlay.width, OVERLAY_ROW_HEIGHT, OVERLAY_MASK_DEPTH); + xwin->overlay.tmp_picture = XRenderCreatePicture(vwm->display, pixmap, XRenderFindStandardFormat(vwm->display, OVERLAY_MASK_FORMAT), 0, NULL); + XFreePixmap(vwm->display, pixmap); + + /* keep the text_pixmap reference around for XDrawText usage */ + xwin->overlay.text_pixmap = XCreatePixmap(vwm->display, VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.text_picture = XRenderCreatePicture(vwm->display, xwin->overlay.text_pixmap, XRenderFindStandardFormat(vwm->display, OVERLAY_MASK_FORMAT), 0, NULL); + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(vwm->display, VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.shadow_picture = XRenderCreatePicture(vwm->display, pixmap, XRenderFindStandardFormat(vwm->display, OVERLAY_MASK_FORMAT), 0, NULL); + XFreePixmap(vwm->display, pixmap); + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.shadow_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(vwm->display, VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, 32); + xwin->overlay.picture = XRenderCreatePicture(vwm->display, pixmap, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), 0, NULL); + XFreePixmap(vwm->display, pixmap); + + if (existing.width) { + /* XXX: note the graph pictures are copied from their current phase in the x dimension */ + XRenderComposite(vwm->display, PictOpSrc, existing.grapha_picture, None, xwin->overlay.grapha_picture, + existing.phase, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(vwm->display, PictOpSrc, existing.graphb_picture, None, xwin->overlay.graphb_picture, + existing.phase, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(vwm->display, PictOpSrc, existing.text_picture, None, xwin->overlay.text_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(vwm->display, PictOpSrc, existing.shadow_picture, None, xwin->overlay.shadow_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(vwm->display, PictOpSrc, existing.picture, None, xwin->overlay.picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + xwin->overlay.phase = 0; /* having unrolled the existing graph[ab] pictures into the larger ones, phase is reset to 0 */ + XRenderFreePicture(vwm->display, existing.grapha_picture); + XRenderFreePicture(vwm->display, existing.graphb_picture); + XRenderFreePicture(vwm->display, existing.tmp_picture); + XRenderFreePicture(vwm->display, existing.text_picture); + XFreePixmap(vwm->display, existing.text_pixmap); + XRenderFreePicture(vwm->display, existing.shadow_picture); + XRenderFreePicture(vwm->display, existing.picture); + } + } + + xwin->overlay.phase += (xwin->overlay.width - 1); /* simply change this to .phase++ to scroll the other direction */ + xwin->overlay.phase %= xwin->overlay.width; + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); + XRenderFillRectangle(vwm->display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); + + /* recursively draw the monitored processes to the overlay */ + draw_overlay(vwm, xwin, xwin->monitor, &depth, &row); +} + + +/* this callback gets invoked at sample time for every process we've explicitly monitored (not autofollowed children/threads) + * It's where we update the cumulative data for all windows, including the graph masks, regardless of their visibility + * It's also where we compose the graphs and text for visible windows into a picture ready for compositing with the window contents */ +static void proc_sample_callback(vmon_t *vmon, vmon_proc_t *proc, vwm_xwindow_t *xwin) +{ + //VWM_TRACE("proc=%p xwin=%p", proc, xwin); + /* render the various always-updated overlays, this is the component we do regardless of the overlays mode and window visibility, + * essentially the incrementally rendered/historic components */ + maintain_overlay(vwm_ptr, xwin); + + /* if we've updated overlays for a mapped window, kick the compositor to do the costly parts of overlay drawing and compositing. */ + if (vwm_xwin_is_mapped(vwm_ptr, xwin)) vwm_composite_repaint_needed(vwm_ptr); +} + + +/* this callback gets invoked at sample time once "per sys" */ +static void sample_callback(vmon_t *_vmon) +{ + vmon_sys_stat_t *sys_stat = vmon.stores[VMON_STORE_SYS_STAT]; + this_total = sys_stat->user + sys_stat->nice + sys_stat->system + + sys_stat->idle + sys_stat->iowait + sys_stat->irq + + sys_stat->softirq + sys_stat->steal + sys_stat->guest; + + total_delta = this_total - last_total; + idle_delta = sys_stat->idle - last_idle; + iowait_delta = sys_stat->iowait - last_iowait; +} + + +/* these callbacks are invoked by the vmon library when process instances become monitored/unmonitored */ +static void vmon_ctor_cb(vmon_t *vmon, vmon_proc_t *proc) +{ + VWM_TRACE("proc->pid=%i", proc->pid); + proc->foo = calloc(1, sizeof(vwm_perproc_ctxt_t)); +} + + +static void vmon_dtor_cb(vmon_t *vmon, vmon_proc_t *proc) +{ + VWM_TRACE("proc->pid=%i", proc->pid); + if (proc->foo) { + free(proc->foo); + proc->foo = NULL; + } +} + + +/* return the composed height of the overlay */ +int vwm_overlay_xwin_composed_height(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + int snowflakes = xwin->overlay.snowflakes_cnt ? 1 + xwin->overlay.snowflakes_cnt : 0; /* don't include the separator row if there are no snowflakes */ + + return MIN((xwin->overlay.heirarchy_end + snowflakes) * OVERLAY_ROW_HEIGHT, xwin->attrs.height); +} + +/* reset snowflakes on the specified window */ +void vwm_overlay_xwin_reset_snowflakes(vwm_t *vwm, vwm_xwindow_t *xwin) { + if (xwin->overlay.snowflakes_cnt) { + xwin->overlay.snowflakes_cnt = 0; + vwm_composite_damage_win(vwm, xwin); + } +} + +static void init_overlay(vwm_t *vwm) { + static int initialized; + Window bitmask; + + if (initialized) return; + initialized = 1; + + /* we stow the vwm pointer so the vmon callback can access it, rather than allocating something + * to encapsulate the xwin and vwm pointers just for the callback... */ + vwm_ptr = vwm; + + /* initialize libvmon */ + vmon_init(&vmon, VMON_FLAG_2PASS, VMON_WANT_SYS_STAT, (VMON_WANT_PROC_STAT | VMON_WANT_PROC_FOLLOW_CHILDREN | VMON_WANT_PROC_FOLLOW_THREADS)); + vmon.proc_ctor_cb = vmon_ctor_cb; + vmon.proc_dtor_cb = vmon_dtor_cb; + vmon.sample_cb = sample_callback; + gettimeofday(&this_sample, NULL); + + /* get all the text and graphics stuff setup for overlays */ + overlay_font = XLoadQueryFont(vwm->display, OVERLAY_FIXED_FONT); + + /* create a GC for rendering the text using Xlib into the text overlay stencils */ + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, 1, OVERLAY_MASK_DEPTH); + text_gc = XCreateGC(vwm->display, bitmask, 0, NULL); + XSetForeground(vwm->display, text_gc, WhitePixel(vwm->display, vwm->screen_num)); + XFreePixmap(vwm->display, bitmask); + + /* create some repeating source fill pictures for drawing through the text and graph stencils */ + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, 1, 32); + overlay_text_fill = XRenderCreatePicture(vwm->display, bitmask, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_text_fill, &overlay_visible_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, 1, 32); + overlay_shadow_fill = XRenderCreatePicture(vwm->display, bitmask, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_shadow_fill, &overlay_shadow_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, OVERLAY_ROW_HEIGHT, 32); + overlay_bg_fill = XRenderCreatePicture(vwm->display, bitmask, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_bg_fill, &overlay_bg_color, 0, 0, 1, OVERLAY_ROW_HEIGHT); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_bg_fill, &overlay_div_color, 0, OVERLAY_ROW_HEIGHT - 1, 1, 1); + + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, 1, 32); + overlay_snowflakes_text_fill = XRenderCreatePicture(vwm->display, bitmask, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_snowflakes_text_fill, &overlay_snowflakes_visible_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, 1, 32); + overlay_grapha_fill = XRenderCreatePicture(vwm->display, bitmask, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_grapha_fill, &overlay_grapha_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, 1, 32); + overlay_graphb_fill = XRenderCreatePicture(vwm->display, bitmask, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_graphb_fill, &overlay_graphb_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(vwm->display, VWM_XROOT(vwm), 1, 2, 32); + overlay_finish_fill = XRenderCreatePicture(vwm->display, bitmask, XRenderFindStandardFormat(vwm->display, PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_finish_fill, &overlay_visible_color, 0, 0, 1, 1); + XRenderFillRectangle(vwm->display, PictOpSrc, overlay_finish_fill, &overlay_trans_color, 0, 1, 1, 1); +} + + +/* install a monitor on the window if it doesn't already have one and has _NET_WM_PID set */ +void vwm_overlay_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + Atom type; + int fmt; + unsigned long nitems; + unsigned long nbytes; + long *foo = NULL; + int pid = -1; + + init_overlay(vwm); + + if (xwin->monitor) return; + + if (XGetWindowProperty(vwm->display, xwin->id, vwm->wm_pid_atom, 0, 1, False, XA_CARDINAL, + &type, &fmt, &nitems, &nbytes, (unsigned char **)&foo) != Success || !foo) return; + + pid = *foo; + XFree(foo); + + /* add the client process to the monitoring heirarchy */ + /* XXX note libvmon here maintains a unique callback for each unique callback+xwin pair, so multi-window processes work */ + xwin->monitor = vmon_proc_monitor(&vmon, NULL, pid, VMON_WANT_PROC_INHERIT, (void (*)(vmon_t *, vmon_proc_t *, void *))proc_sample_callback, xwin); + /* FIXME: count_rows() isn't returning the right count sometimes (off by ~1), it seems to be related to racing with the automatic child monitoring */ + /* the result is an extra row sometimes appearing below the process heirarchy */ + xwin->overlay.heirarchy_end = 1 + count_rows(xwin->monitor); + xwin->overlay.snowflakes_cnt = 0; +} + + +/* remove monitoring on the window if installed */ +void vwm_overlay_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + if (xwin->monitor) vmon_proc_unmonitor(&vmon, xwin->monitor, (void (*)(vmon_t *, vmon_proc_t *, void *))proc_sample_callback, xwin); +} + + +/* this composes the maintained overlay into the window's overlay picture, this gets called from paint_all() on every repaint of xwin */ +/* we noop the call if the gen_last_composed and monitor->proc.generation numbers match, indicating there's nothing new to compose. */ +void vwm_overlay_xwin_compose(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + XserverRegion region; + XRectangle damage; + int height; + + if (!xwin->overlay.width) return; /* prevent winning race with maintain_overlay() and using an unready overlay... */ + + if (xwin->overlay.gen_last_composed == xwin->monitor->generation) return; /* noop if no sampling occurred since last compose */ + xwin->overlay.gen_last_composed = xwin->monitor->generation; /* remember this generation */ + + //VWM_TRACE("composing %p", xwin); + + height = vwm_overlay_xwin_composed_height(vwm, xwin); + + /* fill the overlay picture with the background */ + XRenderComposite(vwm->display, PictOpSrc, overlay_bg_fill, None, xwin->overlay.picture, + 0, 0, + 0, 0, + 0, 0, + xwin->attrs.width, height); + + /* draw the graphs into the overlay through the stencils being maintained by the sample callbacks */ + XRenderComposite(vwm->display, PictOpOver, overlay_grapha_fill, xwin->overlay.grapha_picture, xwin->overlay.picture, + 0, 0, + xwin->overlay.phase, 0, + 0, 0, + xwin->attrs.width, height); + XRenderComposite(vwm->display, PictOpOver, overlay_graphb_fill, xwin->overlay.graphb_picture, xwin->overlay.picture, + 0, 0, + xwin->overlay.phase, 0, + 0, 0, + xwin->attrs.width, height); + + /* draw the shadow into the overlay picture using a translucent black source drawn through the shadow mask */ + XRenderComposite(vwm->display, PictOpOver, overlay_shadow_fill, xwin->overlay.shadow_picture, xwin->overlay.picture, + 0, 0, + 0, 0, + 0, 0, + xwin->attrs.width, height); + + /* render overlay text into the overlay picture using a white source drawn through the overlay text as a mask, on top of everything */ + XRenderComposite(vwm->display, PictOpOver, overlay_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, + 0, 0, + 0, 0, + 0, 0, + xwin->attrs.width, (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); + + XRenderComposite(vwm->display, PictOpOver, overlay_snowflakes_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, + 0, 0, + 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, + 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, height - (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); + + /* damage the window to ensure the updated overlay is drawn (TODO: this can be done more selectively/efficiently) */ + damage.x = xwin->attrs.x + xwin->attrs.border_width; + damage.y = xwin->attrs.y + xwin->attrs.border_width; + damage.width = xwin->attrs.width; + damage.height = height; + region = XFixesCreateRegion(vwm->display, &damage, 1); + vwm_composite_damage_add(vwm, region); +} + +void vwm_overlay_rate_increase(vwm_t *vwm) { + if (sampling_interval + 1 < sizeof(sampling_intervals) / sizeof(sampling_intervals[0])) sampling_interval++; +} + +void vwm_overlay_rate_decrease(vwm_t *vwm) { + if (sampling_interval >= 0) sampling_interval--; +} + + +/* comvenience function for returning the time delta as a seconds.fraction float */ +static float delta(struct timeval *cur, struct timeval *prev) +{ + struct timeval res; + float delta; + + /* determine the # of whole.fractional seconds between prev and cur */ + timersub(cur, prev, &res); + + delta = res.tv_sec; + delta += (float)((float)res.tv_usec) / 1000000.0; + + return delta; +} + + +void vwm_overlay_update(vwm_t *vwm, int *desired_delay) { + static int sampling_paused = 0; + static int contiguous_drops = 0; + float this_delta; + + init_overlay(vwm); + + gettimeofday(&maybe_sample, NULL); + if ((sampling_interval == -1 && !sampling_paused) || /* XXX this is kind of a kludge to get the 0 Hz indicator drawn before pausing */ + (sampling_interval != -1 && ((this_delta = delta(&maybe_sample, &this_sample)) >= sampling_intervals[sampling_interval]))) { + vmon_sys_stat_t *sys_stat; + + /* automatically lower the sample rate if we can't keep up with the current sample rate */ + if (sampling_interval != -1 && sampling_interval <= prev_sampling_interval && + this_delta >= (sampling_intervals[sampling_interval] * 1.5)) { + contiguous_drops++; + /* require > 1 contiguous drops before lowering the rate, tolerates spurious one-off stalls */ + if (contiguous_drops > 2) sampling_interval--; + } else contiguous_drops = 0; + + /* age the sys-wide sample data into "last" variables, before the new sample overwrites them. */ + last_sample = this_sample; + this_sample = maybe_sample; + if ((sys_stat = vmon.stores[VMON_STORE_SYS_STAT])) { + last_user_cpu = sys_stat->user; + last_system_cpu = sys_stat->system; + last_total = sys_stat->user + + sys_stat->nice + + sys_stat->system + + sys_stat->idle + + sys_stat->iowait + + sys_stat->irq + + sys_stat->softirq + + sys_stat->steal + + sys_stat->guest; + + last_idle = sys_stat->idle; + last_iowait = sys_stat->iowait; + } + + vmon_sample(&vmon); /* XXX: calls proc_sample_callback() for explicitly monitored processes after sampling their descendants */ + /* XXX: also calls sample_callback() per invocation after sampling the sys wants */ + sampling_paused = (sampling_interval == -1); + prev_sampling_interval = sampling_interval; + } + + + /* TODO: make some effort to compute how long to sleep, but this is perfectly fine for now. */ + *desired_delay = sampling_interval != -1 ? sampling_intervals[sampling_interval] * 300.0 : -1; +} diff --git a/src/overlay.h b/src/overlay.h new file mode 100644 index 0000000..94e2dca --- /dev/null +++ b/src/overlay.h @@ -0,0 +1,36 @@ +#ifndef _OVERLAY_H +#define _OVERLAY_H + +#include <X11/Xlib.h> +#include <X11/extensions/Xrender.h> + +typedef struct _vwm_t vwm_t; +typedef struct _vwm_xwindow_t vwm_xwindow_t; + +/* everything needed by the per-window overlay's context */ +typedef struct _vwm_overlay_t { + Pixmap text_pixmap; /* pixmap for overlayed text (kept around for XDrawText usage) */ + Picture text_picture; /* picture representation of text_pixmap */ + Picture shadow_picture; /* text shadow layer */ + Picture grapha_picture; /* graph A layer */ + Picture graphb_picture; /* graph B layer */ + Picture tmp_picture; /* 1 row worth of temporary picture space */ + Picture picture; /* overlay picture derived from the pixmap, for render compositing */ + int width; /* current width of the overlay */ + int height; /* current height of the overlay */ + int phase; /* current position within the (horizontally scrolling) graphs */ + int heirarchy_end; /* row where the process heirarchy currently ends */ + int snowflakes_cnt; /* count of snowflaked rows (reset to zero to truncate snowflakes display) */ + int gen_last_composed; /* the last composed vmon generation */ +} vwm_overlay_t; + +int vwm_overlay_xwin_composed_height(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_reset_snowflakes(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_compose(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_rate_increase(vwm_t *vwm); +void vwm_overlay_rate_decrease(vwm_t *vwm); +void vwm_overlay_update(vwm_t *vwm, int *desired_delay); + +#endif diff --git a/src/screen.c b/src/screen.c new file mode 100644 index 0000000..8fb63a5 --- /dev/null +++ b/src/screen.c @@ -0,0 +1,152 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +/* return the appropriate screen, don't use the return value across event loops because randr events reallocate the array. */ + +#include <X11/Xlib.h> +#include <stdarg.h> +#include <values.h> + +#include "list.h" +#include "screen.h" +#include "vwm.h" +#include "window.h" +#include "xwindow.h" + + /* Xinerama/multihead screen functions */ + +/* return what fraction (0.0-1.0) of vwin overlaps with scr */ +static float vwm_screen_overlaps_xwin(vwm_t *vwm, const vwm_screen_t *scr, vwm_xwindow_t *xwin) +{ + float pct = 0, xover = 0, yover = 0; + + if (scr->x_org + scr->width < xwin->attrs.x || scr->x_org > xwin->attrs.x + xwin->attrs.width || + scr->y_org + scr->height < xwin->attrs.y || scr->y_org > xwin->attrs.y + xwin->attrs.height) + goto _out; + + /* they overlap, by how much? */ + xover = MIN(scr->x_org + scr->width, xwin->attrs.x + xwin->attrs.width) - MAX(scr->x_org, xwin->attrs.x); + yover = MIN(scr->y_org + scr->height, xwin->attrs.y + xwin->attrs.height) - MAX(scr->y_org, xwin->attrs.y); + + pct = (xover * yover) / (xwin->attrs.width * xwin->attrs.height); +_out: + VWM_TRACE("xover=%f yover=%f width=%i height=%i pct=%.4f", xover, yover, xwin->attrs.width, xwin->attrs.height, pct); + return pct; +} + + +const vwm_screen_t * vwm_screen_find(vwm_t *vwm, vwm_screen_rel_t rel, ...) +{ + static vwm_screen_t faux; + vwm_screen_t *scr, *best = &faux; /* default to faux as best */ + int i; + + faux.screen_number = 0; + faux.x_org = 0; + faux.y_org = 0; + faux.width = WidthOfScreen(DefaultScreenOfDisplay(vwm->display)); + faux.height = HeightOfScreen(DefaultScreenOfDisplay(vwm->display)); + + if (!vwm->xinerama_screens) goto _out; + +#define for_each_screen(_tmp) \ + for (i = 0, _tmp = vwm->xinerama_screens; i < vwm->xinerama_screens_cnt; _tmp = &vwm->xinerama_screens[++i]) + + switch (rel) { + case VWM_SCREEN_REL_XWIN: { + va_list ap; + vwm_xwindow_t *xwin; + float best_pct = 0, this_pct; + + va_start(ap, rel); + xwin = va_arg(ap, vwm_xwindow_t *); + va_end(ap); + + for_each_screen(scr) { + this_pct = vwm_screen_overlaps_xwin(vwm, scr, xwin); + if (this_pct > best_pct) { + best = scr; + best_pct = this_pct; + } + } + break; + } + + case VWM_SCREEN_REL_POINTER: { + int root_x, root_y, win_x, win_y; + unsigned int mask; + Window root, child; + + /* get the pointer coordinates and find which screen it's in */ + XQueryPointer(vwm->display, VWM_XROOT(vwm), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask); + + for_each_screen(scr) { + if (root_x >= scr->x_org && root_x < scr->x_org + scr->width && + root_y >= scr->y_org && root_y < scr->y_org + scr->height) { + best = scr; + break; + } + } + break; + } + + case VWM_SCREEN_REL_TOTAL: { + short x1 = MAXSHORT, y1 = MAXSHORT, x2 = MINSHORT, y2 = MINSHORT; + /* find the smallest x_org and y_org, the highest x_org + width and y_org + height, those are the two corners of the total rect */ + for_each_screen(scr) { + if (scr->x_org < x1) x1 = scr->x_org; + if (scr->y_org < y1) y1 = scr->y_org; + if (scr->x_org + scr->width > x2) x2 = scr->x_org + scr->width; + if (scr->y_org + scr->height > y2) y2 = scr->y_org + scr->height; + } + faux.x_org = x1; + faux.y_org = y1; + faux.width = x2 - x1; + faux.height = y2 - y1; + best = &faux; + break; + } + } +_out: + VWM_TRACE("Found Screen #%i: %hix%hi @ %hi,%hi", best->screen_number, best->width, best->height, best->x_org, best->y_org); + + return best; +} + + +/* check if a screen contains any windows (assuming the current desktop) */ +int vwm_screen_is_empty(vwm_t *vwm, const vwm_screen_t *scr) +{ + vwm_xwindow_t *xwin; + int is_empty = 1; + + list_for_each_entry(xwin, &vwm->xwindows, xwindows) { + if (!xwin->mapped) continue; + if (!xwin->managed || (xwin->managed->desktop == vwm->focused_desktop && !xwin->managed->shelved && !xwin->managed->configuring)) { + /* XXX: it may make more sense to see what %age of the screen is overlapped by windows, and consider it empty if < some % */ + /* This is just seeing if any window is predominantly within the specified screen, the rationale being if you had a focusable + * window on the screen you would have used the keyboard to make windows go there; this function is only used in determining + * wether a new window should go where the pointer is or not. */ + if (vwm_screen_overlaps_xwin(vwm, scr, xwin) >= 0.05) { + is_empty = 0; + break; + } + } + } + + return is_empty; +} diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 0000000..c8b7531 --- /dev/null +++ b/src/screen.h @@ -0,0 +1,19 @@ +#ifndef _SCREEN_H +#define _SCREEN_H + +#include <X11/extensions/Xinerama.h> /* XINERAMA extension, facilitates easy multihead awareness */ + +typedef struct _vwm_t vwm_t; + +typedef XineramaScreenInfo vwm_screen_t; /* conveniently reuse the xinerama type for describing screens */ + +typedef enum _vwm_screen_rel_t { + VWM_SCREEN_REL_XWIN, /* return the screen the supplied window most resides in */ + VWM_SCREEN_REL_POINTER, /* return the screen the pointer resides in */ + VWM_SCREEN_REL_TOTAL, /* return the bounding rectangle of all screens as one */ +} vwm_screen_rel_t; + +const vwm_screen_t * vwm_screen_find(vwm_t *vwm, vwm_screen_rel_t rel, ...); +int vwm_screen_is_empty(vwm_t *vwm, const vwm_screen_t *scr); + +#endif diff --git a/src/vwm.c b/src/vwm.c new file mode 100644 index 0000000..c2e5126 --- /dev/null +++ b/src/vwm.c @@ -0,0 +1,291 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <X11/cursorfont.h> +#include <X11/Xatom.h> +#include <X11/extensions/sync.h> /* SYNC extension, enables us to give vwm the highest X client priority, helps keep vwm responsive at all times */ +#include <X11/extensions/Xinerama.h> /* XINERAMA extension, facilitates easy multihead awareness */ +#include <X11/extensions/Xrandr.h> /* RANDR extension, facilitates display configuration change awareness */ +#include <X11/extensions/Xdamage.h> /* Damage extension, enables receipt of damage events, reports visible regions needing updating (compositing) */ +#include <X11/extensions/Xrender.h> /* Render extension, enables use of alpha channels and accelerated rendering of surfaces having alpha (compositing) */ +#include <X11/extensions/Xcomposite.h> /* Composite extension, enables off-screen redirection of window rendering (compositing) */ +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <string.h> +#include <inttypes.h> +#include <values.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/resource.h> +#include <poll.h> + +#include "composite.h" +#include "desktop.h" +#include "launch.h" +#include "logo.h" +#include "vwm.h" +#include "xevent.h" +#include "xwindow.h" + +static vwm_t vwm; + + /* Sync */ +static int sync_event, sync_error; + + /* Xinerama */ +static int xinerama_event, xinerama_error; +static int randr_event, randr_error; + + /* Compositing */ +static int composite_event, composite_error, composite_opcode; + +static int errhandler(Display *display, XErrorEvent *err) +{ + /* TODO */ + return 1; +} + +int main(int argc, char *argv[]) +{ + int err = 0; + int done = 0; + XEvent event; + Cursor pointer; + struct pollfd pfd; + char *console_args[] = {"xterm", "-class", CONSOLE_WM_CLASS, "-e", "/bin/sh", "-c", "screen -D -RR " CONSOLE_SESSION_STRING, NULL}; + +#define reterr_if(_cond, _fmt, _args...) \ + err++;\ + if (_cond) {\ + VWM_ERROR(_fmt, ##_args);\ + return err;\ + } + + INIT_LIST_HEAD(&vwm.desktops); + INIT_LIST_HEAD(&vwm.desktops_mru); + INIT_LIST_HEAD(&vwm.windows_mru); + INIT_LIST_HEAD(&vwm.xwindows); + + /* open connection with the server */ + reterr_if((vwm.display = XOpenDisplay(NULL)) == NULL, "Cannot open display"); + + /* prevent children from inheriting the X connection */ + reterr_if(fcntl(ConnectionNumber(vwm.display), F_SETFD, FD_CLOEXEC) < 0, "Cannot set FD_CLOEXEC on X connection"); + + /* get our scheduling priority, clients are launched with a priority LAUNCHED_RELATIVE_PRIORITY nicer than this */ + reterr_if((vwm.priority = getpriority(PRIO_PROCESS, getpid())) == -1, "Cannot get scheduling priority"); + + XSetErrorHandler(errhandler); + + vwm.screen_num = DefaultScreen(vwm.display); + + /* query the needed X extensions */ + reterr_if(!XQueryExtension (vwm.display, COMPOSITE_NAME, &composite_opcode, &composite_event, &composite_error), "No composite extension available"); + reterr_if(!XDamageQueryExtension(vwm.display, &vwm.damage_event, &vwm.damage_error), "No damage extension available"); + if (XSyncQueryExtension(vwm.display, &sync_event, &sync_error)) { + /* set the window manager to the maximum X client priority */ + XSyncSetPriority(vwm.display, VWM_XROOT(&vwm), 0x7fffffff); + } + + if (XineramaQueryExtension(vwm.display, &xinerama_event, &xinerama_error)) { + vwm.xinerama_screens = XineramaQueryScreens(vwm.display, &vwm.xinerama_screens_cnt); + } + + if (XRRQueryExtension(vwm.display, &randr_event, &randr_error)) { + XRRSelectInput(vwm.display, VWM_XROOT(&vwm), RRScreenChangeNotifyMask); + } + + /* allocate colors, I make assumptions about the X server's color capabilities since I'll only use this on modern-ish computers... */ + vwm.cmap = DefaultColormap(vwm.display, vwm.screen_num); + +#define color(_sym, _str) \ + XAllocNamedColor(vwm.display, vwm.cmap, _str, &vwm.colors._sym ## _color, &vwm.colors._sym ## _color); +#include "colors.def" +#undef color + + vwm.wm_delete_atom = XInternAtom(vwm.display, "WM_DELETE_WINDOW", False); + vwm.wm_protocols_atom = XInternAtom(vwm.display, "WM_PROTOCOLS", False); + vwm.wm_pid_atom = XInternAtom(vwm.display, "_NET_WM_PID", False); + + XSelectInput(vwm.display, VWM_XROOT(&vwm), + PropertyChangeMask | SubstructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); + XGrabKey(vwm.display, AnyKey, WM_GRAB_MODIFIER, VWM_XROOT(&vwm), False, GrabModeAsync, GrabModeAsync); + + XFlush(vwm.display); + + XSetInputFocus(vwm.display, VWM_XROOT(&vwm), RevertToPointerRoot, CurrentTime); + + /* create initial virtual desktop */ + vwm_desktop_focus(&vwm, vwm_desktop_create(&vwm, NULL)); + vwm_desktop_mru(&vwm, vwm.focused_desktop); + + /* manage all preexisting windows */ + vwm_xwin_create_existing(&vwm); + + /* create GC for logo drawing and window rubber-banding */ + vwm.gc = XCreateGC(vwm.display, VWM_XROOT(&vwm), 0, NULL); + XSetSubwindowMode(vwm.display, vwm.gc, IncludeInferiors); + XSetFunction(vwm.display, vwm.gc, GXxor); + + /* launch the console here so it's likely ready by the time the logo animation finishes (there's no need to synchronize with it currently) */ + vwm_launch(&vwm, console_args, VWM_LAUNCH_MODE_BG); + + /* first the logo color is the foreground */ + XSetForeground(vwm.display, vwm.gc, vwm.colors.logo_color.pixel); + vwm_draw_logo(&vwm); + + /* change to the rubber-banding foreground color */ + XSetForeground(vwm.display, vwm.gc, vwm.colors.rubberband_color.pixel); + + XClearWindow(vwm.display, VWM_XROOT(&vwm)); + + /* set the pointer */ + pointer = XCreateFontCursor(vwm.display, XC_X_cursor); + XDefineCursor(vwm.display, VWM_XROOT(&vwm), pointer); + + pfd.events = POLLIN; + pfd.revents = 0; + pfd.fd = ConnectionNumber(vwm.display); + + while (!done) { + do { + int delay; + + vwm_overlay_update(&vwm, &delay); + XFlush(vwm.display); + + if (!XPending(vwm.display)) { + if (poll(&pfd, 1, delay) == 0) break; + } + + XNextEvent(vwm.display, &event); + switch (event.type) { + case KeyPress: + VWM_TRACE("keypress"); + vwm_xevent_handle_key_press(&vwm, &event.xkey); + break; + + case KeyRelease: + VWM_TRACE("keyrelease"); + vwm_xevent_handle_key_release(&vwm, &event.xkey); + break; + + case ButtonPress: + VWM_TRACE("buttonpresss"); + vwm_xevent_handle_button_press(&vwm, &event.xbutton); + break; + + case MotionNotify: + //VWM_TRACE("motionnotify"); + vwm_xevent_handle_motion_notify(&vwm, &event.xmotion); + break; + + case ButtonRelease: + VWM_TRACE("buttonrelease"); + vwm_xevent_handle_button_release(&vwm, &event.xbutton); + break; + + case CreateNotify: + VWM_TRACE("createnotify"); + vwm_xevent_handle_create_notify(&vwm, &event.xcreatewindow); + break; + + case DestroyNotify: + VWM_TRACE("destroynotify"); + vwm_xevent_handle_destroy_notify(&vwm, &event.xdestroywindow); + break; + + case ConfigureRequest: + VWM_TRACE("configurerequest"); + vwm_xevent_handle_configure_request(&vwm, &event.xconfigurerequest); + break; + + case ConfigureNotify: + VWM_TRACE("configurenotify"); + vwm_xevent_handle_configure_notify(&vwm, &event.xconfigure); + break; + + case UnmapNotify: + VWM_TRACE("unmapnotify"); + vwm_xevent_handle_unmap_notify(&vwm, &event.xunmap); + break; + + case MapNotify: + VWM_TRACE("mapnotify"); + vwm_xevent_handle_map_notify(&vwm, &event.xmap); + break; + + case MapRequest: + VWM_TRACE("maprequest"); + vwm_xevent_handle_map_request(&vwm, &event.xmaprequest); + break; + + case PropertyNotify: + VWM_TRACE("property notify"); + vwm_xevent_handle_property_notify(&vwm, &event.xproperty); + break; + + case MappingNotify: + VWM_TRACE("mapping notify"); + vwm_xevent_handle_mapping_notify(&vwm, &event.xmapping); + break; + + case Expose: + VWM_TRACE("expose"); + break; + + case GravityNotify: + VWM_TRACE("gravitynotify"); + break; + + case ReparentNotify: + VWM_TRACE("reparentnotify"); + break; + + default: + if (event.type == randr_event + RRScreenChangeNotify) { + VWM_TRACE("rrscreenchangenotify"); + if (vwm.xinerama_screens) XFree(vwm.xinerama_screens); + vwm.xinerama_screens = XineramaQueryScreens(vwm.display, &vwm.xinerama_screens_cnt); + + vwm_composite_invalidate_root(&vwm); + } else if (event.type == vwm.damage_event + XDamageNotify) { + //VWM_TRACE("damagenotify"); + vwm_composite_damage_event(&vwm, (XDamageNotifyEvent *)&event); + } else { + VWM_ERROR("Unhandled X op %i", event.type); + } + break; + } + } while (QLength(vwm.display)); + + vwm_composite_paint_all(&vwm); + } + + /* close connection to server */ + XFlush(vwm.display); + XCloseDisplay(vwm.display); + + return 0; +} diff --git a/src/vwm.h b/src/vwm.h new file mode 100644 index 0000000..8bc0f7a --- /dev/null +++ b/src/vwm.h @@ -0,0 +1,79 @@ +#ifndef _UTIL_H +#define _UTIL_H + +#include <stdio.h> +// #include <X11/Xlib.h> +// #include <X11/Xutil.h> +#include <errno.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xinerama.h> + +#include "context.h" +#include "list.h" + +#define WINDOW_BORDER_WIDTH 1 +#define WM_GRAB_MODIFIER Mod1Mask /* the modifier for invoking vwm's controls */ + /* Mod4Mask would be the windows key instead of Alt, but there's an assumption + * in the code that grabs are being activated by Alt which complicates changing it, + * search for XGetModifierMapping to see where, feel free to fix it. Or you can + * just hack the code to expect the appropriate key instead of Alt, I didn't see the + * value of making it modifier mapping aware if it's always Alt for me. */ + +#define CONSOLE_WM_CLASS "VWMConsoleXTerm" /* the class we specify to the "console" xterm */ +#define CONSOLE_SESSION_STRING "_vwm_console.$DISPLAY" /* the unique console screen session identifier */ + + +#define VWM_ERROR(_fmt, _args...) fprintf(stderr, "%s:%i\t%s() "_fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##_args) +#define VWM_PERROR(_fmt, _args...) fprintf(stderr, "%s:%i\t%s() "_fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##_args, strerror(errno)) +#define VWM_BUG(_fmt, _args...) fprintf(stderr, "BUG %s:%i\t%s() "_fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##_args, strerror(errno)) + +#ifdef TRACE +#define VWM_TRACE(_fmt, _args...) fprintf(stderr, "%s:%i\t%s() "_fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##_args) +#else +#define VWM_TRACE(_fmt, _args...) do { } while(0) +#endif + +#define VWM_XROOT(_vwm) RootWindow((_vwm)->display, (_vwm)->screen_num) +#define VWM_XVISUAL(_vwm) DefaultVisual((_vwm)->display, (_vwm)->screen_num) +#define VWM_XDEPTH(_vwm) DefaultDepth((_vwm)->display, (_vwm)->screen_num) + +#define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) +#define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) + +typedef struct _vwm_window_t vwm_window_t; +typedef struct _vwm_desktop_t vwm_desktop_t; + +typedef struct _vwm_t { + Display *display; + Colormap cmap; + int screen_num; + GC gc; + Atom wm_delete_atom; + Atom wm_protocols_atom; + Atom wm_pid_atom; + int damage_event, damage_error; + + list_head_t desktops; /* global list of all (virtual) desktops in spatial created-in order */ + list_head_t desktops_mru; /* global list of all (virtual) desktops in MRU order */ + list_head_t windows_mru; /* global list of all managed windows kept in MRU order */ + list_head_t xwindows; /* global list of all xwindows kept in the X server stacking order */ + vwm_window_t *console; /* the console window */ + vwm_window_t *focused_origin; /* the originating window in a grabbed operation/transaction */ + vwm_desktop_t *focused_desktop; /* currently focused (virtual) desktop */ + vwm_window_t *focused_shelf; /* currently focused shelved window */ + vwm_context_t focused_context; /* currently focused context */ + int priority; /* scheduling priority of the vwm process, launcher nices relative to this */ + unsigned long fence_mask; /* global mask state for vwm_win_focus_next(... VWM_FENCE_MASKED_VIOLATE), + * if you use vwm on enough screens to overflow this, pics or it didn't happen. */ + struct colors { +#define color(_sym, _str) \ + XColor _sym ## _color; + #include "colors.def" +#undef color + } colors; + + XineramaScreenInfo *xinerama_screens; + int xinerama_screens_cnt; +} vwm_t; + +#endif diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..e28330a --- /dev/null +++ b/src/window.c @@ -0,0 +1,468 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + /* vwm "managed" windows (vwm_window_t) (which are built upon the "core" X windows (vwm_xwindow_t)) */ + +#include <X11/Xlib.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "desktop.h" +#include "list.h" +#include "vwm.h" +#include "window.h" +#include "xwindow.h" + +/* unmap the specified window and set the unmapping-in-progress flag so we can discard vwm-generated UnmapNotify events */ +void vwm_win_unmap(vwm_t *vwm, vwm_window_t *vwin) +{ + if (!vwin->xwindow->mapped) { + VWM_TRACE("inhibited unmap of \"%s\", not mapped by client", vwin->xwindow->name); + return; + } + VWM_TRACE("Unmapping \"%s\"", vwin->xwindow->name); + vwin->unmapping = 1; + XUnmapWindow(vwm->display, vwin->xwindow->id); +} + + +/* map the specified window and set the mapping-in-progress flag so we can discard vwm-generated MapNotify events */ +void vwm_win_map(vwm_t *vwm, vwm_window_t *vwin) +{ + if (!vwin->xwindow->mapped) { + VWM_TRACE("inhibited map of \"%s\", not mapped by client", vwin->xwindow->name); + return; + } + VWM_TRACE("Mapping \"%s\"", vwin->xwindow->name); + vwin->mapping = 1; + XMapWindow(vwm->display, vwin->xwindow->id); +} + + +/* make the specified window the most recently used one */ +void vwm_win_mru(vwm_t *vwm, vwm_window_t *vwin) +{ + list_move(&vwin->windows_mru, &vwm->windows_mru); +} + + +/* look up the X window in the global managed windows list */ +vwm_window_t * vwm_win_lookup(vwm_t *vwm, Window win) +{ + vwm_window_t *tmp, *vwin = NULL; + + list_for_each_entry(tmp, &vwm->windows_mru, windows_mru) { + if (tmp->xwindow->id == win) { + vwin = tmp; + break; + } + } + + return vwin; +} + + +/* return the currently focused window (considers current context...), may return NULL */ +vwm_window_t * vwm_win_focused(vwm_t *vwm) +{ + vwm_window_t *vwin = NULL; + + switch (vwm->focused_context) { + case VWM_CONTEXT_SHELF: + vwin = vwm->focused_shelf; + break; + + case VWM_CONTEXT_DESKTOP: + if (vwm->focused_desktop) vwin = vwm->focused_desktop->focused_window; + break; + + default: + VWM_BUG("Unsupported context"); + break; + } + + return vwin; +} + + +/* "autoconfigure" windows (configuration shortcuts like fullscreen/halfscreen/quarterscreen) and restoring the window */ +void vwm_win_autoconf(vwm_t *vwm, vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_autoconf_t conf, ...) +{ + const vwm_screen_t *scr; + va_list ap; + XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; + + /* remember the current configuration as the "client" configuration if it's not an autoconfigured one. */ + if (vwin->autoconfigured == VWM_WIN_AUTOCONF_NONE) vwin->client = vwin->xwindow->attrs; + + scr = vwm_screen_find(vwm, rel, vwin->xwindow); /* XXX FIXME: this becomes a bug when vwm_screen_find() uses non-xwin va_args */ + va_start(ap, conf); + switch (conf) { + case VWM_WIN_AUTOCONF_QUARTER: { + vwm_corner_t corner = va_arg(ap, vwm_corner_t); + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + switch (corner) { + case VWM_CORNER_TOP_LEFT: + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_CORNER_TOP_RIGHT: + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org; + break; + + case VWM_CORNER_BOTTOM_RIGHT: + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org + scr->height / 2; + break; + + case VWM_CORNER_BOTTOM_LEFT: + changes.x = scr->x_org; + changes.y = scr->y_org + scr->height / 2; + break; + } + break; + } + + case VWM_WIN_AUTOCONF_HALF: { + vwm_side_t side = va_arg(ap, vwm_side_t); + switch (side) { + case VWM_SIDE_TOP: + changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_SIDE_BOTTOM: + changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org + scr->height / 2; + break; + + case VWM_SIDE_LEFT: + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_SIDE_RIGHT: + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org; + break; + } + break; + } + + case VWM_WIN_AUTOCONF_FULL: + changes.width = scr->width - WINDOW_BORDER_WIDTH * 2; + changes.height = scr->height - WINDOW_BORDER_WIDTH * 2; + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_WIN_AUTOCONF_ALL: + changes.width = scr->width; + changes.height = scr->height; + changes.x = scr->x_org; + changes.y = scr->y_org; + changes.border_width = 0; + break; + + case VWM_WIN_AUTOCONF_NONE: /* restore window if autoconfigured */ + changes.width = vwin->client.width; + changes.height = vwin->client.height; + changes.x = vwin->client.x; + changes.y = vwin->client.y; + break; + } + va_end(ap); + + XConfigureWindow(vwm->display, vwin->xwindow->id, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); + vwin->autoconfigured = conf; +} + + +/* focus a window */ +/* this updates window border color as needed and the X input focus if mapped */ +void vwm_win_focus(vwm_t *vwm, vwm_window_t *vwin) +{ + VWM_TRACE("focusing: %#x", (unsigned int)vwin->xwindow->id); + + if (vwm_xwin_is_mapped(vwm, vwin->xwindow)) { + /* if vwin is mapped give it the input focus */ + XSetInputFocus(vwm->display, vwin->xwindow->id, RevertToPointerRoot, CurrentTime); + } + + /* update the border color accordingly */ + if (vwin->shelved) { + /* set the border of the newly focused window to the shelved color */ + XSetWindowBorder(vwm->display, vwin->xwindow->id, vwin == vwm->console ? vwm->colors.shelved_console_border_color.pixel : vwm->colors.shelved_window_border_color.pixel); + /* fullscreen windows in the shelf when focused, since we don't intend to overlap there */ + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_POINTER, VWM_WIN_AUTOCONF_FULL); /* XXX TODO: for now the shelf follows the pointer, it's simple. */ + } else { + if (vwin->desktop->focused_window) { + /* set the border of the previously focused window on the same desktop to the unfocused color */ + XSetWindowBorder(vwm->display, vwin->desktop->focused_window->xwindow->id, vwm->colors.unfocused_window_border_color.pixel); + } + + /* set the border of the newly focused window to the focused color */ + XSetWindowBorder(vwm->display, vwin->xwindow->id, vwm->colors.focused_window_border_color.pixel); + + /* persist this on a per-desktop basis so it can be restored on desktop switches */ + vwin->desktop->focused_window = vwin; + } +} + + +/* focus the next window on a virtual desktop relative to the supplied window, in the specified context, respecting screen boundaries according to fence. */ +vwm_window_t * vwm_win_focus_next(vwm_t *vwm, vwm_window_t *vwin, vwm_fence_t fence) +{ + const vwm_screen_t *scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, vwin->xwindow), *next_scr = NULL; + vwm_window_t *next; + unsigned long visited_mask; + +_retry: + visited_mask = 0; + list_for_each_entry(next, &vwin->windows_mru, windows_mru) { + /* searching for the next mapped window in this context, using vwin->windows as the head */ + if (&next->windows_mru == &vwm->windows_mru) continue; /* XXX: skip the containerless head, we're leveraging the circular list implementation */ + + if ((vwin->shelved && next->shelved) || + ((!vwin->shelved && !next->shelved && next->desktop == vwin->desktop) && + (fence == VWM_FENCE_IGNORE || + ((fence == VWM_FENCE_RESPECT || fence == VWM_FENCE_TRY_RESPECT) && vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, next->xwindow) == scr) || + (fence == VWM_FENCE_VIOLATE && vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, next->xwindow) != scr) || + (fence == VWM_FENCE_MASKED_VIOLATE && (next_scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, next->xwindow)) != scr && + !((1UL << next_scr->screen_number) & vwm->fence_mask)) + ))) break; + + if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr && next_scr != scr) visited_mask |= (1UL << next_scr->screen_number); + } + + if (fence == VWM_FENCE_TRY_RESPECT && next == vwin) { + /* if we tried to respect the fence and failed to find a next, fallback to ignoring the fence and try again */ + fence = VWM_FENCE_IGNORE; + goto _retry; + } else if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr) { + /* if we did a masked violate update the mask with the potentially new screen number */ + if (next == vwin && visited_mask) { + /* if we failed to find a next window on a different screen but had candidates we've exhausted screens and need to reset the mask then retry */ + VWM_TRACE("all candidate screens masked @ 0x%lx, resetting mask", vwm->fence_mask); + vwm->fence_mask = 0; + goto _retry; + } + vwm->fence_mask |= (1UL << next_scr->screen_number); + VWM_TRACE("VWM_FENCE_MASKED_VIOLATE fence_mask now: 0x%lx\n", vwm->fence_mask); + } + + if (vwin->shelved) { + if (next != vwm->focused_shelf) { + /* shelf switch, unmap the focused shelf and take it over */ + /* TODO FIXME: this makes assumptions about the shelf being focused calling unmap/map directly.. */ + vwm_win_unmap(vwm, vwm->focused_shelf); + + XFlush(vwm->display); + + vwm_win_map(vwm, next); + vwm->focused_shelf = next; + vwm_win_focus(vwm, next); + } + } else { + if (next != next->desktop->focused_window) { + /* focus the changed window */ + vwm_win_focus(vwm, next); + XRaiseWindow(vwm->display, next->xwindow->id); + } + } + + VWM_TRACE("vwin=%p xwin=%p name=\"%s\"", next, next->xwindow, next->xwindow->name); + + return next; +} + + +/* shelves a window, if the window is focused we focus the next one (if possible) */ +void vwm_win_shelve(vwm_t *vwm, vwm_window_t *vwin) +{ + /* already shelved, NOOP */ + if (vwin->shelved) return; + + /* shelving focused window, focus the next window */ + if (vwin == vwin->desktop->focused_window) { + vwm_win_mru(vwm, vwm_win_focus_next(vwm, vwin, VWM_FENCE_RESPECT)); + } + + if (vwin == vwin->desktop->focused_window) { + /* TODO: we can probably put this into vwm_win_focus_next() and have it always handled there... */ + vwin->desktop->focused_window = NULL; + } + + vwin->shelved = 1; + vwm_win_mru(vwm, vwin); + + /* newly shelved windows always become the focused shelf */ + vwm->focused_shelf = vwin; + + vwm_win_unmap(vwm, vwin); +} + + +/* helper for (idempotently) unfocusing a window, deals with context switching etc... */ +void vwm_win_unfocus(vwm_t *vwm, vwm_window_t *vwin) +{ + /* if we're the focused shelved window, cycle the focus to the next shelved window if possible, if there's no more shelf, switch to the desktop */ + /* TODO: there's probably some icky behaviors for focused windows unmapping/destroying in unfocused contexts, we probably jump contexts suddenly. */ + if (vwin == vwm->focused_shelf) { + VWM_TRACE("unfocusing focused shelf"); + vwm_win_focus_next(vwm, vwin, VWM_FENCE_IGNORE); + + if (vwin == vwm->focused_shelf) { + VWM_TRACE("shelf empty, leaving"); + /* no other shelved windows, exit the shelf context */ + vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP); + vwm->focused_shelf = NULL; + } + } + + /* if we're the focused window cycle the focus to the next window on the desktop if possible */ + if (vwin->desktop->focused_window == vwin) { + VWM_TRACE("unfocusing focused window"); + vwm_win_focus_next(vwm, vwin, VWM_FENCE_TRY_RESPECT); + } + + if (vwin->desktop->focused_window == vwin) { + VWM_TRACE("desktop empty"); + vwin->desktop->focused_window = NULL; + } +} + + +/* demote an managed window to an unmanaged one */ +vwm_xwindow_t * vwm_win_unmanage(vwm_t *vwm, vwm_window_t *vwin) +{ + vwm_win_mru(vwm, vwin); /* bump vwin to the mru head before unfocusing so we always move focus to the current head on unmanage of the focused window */ + vwm_win_unfocus(vwm, vwin); + list_del(&vwin->windows_mru); + + if (vwin == vwm->console) vwm->console = NULL; + if (vwin == vwm->focused_origin) vwm->focused_origin = NULL; + + vwin->xwindow->managed = NULL; + + return vwin->xwindow; +} + + +/* promote an unmanaged window to a managed one */ +vwm_window_t * vwm_win_manage_xwin(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + vwm_window_t *focused, *vwin = NULL; + + if (xwin->managed) { + VWM_TRACE("suppressed re-management of xwin=%p", xwin); + goto _fail; + } + + if (!(vwin = (vwm_window_t *)malloc(sizeof(vwm_window_t)))) { + VWM_PERROR("Failed to allocate vwin"); + goto _fail; + } + + XUngrabButton(vwm->display, AnyButton, AnyModifier, xwin->id); + XGrabButton(vwm->display, AnyButton, WM_GRAB_MODIFIER, xwin->id, False, (PointerMotionMask | ButtonPressMask | ButtonReleaseMask), GrabModeAsync, GrabModeAsync, None, None); + XGrabKey(vwm->display, AnyKey, WM_GRAB_MODIFIER, xwin->id, False, GrabModeAsync, GrabModeAsync); + XSetWindowBorder(vwm->display, xwin->id, vwm->colors.unfocused_window_border_color.pixel); + + vwin->hints = XAllocSizeHints(); + if (!vwin->hints) { + VWM_PERROR("Failed to allocate WM hints"); + goto _fail; + } + XGetWMNormalHints(vwm->display, xwin->id, vwin->hints, &vwin->hints_supplied); + + xwin->managed = vwin; + vwin->xwindow = xwin; + + vwin->desktop = vwm->focused_desktop; + vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; + vwin->mapping = vwin->unmapping = vwin->configuring = 0; + vwin->shelved = (vwm->focused_context == VWM_CONTEXT_SHELF); /* if we're in the shelf when the window is created, the window is shelved */ + vwin->client = xwin->attrs; /* remember whatever the current attributes are */ + + VWM_TRACE("hints: flags=%lx x=%i y=%i w=%i h=%i minw=%i minh=%i maxw=%i maxh=%i winc=%i hinc=%i basew=%i baseh=%i grav=%x", + vwin->hints->flags, + vwin->hints->x, vwin->hints->y, + vwin->hints->width, vwin->hints->height, + vwin->hints->min_width, vwin->hints->min_height, + vwin->hints->max_width, vwin->hints->max_height, + vwin->hints->width_inc, vwin->hints->height_inc, + vwin->hints->base_width, vwin->hints->base_height, + vwin->hints->win_gravity); + + if ((vwin->hints_supplied & (USSize | PSize))) { + vwin->client.width = vwin->hints->base_width; + vwin->client.height = vwin->hints->base_height; + } + + /* put it on the global windows_mru list, if there's a focused window insert the new one after it */ + if (!list_empty(&vwm->windows_mru) && (focused = vwm_win_focused(vwm))) { + /* insert the vwin immediately after the focused window, so Mod1+Tab goes to the new window */ + list_add(&vwin->windows_mru, &focused->windows_mru); + } else { + list_add(&vwin->windows_mru, &vwm->windows_mru); + } + + /* always raise newly managed windows so we know about them. */ + XRaiseWindow(vwm->display, xwin->id); + + /* if the desktop has no focused window yet, automatically focus the newly managed one, provided we're on the desktop context */ + if (!vwm->focused_desktop->focused_window && vwm->focused_context == VWM_CONTEXT_DESKTOP) { + VWM_TRACE("Mapped new window \"%s\" is alone on desktop \"%s\", focusing", xwin->name, vwm->focused_desktop->name); + vwm_win_focus(vwm, vwin); + } + + return vwin; + +_fail: + if (vwin) { + if (vwin->hints) XFree(vwin->hints); + free(vwin); + } + return NULL; +} + + +/* migrate a window to a new desktop, focuses the destination desktop as well */ +void vwm_win_migrate(vwm_t *vwm, vwm_window_t *vwin, vwm_desktop_t *desktop) +{ + vwm_win_unfocus(vwm, vwin); /* go through the motions of unfocusing the window if it is focused */ + vwin->shelved = 0; /* ensure not shelved */ + vwin->desktop = desktop; /* assign the new desktop */ + vwm_desktop_focus(vwm, desktop); /* currently we always focus the new desktop in a migrate */ + + vwm_win_focus(vwm, vwin); /* focus the window so borders get updated */ + vwm_win_mru(vwm, vwin); /* TODO: is this right? shouldn't the Mod1 release be what's responsible for this? I migrate so infrequently it probably doesn't matter */ + + XRaiseWindow(vwm->display, vwin->xwindow->id); /* ensure the window is raised */ +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..3b3c745 --- /dev/null +++ b/src/window.h @@ -0,0 +1,81 @@ +#ifndef _WINDOW_H +#define _WINDOW_H + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include "list.h" +#include "screen.h" + +typedef struct _vwm_t vwm_t; +typedef struct _vwm_xwindow_t vwm_xwindow_t; +typedef struct _vwm_desktop_t vwm_desktop_t; + +typedef enum _vwm_win_autoconf_t { + VWM_WIN_AUTOCONF_NONE, /* un-autoconfigured window (used to restore the configuration) */ + VWM_WIN_AUTOCONF_QUARTER, /* quarter-screened */ + VWM_WIN_AUTOCONF_HALF, /* half-screened */ + VWM_WIN_AUTOCONF_FULL, /* full-screened */ + VWM_WIN_AUTOCONF_ALL /* all-screened (borderless) */ +} vwm_win_autoconf_t; + +/* the managed window we create for every mapped window we actually manage */ +typedef struct _vwm_window_t { + list_head_t windows_mru; /* global list of managed windows kept in MRU order */ + + vwm_xwindow_t *xwindow; /* window being managed */ + vwm_desktop_t *desktop; /* desktop this window belongs to currently */ + + XWindowAttributes client; /* attrs of the client-configured window */ + + XSizeHints *hints; /* hints the client supplied */ + long hints_supplied; /* bitfield reflecting the hints the client supplied */ + + unsigned int autoconfigured:3; /* autoconfigured window states (none/quarter/half/full/all) */ + unsigned int mapping:1; /* is the window being mapped? (by vwm) */ + unsigned int unmapping:1; /* is the window being unmapped? (by vwm) */ + unsigned int configuring:1; /* is the window being configured/placed? (by vwm) */ + unsigned int shelved:1; /* is the window shelved? */ +} vwm_window_t; + + +void vwm_win_unmap(vwm_t *vwm, vwm_window_t *vwin); +void vwm_win_map(vwm_t *vwm, vwm_window_t *vwin); +void vwm_win_mru(vwm_t *vwm, vwm_window_t *vwin); +vwm_window_t * vwm_win_lookup(vwm_t *vwm, Window win); +vwm_window_t * vwm_win_focused(vwm_t *vwm); + +typedef enum _vwm_side_t { + VWM_SIDE_TOP, + VWM_SIDE_BOTTOM, + VWM_SIDE_LEFT, + VWM_SIDE_RIGHT +} vwm_side_t; + +typedef enum _vwm_corner_t { + VWM_CORNER_TOP_LEFT, + VWM_CORNER_TOP_RIGHT, + VWM_CORNER_BOTTOM_RIGHT, + VWM_CORNER_BOTTOM_LEFT +} vwm_corner_t; + +void vwm_win_autoconf(vwm_t *vwm, vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_autoconf_t conf, ...); +void vwm_win_focus(vwm_t *vwm, vwm_window_t *vwin); + +typedef enum _vwm_fence_t { + VWM_FENCE_IGNORE = 0, /* behave as if screen boundaries don't exist (like the pre-Xinerama code) */ + VWM_FENCE_RESPECT, /* confine operation to within the screen */ + VWM_FENCE_TRY_RESPECT, /* confine operation to within the screen, unless no options exist. */ + VWM_FENCE_VIOLATE, /* leave the screen for any other */ + VWM_FENCE_MASKED_VIOLATE /* leave the screen for any other not masked */ +} vwm_fence_t; + +vwm_window_t * vwm_win_focus_next(vwm_t *vwm, vwm_window_t *vwin, vwm_fence_t fence); +void vwm_win_shelve(vwm_t *vwm, vwm_window_t *vwin); +void vwm_win_unfocus(vwm_t *vwm, vwm_window_t *vwin); +vwm_xwindow_t * vwm_win_unmanage(vwm_t *vwm, vwm_window_t *vwin); +vwm_window_t * vwm_win_manage_xwin(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_win_migrate(vwm_t *vwm, vwm_window_t *vwin, vwm_desktop_t *desktop); + + +#endif diff --git a/src/xevent.c b/src/xevent.c new file mode 100644 index 0000000..1955ee3 --- /dev/null +++ b/src/xevent.c @@ -0,0 +1,269 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "X11/Xlib.h" + +#include "key.h" +#include "clickety.h" +#include "composite.h" +#include "desktop.h" +#include "screen.h" +#include "vwm.h" +#include "window.h" +#include "xwindow.h" + +/* this forms the glue between the X event loop in vwm.c and the rest of the + * code making up vwm */ + +void vwm_xevent_handle_key_press(vwm_t *vwm, XKeyPressedEvent *ev) +{ + vwm_key_pressed(vwm, ev->window, ev); +} + + +void vwm_xevent_handle_key_release(vwm_t *vwm, XKeyReleasedEvent *ev) +{ + vwm_key_released(vwm, ev->window, ev); +} + + +void vwm_xevent_handle_button_press(vwm_t *vwm, XButtonPressedEvent *ev) +{ + vwm_clickety_pressed(vwm, ev->window, ev); +} + + +void vwm_xevent_handle_motion_notify(vwm_t *vwm, XMotionEvent *ev) +{ + vwm_clickety_motion(vwm, ev->window, ev); +} + + +void vwm_xevent_handle_button_release(vwm_t *vwm, XButtonReleasedEvent *ev) +{ + vwm_clickety_released(vwm, ev->window, ev); +} + + +void vwm_xevent_handle_create_notify(vwm_t *vwm, XCreateWindowEvent *ev) +{ + vwm_xwin_create(vwm, ev->window, VWM_NOT_GRABBED); +} + + +void vwm_xevent_handle_destroy_notify(vwm_t *vwm, XDestroyWindowEvent *ev) +{ + vwm_xwindow_t *xwin; + + if ((xwin = vwm_xwin_lookup(vwm, ev->window))) { + vwm_xwin_destroy(vwm, xwin); + } +} + + +void vwm_xevent_handle_configure_request(vwm_t *vwm, XConfigureRequestEvent *ev) +{ + XWindowChanges changes = { + .x = ev->x, /* TODO: for now I don't manipulate anything */ + .y = ev->y, + .width = ev->width, + .height = ev->height, + .border_width = WINDOW_BORDER_WIDTH /* except I do override whatever the border width may be */ + }; + unsigned long change_mask = (ev->value_mask & (CWX | CWY | CWWidth | CWHeight)) | CWBorderWidth; + vwm_xwindow_t *xwin; + + /* XXX: windows raising themselves is annoying, so discard CWSibling and CWStackMode. */ + + if ((xwin = vwm_xwin_lookup(vwm, ev->window)) && + xwin->managed && + xwin->managed->autoconfigured == VWM_WIN_AUTOCONF_ALL) { + /* this is to allow auto-allscreen to succeed in getting a borderless window configured */ + change_mask &= ~CWBorderWidth; + } + + XConfigureWindow(vwm->display, ev->window, change_mask, &changes); +} + + +void vwm_xevent_handle_configure_notify(vwm_t *vwm, XConfigureEvent *ev) +{ + vwm_xwindow_t *xwin; + + if ((xwin = vwm_xwin_lookup(vwm, ev->window))) { + XWindowAttributes attrs; + vwm_xwin_restack(vwm, xwin, ev->above); + XGetWindowAttributes(vwm->display, ev->window, &attrs); + vwm_composite_handle_configure(vwm, xwin, &attrs); + VWM_TRACE("pre x=%i y=%i w=%i h=%i\n", xwin->attrs.x, xwin->attrs.y, xwin->attrs.width, xwin->attrs.height); + xwin->attrs = attrs; + VWM_TRACE("post x=%i y=%i w=%i h=%i\n", xwin->attrs.x, xwin->attrs.y, xwin->attrs.width, xwin->attrs.height); + } +} + + +void vwm_xevent_handle_unmap_notify(vwm_t *vwm, XUnmapEvent *ev) +{ + vwm_xwindow_t *xwin; + + /* unlike MapRequest, we simply are notified when a window is unmapped. */ + if ((xwin = vwm_xwin_lookup(vwm, ev->window))) { + if (xwin->managed) { + if (xwin->managed->unmapping) { + VWM_TRACE("swallowed vwm-induced UnmapNotify"); + xwin->managed->unmapping = 0; + } else { + /* client requested unmap, demote the window and note the unmapped state */ + vwm_win_unmanage(vwm, xwin->managed); + xwin->mapped = 0; + } + } else { + /* if it's not managed, we can't have caused the map */ + xwin->mapped = 0; + } + + vwm_composite_damage_win(vwm, xwin); + } +} + + +void vwm_xevent_handle_map_notify(vwm_t *vwm, XMapEvent *ev) +{ + vwm_xwindow_t *xwin; + + if ((xwin = vwm_xwin_lookup(vwm, ev->window))) { + if (xwin->managed && xwin->managed->mapping) { + VWM_TRACE("swallowed vwm-induced MapNotify"); + xwin->managed->mapping = 0; + } else { + /* some windows like popup dialog boxes bypass MapRequest */ + xwin->mapped = 1; + } + + vwm_composite_handle_map(vwm, xwin); + } +} + + +void vwm_xevent_handle_map_request(vwm_t *vwm, XMapRequestEvent *ev) +{ + vwm_xwindow_t *xwin; + vwm_window_t *vwin = NULL; + int domap = 1; + + /* FIXME TODO: this is a fairly spuriously open-coded mess, this stuff + * needs to be factored and moved elsewhere */ + + if ((xwin = vwm_xwin_lookup(vwm, ev->window)) && + ((vwin = xwin->managed) || (vwin = vwm_win_manage_xwin(vwm, xwin)))) { + XWindowAttributes attrs; + XWindowChanges changes = {.x = 0, .y = 0}; + unsigned changes_mask = (CWX | CWY); + XClassHint *classhint; + const vwm_screen_t *scr = NULL; + + xwin->mapped = 1; /* note that the client mapped the window */ + + /* figure out if the window is the console */ + if ((classhint = XAllocClassHint())) { + if (XGetClassHint(vwm->display, ev->window, classhint) && !strcmp(classhint->res_class, CONSOLE_WM_CLASS)) { + vwm->console = vwin; + vwm_win_shelve(vwm, vwin); + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); + domap = 0; + } + + if (classhint->res_class) XFree(classhint->res_class); + if (classhint->res_name) XFree(classhint->res_name); + XFree(classhint); + } + + /* TODO: this is a good place to hook in a window placement algo */ + + /* on client-requested mapping we place the window */ + if (!vwin->shelved) { + /* we place the window on the screen containing the the pointer only if that screen is empty, + * otherwise we place windows on the screen containing the currently focused window */ + /* since we query the geometry of windows in determining where to place them, a configuring + * flag is used to exclude the window being configured from those queries */ + scr = vwm_screen_find(vwm, VWM_SCREEN_REL_POINTER); + vwin->configuring = 1; + if (vwm_screen_is_empty(vwm, scr)) { + /* focus the new window if it isn't already focused when it's going to an empty screen */ + VWM_TRACE("window \"%s\" is alone on screen \"%i\", focusing", vwin->xwindow->name, scr->screen_number); + vwm_win_focus(vwm, vwin); + } else { + scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, vwm->focused_desktop->focused_window->xwindow); + } + vwin->configuring = 0; + + changes.x = scr->x_org; + changes.y = scr->y_org; + } else if (vwm->focused_context == VWM_CONTEXT_SHELF) { + scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, vwm->focused_shelf->xwindow); + changes.x = scr->x_org; + changes.y = scr->y_org; + } + + /* XXX TODO: does this belong here? */ + XGetWMNormalHints(vwm->display, ev->window, vwin->hints, &vwin->hints_supplied); + XGetWindowAttributes(vwm->display, ev->window, &attrs); + + + /* if the window size is precisely the screen size then directly "allscreen" the window right here */ + if (!vwin->shelved && scr && + attrs.width == scr->width && + attrs.height == scr->height) { + VWM_TRACE("auto-allscreened window \"%s\"", vwin->xwindow->name); + changes.border_width = 0; + changes_mask |= CWBorderWidth; + vwin->autoconfigured = VWM_WIN_AUTOCONF_ALL; + } + + vwin->client.x = changes.x; + vwin->client.y = changes.y; + vwin->client.height = attrs.height; + vwin->client.width = attrs.width; + + XConfigureWindow(vwm->display, ev->window, changes_mask, &changes); + } + + if (domap) { + XMapWindow(vwm->display, ev->window); + if (vwin && vwin->desktop->focused_window == vwin) { + XSync(vwm->display, False); + XSetInputFocus(vwm->display, vwin->xwindow->id, RevertToPointerRoot, CurrentTime); + } + } +} + + +void vwm_xevent_handle_property_notify(vwm_t *vwm, XPropertyEvent *ev) +{ + vwm_xwindow_t *xwin; + + if ((xwin = vwm_xwin_lookup(vwm, ev->window)) && + ev->atom == vwm->wm_pid_atom && + ev->state == PropertyNewValue) vwm_overlay_xwin_create(vwm, xwin); +} + + +void vwm_xevent_handle_mapping_notify(vwm_t *vwm, XMappingEvent *ev) +{ + XRefreshKeyboardMapping(ev); +} diff --git a/src/xevent.h b/src/xevent.h new file mode 100644 index 0000000..dc710fd --- /dev/null +++ b/src/xevent.h @@ -0,0 +1,18 @@ +#include "X11/Xlib.h" + +typedef struct _vwm_t vwm_t; + +void vwm_xevent_handle_key_press(vwm_t *vwm, XKeyPressedEvent *ev); +void vwm_xevent_handle_key_release(vwm_t *vwm, XKeyReleasedEvent *ev); +void vwm_xevent_handle_button_press(vwm_t *vwm, XButtonPressedEvent *ev); +void vwm_xevent_handle_motion_notify(vwm_t *vwm, XMotionEvent *ev); +void vwm_xevent_handle_button_release(vwm_t *vwm, XButtonReleasedEvent *ev); +void vwm_xevent_handle_create_notify(vwm_t *vwm, XCreateWindowEvent *ev); +void vwm_xevent_handle_destroy_notify(vwm_t *vwm, XDestroyWindowEvent *ev); +void vwm_xevent_handle_configure_request(vwm_t *vwm, XConfigureRequestEvent *ev); +void vwm_xevent_handle_configure_notify(vwm_t *vwm, XConfigureEvent *ev); +void vwm_xevent_handle_unmap_notify(vwm_t *vwm, XUnmapEvent *ev); +void vwm_xevent_handle_map_notify(vwm_t *vwm, XMapEvent *ev); +void vwm_xevent_handle_map_request(vwm_t *vwm, XMapRequestEvent *ev); +void vwm_xevent_handle_property_notify(vwm_t *vwm, XPropertyEvent *ev); +void vwm_xevent_handle_mapping_notify(vwm_t *vwm, XMappingEvent *ev); diff --git a/src/xwindow.c b/src/xwindow.c new file mode 100644 index 0000000..f646aac --- /dev/null +++ b/src/xwindow.c @@ -0,0 +1,247 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + /* bare X windows stuff, there's a distinction between bare xwindows and the vwm managed windows */ +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <stdlib.h> + +#include "composite.h" +#include "list.h" +#include "vwm.h" +#include "window.h" +#include "xwindow.h" + +#define HONOR_OVERRIDE_REDIRECT + +/* send a client message to a window (currently used for WM_DELETE) */ +void vwm_xwin_message(vwm_t *vwm, vwm_xwindow_t *xwin, Atom type, long foo) +{ + XEvent event; + + memset(&event, 0, sizeof(event)); + event.xclient.type = ClientMessage; + event.xclient.window = xwin->id; + event.xclient.message_type = type; + event.xclient.format = 32; + event.xclient.data.l[0] = foo; + event.xclient.data.l[1] = CurrentTime; /* XXX TODO: is CurrentTime actually correct to use for this purpose? */ + + XSendEvent(vwm->display, xwin->id, False, 0, &event); +} + + +/* look up the X window in the global xwindows list (includes unmanaged windows like override_redirect/popup menus) */ +vwm_xwindow_t * vwm_xwin_lookup(vwm_t *vwm, Window win) +{ + vwm_xwindow_t *tmp, *xwin = NULL; + + list_for_each_entry(tmp, &vwm->xwindows, xwindows) { + if (tmp->id == win) { + xwin = tmp; + break; + } + } + + return xwin; +} + + +/* determine if a window is mapped (vwm-mapped) according to the current context */ +int vwm_xwin_is_mapped(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + int ret = 0; + + if (!xwin->mapped) return 0; + + if (xwin->managed) { + switch (vwm->focused_context) { + case VWM_CONTEXT_SHELF: + if (vwm->focused_shelf == xwin->managed) ret = xwin->mapped; + break; + + case VWM_CONTEXT_DESKTOP: + if (vwm->focused_desktop == xwin->managed->desktop && !xwin->managed->shelved) ret = xwin->mapped; + break; + + default: + VWM_BUG("Unsupported context"); + break; + } + } else { /* unmanaged xwins like popup dialogs when mapped are always visible */ + ret = 1; + } + + /* annoyingly, Xorg stops delivering VisibilityNotify events for redirected windows, so we don't conveniently know if a window is obscured or not :( */ + /* I could maintain my own data structure for answering this question, but that's pretty annoying when Xorg already has that knowledge. */ + + return ret; +} + + +/* creates and potentially manages a new window (called in response to CreateNotify events, and during startup for all existing windows) */ +/* if the window is already mapped and not an override_redirect window, it becomes managed here. */ +vwm_xwindow_t * vwm_xwin_create(vwm_t *vwm, Window win, vwm_grab_mode_t grabbed) +{ + XWindowAttributes attrs; + vwm_xwindow_t *xwin = NULL; + + VWM_TRACE("creating %#x", (unsigned int)win); + + /* prevent races */ + if (!grabbed) { + XGrabServer(vwm->display); + XSync(vwm->display, False); + } + + /* verify the window still exists */ + if (!XGetWindowAttributes(vwm->display, win, &attrs)) goto _out_grabbed; + + /* don't create InputOnly windows */ + if (attrs.class == InputOnly) goto _out_grabbed; + + if (!(xwin = (vwm_xwindow_t *)malloc(sizeof(vwm_xwindow_t)))) { + VWM_PERROR("Failed to allocate xwin"); + goto _out_grabbed; + } + + xwin->id = win; + xwin->attrs = attrs; + xwin->managed = NULL; + xwin->name = NULL; + XFetchName(vwm->display, win, &xwin->name); + + xwin->monitor = NULL; + xwin->overlay.width = xwin->overlay.height = xwin->overlay.phase = 0; + xwin->overlay.gen_last_composed = -1; + + /* This is so we get the PropertyNotify event and can get the pid when it's set post-create, + * with my _NET_WM_PID patch the property is immediately available */ + XSelectInput(vwm->display, win, PropertyChangeMask); + + /* we must track the mapped-by-client state of the window independent of managed vs. unmanaged because + * in the case of override_redirect windows they may be unmapped (invisible) or mapped (visible) like menus without being managed. + * otherwise we could just use !xwin.managed to indicate unmapped, which is more vwm2-like, but insufficient when compositing. */ + xwin->mapped = (attrs.map_state != IsUnmapped); + + vwm_overlay_xwin_create(vwm, xwin); + vwm_composite_xwin_create(vwm, xwin); + + list_add_tail(&xwin->xwindows, &vwm->xwindows); /* created windows are always placed on the top of the stacking order */ + +#ifdef HONOR_OVERRIDE_REDIRECT + if (!attrs.override_redirect && xwin->mapped) vwm_win_manage_xwin(vwm, xwin); +#else + if (xwin->mapped) vwm_win_manage_xwin(vwm, xwin); +#endif +_out_grabbed: + if (!grabbed) XUngrabServer(vwm->display); + + return xwin; +} + + +/* destroy a window, called in response to DestroyNotify events */ +/* if the window is also managed it will be unmanaged first */ +void vwm_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + XGrabServer(vwm->display); + XSync(vwm->display, False); + + if (xwin->managed) vwm_win_unmanage(vwm, xwin->managed); + + list_del(&xwin->xwindows); + + if (xwin->name) XFree(xwin->name); + + vwm_overlay_xwin_destroy(vwm, xwin); + vwm_composite_xwin_destroy(vwm, xwin); + + free(xwin); + + XUngrabServer(vwm->display); +} + + +/* maintain the stack-ordered xwindows list, when new_above is != None xwin is to be placed above new_above, when == None xwin goes to the bottom. */ +void vwm_xwin_restack(vwm_t *vwm, vwm_xwindow_t *xwin, Window new_above) +{ + Window old_above; +#ifdef TRACE + vwm_xwindow_t *tmp; + fprintf(stderr, "restack of %#x new_above=%#x\n", (unsigned int)xwin->id, (unsigned int)new_above); + fprintf(stderr, "restack pre:"); + list_for_each_entry(tmp, &vwm->xwindows, xwindows) { + fprintf(stderr, " %#x", (unsigned int)tmp->id); + } + fprintf(stderr, "\n"); +#endif + if (xwin->xwindows.prev != &vwm->xwindows) { + old_above = list_entry(xwin->xwindows.prev, vwm_xwindow_t, xwindows)->id; + } else { + old_above = None; + } + + if (old_above != new_above) { + vwm_xwindow_t *new; + + if (new_above == None) { /* to the bottom of the stack, so just above the &xwindows head */ + list_move(&xwin->xwindows, &vwm->xwindows); + } else if ((new = vwm_xwin_lookup(vwm, new_above))) { /* to just above new_above */ + list_move(&xwin->xwindows, &new->xwindows); + } + } +#ifdef TRACE + fprintf(stderr, "restack post:"); + list_for_each_entry(tmp, &vwm->xwindows, xwindows) { + fprintf(stderr, " %#x", (unsigned int)tmp->id); + } + fprintf(stderr, "\n\n"); +#endif +} + +/* create xwindows for all existing windows (for startup) */ +int vwm_xwin_create_existing(vwm_t *vwm) +{ + Window root, parent; + Window *children = NULL; + unsigned int n_children, i; + + /* TODO FIXME I don't think this is right anymore, not since we went compositing and split managed vs. bare xwindows... */ + XGrabServer(vwm->display); + XSync(vwm->display, False); + XQueryTree(vwm->display, VWM_XROOT(vwm), &root, &parent, &children, &n_children); + + for (i = 0; i < n_children; i++) { + if (children[i] == None) continue; + + if ((vwm_xwin_create(vwm, children[i], VWM_GRABBED) == NULL)) goto _fail_grabbed; + } + + XUngrabServer(vwm->display); + + if (children) XFree(children); + + return 1; + +_fail_grabbed: + XUngrabServer(vwm->display); + + if (children) XFree(children); + + return 0; +} diff --git a/src/xwindow.h b/src/xwindow.h new file mode 100644 index 0000000..f3be681 --- /dev/null +++ b/src/xwindow.h @@ -0,0 +1,52 @@ +#ifndef _XWIN_H +#define _XWIN_H + +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xrender.h> +#include <X11/Xlib.h> + +#include "libvmon/vmon.h" +#include "list.h" +#include "overlay.h" + +typedef struct _vwm_t vwm_t; +typedef struct _vwm_window_t vwm_window_t; + +/* every window gets this, even non-managed ones. For compositing vwm must track everything visible, even popup menus. */ +typedef struct _vwm_xwindow_t { + list_head_t xwindows; /* global list of all windows kept in X stacking order */ + + Window id; /* X Window backing this instance */ + XWindowAttributes attrs; /* X window's current attributes, kept up-to-date in handling of ConfigureNotify events */ + Damage damage; /* X damage object associated with the window (for compositing) */ + Picture picture; /* X picture object representing the window (for compositing) */ + Pixmap pixmap; /* X pixmap object representing the window (for compositing) */ + + vmon_proc_t *monitor; /* vmon process monitor handle, may be NULL if for example the X client doesn't supply a PID */ + vwm_overlay_t overlay; /* monitoring overlay state */ + + char *name; /* client name */ + unsigned int mapped:1; /* is the window currently mapped (by client) */ + unsigned int occluded:1; /* is the window occluded entirely by another window? (used and valid only during paint_all()) */ + /* if only Xorg could send VisibilityNotify events when requested for redirected windows :( */ + vwm_window_t *managed; /* is the window "managed"? NULL or this points to the managed context of the window */ +} vwm_xwindow_t; + +/* creates and potentially manages a new window (called in response to CreateNotify events, and during startup for all existing windows) */ +/* if the window is already mapped and not an override_redirect window, it becomes managed here. */ +typedef enum _vwm_grab_mode_t { + VWM_NOT_GRABBED = 0, + VWM_GRABBED +} vwm_grab_mode_t; + +void vwm_xwin_message(vwm_t *vwm, vwm_xwindow_t *xwin, Atom type, long foo); +vwm_xwindow_t * vwm_xwin_lookup(vwm_t *vwm, Window win); +int vwm_xwin_is_mapped(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_xwin_monitor(vwm_t *vwm, vwm_xwindow_t *xwin); +vwm_xwindow_t * vwm_xwin_create(vwm_t *vwm, Window win, vwm_grab_mode_t grabbed); +void vwm_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_xwin_restack(vwm_t *vwm, vwm_xwindow_t *xwin, Window new_above); +int vwm_xwin_create_existing(vwm_t *vwm); + + +#endif @@ -1,3285 +0,0 @@ -/* - * \/\/\ - * - * Copyright (C) 2012-2015 Vito Caputo - <vcaputo@gnugeneration.com> - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/* The compositing code is heavily influenced by Keith Packard's xcompmgr. - */ - -#include <X11/Xlib.h> -#include <X11/keysym.h> -#include <X11/cursorfont.h> -#include <X11/Xatom.h> -#include <X11/extensions/sync.h> /* SYNC extension, enables us to give vwm the highest X client priority, helps keep vwm responsive at all times */ -#include <X11/extensions/Xinerama.h> /* XINERAMA extension, facilitates easy multihead awareness */ -#include <X11/extensions/Xrandr.h> /* RANDR extension, facilitates display configuration change awareness */ -#include <X11/extensions/Xdamage.h> /* Damage extension, enables receipt of damage events, reports visible regions needing updating (compositing) */ -#include <X11/extensions/Xrender.h> /* Render extension, enables use of alpha channels and accelerated rendering of surfaces having alpha (compositing) */ -#include <X11/extensions/Xcomposite.h> /* Composite extension, enables off-screen redirection of window rendering (compositing) */ -#include <X11/extensions/Xfixes.h> /* XFixes extension exposes things like regions (compositing) */ -#include <unistd.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdarg.h> -#include <string.h> -#include <inttypes.h> -#include <values.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <poll.h> -#include "libvmon/vmon.h" -#include "vwm.h" - -#define WINDOW_BORDER_WIDTH 1 -#define CONSOLE_WM_CLASS "VWMConsoleXTerm" /* the class we specify to the "console" xterm */ -#define CONSOLE_SESSION_STRING "_vwm_console.$DISPLAY" /* the unique console screen session identifier */ - -#define WM_GRAB_MODIFIER Mod1Mask /* the modifier for invoking vwm's controls */ - /* Mod4Mask would be the windows key instead of Alt, but there's an assumption - * in the code that grabs are being activated by Alt which complicates changing it, - * search for XGetModifierMapping to see where, feel free to fix it. Or you can - * just hack the code to expect the appropriate key instead of Alt, I didn't see the - * value of making it modifier mapping aware if it's always Alt for me. */ -#define LAUNCHED_RELATIVE_PRIORITY 10 /* the wm priority plus this is used as the priority of launched processes */ -#define HONOR_OVERRIDE_REDIRECT /* search for HONOR_OVERRIDE_REDIRECT for understanding */ - - -#define OVERLAY_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */ -#define OVERLAY_MASK_FORMAT PictStandardA8 -#define OVERLAY_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1" -#define OVERLAY_ROW_HEIGHT 15 /* this should always be larger than the font height */ -#define OVERLAY_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */ -#define OVERLAY_GRAPH_MIN_HEIGHT (4 * OVERLAY_ROW_HEIGHT) -#define OVERLAY_ISTHREAD_ARGV "~" /* use this string to mark threads in the argv field */ -#define OVERLAY_NOCOMM_ARGV "#missed it!" /* use this string to substitute the command when missing in argv field */ - - -typedef enum _vwm_context_focus_t { - VWM_CONTEXT_FOCUS_OTHER = 0, /* focus the other context relative to the current one */ - VWM_CONTEXT_FOCUS_DESKTOP, /* focus the desktop context */ - VWM_CONTEXT_FOCUS_SHELF /* focus the shelf context */ -} vwm_context_focus_t; - -typedef enum _vwm_compositing_mode_t { - VWM_COMPOSITING_OFF = 0, /* non-composited, no redirected windows, most efficient */ - VWM_COMPOSITING_MONITORS = 1 /* composited process monitoring overlays, slower but really useful. */ -} vwm_compositing_mode_t; - -typedef XineramaScreenInfo vwm_screen_t; /* conveniently reuse the xinerama type for describing screens */ - -static LIST_HEAD(desktops); /* global list of all (virtual) desktops in spatial created-in order */ -static LIST_HEAD(desktops_mru); /* global list of all (virtual) desktops in MRU order */ -static LIST_HEAD(windows_mru); /* global list of all managed windows kept in MRU order */ -static LIST_HEAD(xwindows); /* global list of all xwindows kept in the X server stacking order */ -static vwm_window_t *console = NULL; /* the console window */ -static vwm_window_t *focused_origin = NULL; /* the originating window in a grabbed operation/transaction */ -static vwm_desktop_t *focused_desktop = NULL; /* currently focused (virtual) desktop */ -static vwm_window_t *focused_shelf = NULL; /* currently focused shelved window */ -static vwm_context_focus_t focused_context = VWM_CONTEXT_FOCUS_DESKTOP; /* currently focused context */ -static vwm_compositing_mode_t compositing_mode = VWM_COMPOSITING_OFF; /* current compositing mode */ -static int key_is_grabbed = 0; /* flag for tracking keyboard grab state */ -static int priority; /* scheduling priority of the vwm process, launcher nices relative to this */ -static unsigned long fence_mask = 0; /* global mask state for vwm_win_focus_next(... VWM_FENCE_MASKED_VIOLATE), - * if you use vwm on enough screens to overflow this, pics or it didn't happen. */ - - /* Uninteresting stuff */ -static Display *display; -static Colormap cmap; - -#define color(_sym, _str) \ -static XColor _sym ## _color; -#include "colors.def" -#undef color - -static int screen_num; -static GC gc; -static Atom wm_delete_atom; -static Atom wm_protocols_atom; -static Atom wm_pid_atom; -static int sync_event, sync_error; - - /* Xinerama */ -static int xinerama_event, xinerama_error; -static XineramaScreenInfo *xinerama_screens = NULL; -static int xinerama_screens_cnt; -static int randr_event, randr_error; - - /* Compositing */ -static int composite_event, composite_error, composite_opcode; -static int damage_event, damage_error; -static XserverRegion combined_damage = None; -static Picture root_picture = None, root_buffer = None; /* compositing gets double buffered */ -static XWindowAttributes root_attrs; -static XRenderPictureAttributes pa_inferiors = { .subwindow_mode = IncludeInferiors }; - - /* Compositing / Overlays */ -static XFontStruct *overlay_font; -static GC text_gc; -static XRenderPictureAttributes pa_repeat = { .repeat = 1 }; -static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 }; -static Picture overlay_shadow_fill, /* TODO: the repetition here smells like an XMacro waiting to happen */ - overlay_text_fill, - overlay_bg_fill, - overlay_snowflakes_text_fill, - overlay_grapha_fill, - overlay_graphb_fill, - overlay_finish_fill; -static XRenderColor overlay_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff }, - overlay_shadow_color = { 0x0000, 0x0000, 0x0000, 0x8800}, - overlay_bg_color = { 0x0, 0x1000, 0x0, 0x9000}, - overlay_div_color = { 0x2000, 0x3000, 0x2000, 0x9000}, - overlay_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 }, - overlay_trans_color = {0x00, 0x00, 0x00, 0x00}, - overlay_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */ - overlay_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */ - - /* libvmon */ -static struct timeval maybe_sample, last_sample, this_sample = {0,0}; -static typeof(((vmon_sys_stat_t *)0)->user) last_user_cpu; -static typeof(((vmon_sys_stat_t *)0)->system) last_system_cpu; -static unsigned long long last_total, this_total, total_delta; -static unsigned long long last_idle, last_iowait, idle_delta, iowait_delta; -static vmon_t vmon; - -static float sampling_intervals[] = { - 1, /* ~1Hz */ - .1, /* ~10Hz */ - .05, /* ~20Hz */ - .025, /* ~40Hz */ - .01666}; /* ~60Hz */ -static int prev_sampling_interval = 1, sampling_interval = 1; - - /* some needed prototypes */ -static vwm_xwindow_t * vwm_xwin_lookup(Window win); -static inline int vwm_xwin_is_mapped(vwm_xwindow_t *xwin); -static void vwm_comp_damage_add(XserverRegion damage); -static vwm_xwindow_t * vwm_win_unmanage(vwm_window_t *vwin); -static vwm_window_t * vwm_win_manage_xwin(vwm_xwindow_t *xwin); -static void vwm_win_unmap(vwm_window_t *vwin); -static void vwm_win_map(vwm_window_t *vwin); -static void vwm_win_focus(vwm_window_t *vwin); -static void vwm_keypressed(Window win, XEvent *keypress); - -#define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) -#define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) - - - /* libvmon integration, warning: this gets a little crazy especially in the rendering. */ - -/* space we need for every process being monitored */ -typedef struct _vwm_perproc_ctxt_t { - typeof(vmon.generation) generation; - typeof(((vmon_proc_stat_t *)0)->utime) last_utime; - typeof(((vmon_proc_stat_t *)0)->stime) last_stime; - typeof(((vmon_proc_stat_t *)0)->utime) utime_delta; - typeof(((vmon_proc_stat_t *)0)->stime) stime_delta; -} vwm_perproc_ctxt_t; - - -/* moves what's below a given row up above it if specified, the row becoming discarded */ -static void snowflake_row(vwm_xwindow_t *xwin, Picture pic, int copy, int row) -{ - VWM_TRACE("pid=%i xwin=%p row=%i copy=%i heirarhcy_end=%i", xwin->monitor->pid, xwin, row, copy, xwin->overlay.heirarchy_end); - - if (copy) { - /* copy row to tmp */ - XRenderComposite(display, PictOpSrc, pic, None, xwin->overlay.tmp_picture, - 0, row * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, 0, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } - - /* shift up */ - XRenderChangePicture(display, pic, CPRepeat, &pa_no_repeat); - XRenderComposite(display, PictOpSrc, pic, None, pic, - 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, row * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, (1 + xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ - XRenderChangePicture(display, pic, CPRepeat, &pa_repeat); - - if (copy) { - /* copy tmp to top of snowflakes */ - XRenderComposite(display, PictOpSrc, xwin->overlay.tmp_picture, None, pic, - 0, 0, /* src */ - 0, 0, /* mask */ - 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } else { - /* clear the snowflake row */ - XRenderFillRectangle(display, PictOpSrc, pic, &overlay_trans_color, - 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } -} - -/* XXX TODO libvmon automagic children following races with explicit X client pid monitoring with different outcomes, it should be irrelevant which wins, - * currently the only visible difference is the snowflakes gap (heirarchy_end) varies, which is why I haven't bothered to fix it, I barely even notice. - */ - -/* shifts what's below a given row down a row, and clears the row, preparing it for populating */ -static void allocate_row(vwm_xwindow_t *xwin, Picture pic, int row) -{ - VWM_TRACE("pid=%i xwin=%p row=%i", xwin->monitor->pid, xwin, row); - - /* shift everything below the row down */ - XRenderComposite(display, PictOpSrc, pic, None, pic, - 0, row * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, xwin->overlay.height - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ - /* fill the space created with transparent pixels */ - XRenderFillRectangle(display, PictOpSrc, pic, &overlay_trans_color, - 0, row * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ -} - - -/* shadow a row from the text layer in the shadow layer */ -static void shadow_row(vwm_xwindow_t *xwin, int row) -{ - /* the current technique for creating the shadow is to simply render the text at +1/-1 pixel offsets on both axis in translucent black */ - XRenderComposite(display, PictOpSrc, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - -1, row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 0, -1 + row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 1, row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 0, 1 + row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); -} - - -/* simple helper to map the vmon per-proc argv array into an XTextItem array, deals with threads vs. processes and the possibility of the comm field not getting read in before the process exited... */ -static void argv2xtext(vmon_proc_t *proc, XTextItem *items, int *nr_items) /* XXX TODO: reallocate items when too small... */ -{ - int i; - int nr = 0; - - if (proc->is_thread) { /* stick the thread marker at the start of threads */ - items[0].nchars = sizeof(OVERLAY_ISTHREAD_ARGV) - 1; - items[0].chars = OVERLAY_ISTHREAD_ARGV; - items[0].delta = 4; - items[0].font = None; - nr++; - } - - if (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len) { - items[nr].nchars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1; - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array; - } else { - /* sometimes a process is so ephemeral we don't manage to sample its comm, XXX TODO: we always have a pid, stringify it? */ - items[nr].nchars = sizeof(OVERLAY_NOCOMM_ARGV) - 1; - items[nr].chars = OVERLAY_NOCOMM_ARGV; - } - items[nr].delta = 4; - items[nr].font = None; - nr++; - - for (i = 1; i < ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argc; nr++, i++) { - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]; - items[nr].nchars = strlen(((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]); /* TODO: libvmon should inform us of the length */ - items[nr].delta = 4; - items[nr].font = None; - } - - (*nr_items) = nr; -} - - -/* helper for counting number of existing descendants subtrees */ -static int count_rows(vmon_proc_t *proc) { - int count = 1; /* XXX maybe suppress proc->is_new? */ - vmon_proc_t *child; - - if (!proc->is_thread) { - list_for_each_entry(child, &proc->threads, threads) { - count += count_rows(child); - } - } - - list_for_each_entry(child, &proc->children, siblings) { - count += count_rows(child); - } - - return count; -} - - -/* recursive draw function for the consolidated version of the overlay rendering which also implements snowflakes */ -static void draw_overlay(vwm_xwindow_t *xwin, vmon_proc_t *proc, int *depth, int *row) -{ - vmon_proc_t *child; - vwm_perproc_ctxt_t *proc_ctxt = proc->foo; - vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; - - /* graph variables */ - int a_height, b_height; - double utime_delta, stime_delta; - - /* text variables */ - char str[256]; - int str_len; - XTextItem items[1024]; /* XXX TODO: dynamically allocate this and just keep it at the high water mark.. create a struct to encapsulate this, nr_items, and alloc_items... */ - int nr_items; - int direction, ascent, descent; - XCharStruct charstruct; - - if ((*row)) { /* except row 0 (Idle/IOWait graph), handle any stale and new processes/threads */ - if (proc->is_stale) { - /* what to do when a process (subtree) has gone away */ - static int in_stale = 0; - int in_stale_entrypoint = 0; - - /* I snowflake the stale processes from the leaves up for a more intuitive snowflake order... - * (I expect the command at the root of the subtree to appear at the top of the snowflakes...) */ - /* This does require that I do a separate forward recursion to determine the number of rows - * so I can correctly snowflake in reverse */ - if (!in_stale) { - VWM_TRACE("entered stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); - in_stale_entrypoint = in_stale = 1; - (*row) += count_rows(proc) - 1; - } - - (*depth)++; - list_for_each_entry_prev(child, &proc->children, siblings) { - draw_overlay(xwin, child, depth, row); - (*row)--; - } - - if (!proc->is_thread) { - list_for_each_entry_prev(child, &proc->threads, threads) { - draw_overlay(xwin, child, depth, row); - (*row)--; - } - } - (*depth)--; - - VWM_TRACE("%i (%.*s) is stale @ depth %i row %i is_thread=%i", proc->pid, - ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1, - ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array, - (*depth), (*row), proc->is_thread); - - /* stamp the graphs with the finish line */ - XRenderComposite(display, PictOpSrc, overlay_finish_fill, None, xwin->overlay.grapha_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, OVERLAY_ROW_HEIGHT - 1); - XRenderComposite(display, PictOpSrc, overlay_finish_fill, None, xwin->overlay.graphb_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, OVERLAY_ROW_HEIGHT - 1); - - /* extract the row from the various layers */ - snowflake_row(xwin, xwin->overlay.grapha_picture, 1, (*row)); - snowflake_row(xwin, xwin->overlay.graphb_picture, 1, (*row)); - snowflake_row(xwin, xwin->overlay.text_picture, 0, (*row)); - snowflake_row(xwin, xwin->overlay.shadow_picture, 0, (*row)); - xwin->overlay.snowflakes_cnt++; - - /* stamp the name (and whatever else we include) into overlay.text_picture */ - argv2xtext(proc, items, &nr_items); - XDrawText(display, xwin->overlay.text_pixmap, text_gc, - 5, (xwin->overlay.heirarchy_end + 1) * OVERLAY_ROW_HEIGHT - 3,/* dst x, y */ - items, nr_items); - shadow_row(xwin, xwin->overlay.heirarchy_end); - - xwin->overlay.heirarchy_end--; - - if (in_stale_entrypoint) { - VWM_TRACE("exited stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); - in_stale = 0; - } - - return; - } else if (proc->is_new) { - /* what to do when a process has been introduced */ - VWM_TRACE("%i is new", proc->pid); - - allocate_row(xwin, xwin->overlay.grapha_picture, (*row)); - allocate_row(xwin, xwin->overlay.graphb_picture, (*row)); - allocate_row(xwin, xwin->overlay.text_picture, (*row)); - allocate_row(xwin, xwin->overlay.shadow_picture, (*row)); - - xwin->overlay.heirarchy_end++; - } - } - -/* CPU utilization graphs */ - if (!(*row)) { - /* XXX: sortof kludged in IOWait and Idle % @ row 0 */ - stime_delta = iowait_delta; - utime_delta = idle_delta; - } else { - /* use the generation number to avoid recomputing this stuff for callbacks recurring on the same process in the same sample */ - if (proc_ctxt->generation != vmon.generation) { - proc_ctxt->stime_delta = proc_stat->stime - proc_ctxt->last_stime; - proc_ctxt->utime_delta = proc_stat->utime - proc_ctxt->last_utime; - proc_ctxt->last_utime = proc_stat->utime; - proc_ctxt->last_stime = proc_stat->stime; - - proc_ctxt->generation = vmon.generation; - } - - if (proc->is_new) { - /* we need a minimum of two samples before we can compute a delta to plot, - * so we suppress that and instead mark the start of monitoring with an impossible 100% of both graph contexts, a starting line. */ - stime_delta = utime_delta = total_delta; - } else { - stime_delta = proc_ctxt->stime_delta; - utime_delta = proc_ctxt->utime_delta; - } - } - - /* compute the bar heights for this sample */ - a_height = (stime_delta / total_delta * (double)(OVERLAY_ROW_HEIGHT - 1)); /* give up 1 pixel for the div */ - b_height = (utime_delta / total_delta * (double)(OVERLAY_ROW_HEIGHT - 1)); - - /* round up to 1 pixel when the scaled result is a fraction less than 1, - * I want to at least see 1 pixel blips for the slightest cpu utilization */ - if (stime_delta && !a_height) a_height = 1; - if (utime_delta && !b_height) b_height = 1; - - /* draw the two bars for this sample at the current phase in the graphs, note the first is ceiling-based, second floor-based */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_visible_color, - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, a_height); /* dst w, h */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_visible_color, - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT + (OVERLAY_ROW_HEIGHT - b_height) - 1, /* dst x, y */ - 1, b_height); /* dst w, h */ - - if (!(*row)) { - /* here's where the Idle/IOWait row drawing concludes */ - if (compositing_mode) { - snprintf(str, sizeof(str), "\\/\\/\\ %2iHz %n", (int)(sampling_interval < 0 ? 0 : 1 / sampling_intervals[sampling_interval]), &str_len); - /* TODO: I clear and redraw this row every time, which is unnecessary, small optimization would be to only do so when: - * - overlay resized, and then constrain the clear to the affected width - * - Hz changed - */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - 0, 0, /* dst x, y */ - xwin->attrs.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ - XTextExtents(overlay_font, str, str_len, &direction, &ascent, &descent, &charstruct); - XDrawString(display, xwin->overlay.text_pixmap, text_gc, - xwin->attrs.width - charstruct.width, OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - str, str_len); - shadow_row(xwin, 0); - } - (*row)++; - draw_overlay(xwin, proc, depth, row); - return; - } - -/* process heirarchy text and accompanying per-process details like wchan/pid/state... */ - if (compositing_mode) { /* this stuff can be skipped when monitors aren't visible */ - /* TODO: make the columns interactively configurable @ runtime */ - if (!proc->is_new) { - /* XXX for now always clear the row, this should be capable of being optimized in the future (if the datums driving the text haven't changed...) */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - 0, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ - } - - /* put the process' wchan, state, and PID columns @ the far right */ - if (proc->is_thread || list_empty(&proc->threads)) { /* only threads or non-threaded processes include the wchan and state */ - snprintf(str, sizeof(str), " %.*s %5i %c %n", - proc_stat->wchan.len, - proc_stat->wchan.len == 1 && proc_stat->wchan.array[0] == '0' ? "-" : proc_stat->wchan.array, - proc->pid, - proc_stat->state, - &str_len); - } else { /* we're a process having threads, suppress the wchan and state, as they will be displayed for the thread of same pid */ - snprintf(str, sizeof(str), " %5i %n", proc->pid, &str_len); - } - - XTextExtents(overlay_font, str, str_len, &direction, &ascent, &descent, &charstruct); - - /* the process' comm label indented according to depth, followed with their respective argv's */ - argv2xtext(proc, items, &nr_items); - XDrawText(display, xwin->overlay.text_pixmap, text_gc, - (*depth) * (OVERLAY_ROW_HEIGHT / 2), ((*row) + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - items, nr_items); - - /* ensure the area for the rest of the stuff is cleared, we don't put much text into thread rows so skip it for those. */ - if (!proc->is_thread) { - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - xwin->attrs.width - charstruct.width, (*row) * OVERLAY_ROW_HEIGHT, /* dst x,y */ - xwin->overlay.width - (xwin->attrs.width - charstruct.width), OVERLAY_ROW_HEIGHT); /* dst w,h */ - } - - XDrawString(display, xwin->overlay.text_pixmap, text_gc, - xwin->attrs.width - charstruct.width, ((*row) + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - str, str_len); - - /* only if this process isn't the root process @ the window shall we consider all relational drawing conditions */ - if (proc != xwin->monitor) { - vmon_proc_t *ancestor, *sibling, *last_sibling = NULL; - struct list_head *rem; - int needs_tee = 0; - int bar_x = 0, bar_y = 0; - int sub; - - /* XXX: everything done in this code block only dirties _this_ process' row in the rendered overlay output */ - - /* walk up the ancestors until reaching xwin->monitor, any ancestors we encounter which have more siblings we draw a vertical bar for */ - /* this draws the |'s in something like: | | | | comm */ - for (sub = 1, ancestor = proc->parent; ancestor && ancestor != xwin->monitor; ancestor = ancestor->parent) { - sub++; - bar_x = ((*depth) - sub) * (OVERLAY_ROW_HEIGHT / 2) + 4; - bar_y = ((*row) + 1) * OVERLAY_ROW_HEIGHT; - - /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ - for (rem = ancestor->siblings.next; rem != &ancestor->parent->children; rem = rem->next) { - if (!(list_entry(rem, vmon_proc_t, siblings)->is_stale)) { - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y); /* dst x2, y2 (vertical line) */ - break; /* stop looking for more siblings at this ancestor when we find one that isn't stale */ - } - } - } - - /* determine if _any_ of our siblings have children requiring us to draw a tee immediately before our comm string. - * The only sibling which doesn't cause this to happen is the last one in the children list, if it has children it has no impact on its remaining - * siblings, as there are none. - * - * This draws the + in something like: | | | | +comm - */ - - /* find the last sibling (this has to be done due to the potential for stale siblings at the tail, and we'd rather not repeatedly check for it) */ - list_for_each_entry(sibling, &proc->parent->children, siblings) { - if (!sibling->is_stale) last_sibling = sibling; - } - - /* now look for siblings with non-stale children to determine if a tee is needed, ignoring the last sibling */ - list_for_each_entry(sibling, &proc->parent->children, siblings) { - /* skip stale siblings, they aren't interesting as they're invisible, and the last sibling has no bearing on wether we tee or not. */ - if (sibling->is_stale || sibling == last_sibling) continue; - - /* if any of the other siblings have children which are not stale, put a tee in front of our name, but ignore stale children */ - list_for_each_entry(child, &sibling->children, siblings) { - if (!child->is_stale) { - needs_tee = 1; - break; - } - } - - /* if we still don't think we need a tee, check if there are threads */ - if (!needs_tee) { - list_for_each_entry(child, &sibling->threads, threads) { - if (!child->is_stale) { - needs_tee = 1; - break; - } - } - } - - /* found a tee is necessary, all that's left is to determine if the tee is a corner and draw it accordingly, stopping the search. */ - if (needs_tee) { - bar_x = ((*depth) - 1) * (OVERLAY_ROW_HEIGHT / 2) + 4; - - /* if we're the last sibling, corner the tee by shortening the vbar */ - if (proc == last_sibling) { - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y - 4); /* dst x2, y2 (vertical bar) */ - } else { - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y); /* dst x2, y2 (vertical bar) */ - } - - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - 4, /* dst x1, y1 */ - bar_x + 2, bar_y - 4); /* dst x2, y2 (horizontal bar) */ - - /* terminate the outer sibling loop upon drawing the tee... */ - break; - } - } - } - shadow_row(xwin, (*row)); - } - - (*row)++; - - /* recur any threads first, then any children processes */ - (*depth)++; - if (!proc->is_thread) { /* XXX: the threads member serves as the list head only when not a thread */ - list_for_each_entry(child, &proc->threads, threads) { - draw_overlay(xwin, child, depth, row); - } - } - - list_for_each_entry(child, &proc->children, siblings) { - draw_overlay(xwin, child, depth, row); - } - (*depth)--; -} - - -/* consolidated version of overlay text and graph rendering, makes snowflakes integration cleaner, this always gets called regadless of the overlays mode */ -static void maintain_overlay(vwm_xwindow_t *xwin) -{ - int row = 0, depth = 0; - - if (!xwin->monitor || !xwin->monitor->stores[VMON_STORE_PROC_STAT]) return; - - /* TODO: - * I side effect of responding to window resizes in this function is there's a latency proportional to the current sample_interval. - * Something to fix is to resize the overlays when the window resizes. - * However, simply resizing the overlays is insufficient. Their contents need to be redrawn in the new dimensions, this is where it - * gets annoying. The current maintain/draw_overlay makes assumptions about being run from the periodic vmon per-process callback. - * There needs to be a redraw mode added where draw_overlay is just reconstructing the current state, which requires that we suppress - * the phase advance and in maintain_overlay() and just enter draw_overlay() to redraw everything for the same generation. - * So this probably requires some tweaking of draw_overlay() as well as maintain_overlay(). I want to be able tocall mainta_overlays() - * from anywhere, and have it detect if it's being called on the same generation or if the generation has advanced. - * For now, the monitors will just be a little latent in window resizes which is pretty harmless artifact. - */ - - /* if the window is larger than the overlays currently are, enlarge them */ - if (xwin->attrs.width > xwin->overlay.width || xwin->attrs.height > xwin->overlay.height) { - vwm_overlay_t existing; - Pixmap pixmap; - - existing = xwin->overlay; - - xwin->overlay.width = MAX(xwin->overlay.width, MAX(xwin->attrs.width, OVERLAY_GRAPH_MIN_WIDTH)); - xwin->overlay.height = MAX(xwin->overlay.height, MAX(xwin->attrs.height, OVERLAY_GRAPH_MIN_HEIGHT)); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.grapha_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); - XFreePixmap(display, pixmap); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.graphb_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); - XFreePixmap(display, pixmap); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, OVERLAY_ROW_HEIGHT, OVERLAY_MASK_DEPTH); - xwin->overlay.tmp_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), 0, NULL); - XFreePixmap(display, pixmap); - - /* keep the text_pixmap reference around for XDrawText usage */ - xwin->overlay.text_pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.text_picture = XRenderCreatePicture(display, xwin->overlay.text_pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), 0, NULL); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.shadow_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), 0, NULL); - XFreePixmap(display, pixmap); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.shadow_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, 32); - xwin->overlay.picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, PictStandardARGB32), 0, NULL); - XFreePixmap(display, pixmap); - - if (existing.width) { - /* XXX: note the graph pictures are copied from their current phase in the x dimension */ - XRenderComposite(display, PictOpSrc, existing.grapha_picture, None, xwin->overlay.grapha_picture, - existing.phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.graphb_picture, None, xwin->overlay.graphb_picture, - existing.phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.text_picture, None, xwin->overlay.text_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.shadow_picture, None, xwin->overlay.shadow_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.picture, None, xwin->overlay.picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - xwin->overlay.phase = 0; /* having unrolled the existing graph[ab] pictures into the larger ones, phase is reset to 0 */ - XRenderFreePicture(display, existing.grapha_picture); - XRenderFreePicture(display, existing.graphb_picture); - XRenderFreePicture(display, existing.tmp_picture); - XRenderFreePicture(display, existing.text_picture); - XFreePixmap(display, existing.text_pixmap); - XRenderFreePicture(display, existing.shadow_picture); - XRenderFreePicture(display, existing.picture); - } - } - - xwin->overlay.phase += (xwin->overlay.width - 1); /* simply change this to .phase++ to scroll the other direction */ - xwin->overlay.phase %= xwin->overlay.width; - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); - - /* recursively draw the monitored processes to the overlay */ - draw_overlay(xwin, xwin->monitor, &depth, &row); -} - - -/* return the composed height of the overlay */ -static int overlay_composed_height(vwm_xwindow_t *xwin) -{ - int snowflakes = xwin->overlay.snowflakes_cnt ? 1 + xwin->overlay.snowflakes_cnt : 0; /* don't include the separator row if there are no snowflakes */ - return MIN((xwin->overlay.heirarchy_end + snowflakes) * OVERLAY_ROW_HEIGHT, xwin->attrs.height); -} - - -/* this composes the maintained overlay into the window's overlay picture, this gets called from paint_all() on every repaint of xwin */ -/* we noop the call if the gen_last_composed and monitor->proc.generation numbers match, indicating there's nothing new to compose. */ -static void compose_overlay(vwm_xwindow_t *xwin) -{ - XserverRegion region; - XRectangle damage; - int height; - - if (!xwin->overlay.width) return; /* prevent winning race with maintain_overlay() and using an unready overlay... */ - - if (xwin->overlay.gen_last_composed == xwin->monitor->generation) return; /* noop if no sampling occurred since last compose */ - xwin->overlay.gen_last_composed = xwin->monitor->generation; /* remember this generation */ - - //VWM_TRACE("composing %p", xwin); - - height = overlay_composed_height(xwin); - - /* fill the overlay picture with the background */ - XRenderComposite(display, PictOpSrc, overlay_bg_fill, None, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, height); - - /* draw the graphs into the overlay through the stencils being maintained by the sample callbacks */ - XRenderComposite(display, PictOpOver, overlay_grapha_fill, xwin->overlay.grapha_picture, xwin->overlay.picture, - 0, 0, - xwin->overlay.phase, 0, - 0, 0, - xwin->attrs.width, height); - XRenderComposite(display, PictOpOver, overlay_graphb_fill, xwin->overlay.graphb_picture, xwin->overlay.picture, - 0, 0, - xwin->overlay.phase, 0, - 0, 0, - xwin->attrs.width, height); - - /* draw the shadow into the overlay picture using a translucent black source drawn through the shadow mask */ - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.shadow_picture, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, height); - - /* render overlay text into the overlay picture using a white source drawn through the overlay text as a mask, on top of everything */ - XRenderComposite(display, PictOpOver, overlay_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); - - XRenderComposite(display, PictOpOver, overlay_snowflakes_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, - 0, 0, - 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, - 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, height - (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); - - /* damage the window to ensure the updated overlay is drawn (TODO: this can be done more selectively/efficiently) */ - damage.x = xwin->attrs.x + xwin->attrs.border_width; - damage.y = xwin->attrs.y + xwin->attrs.border_width; - damage.width = xwin->attrs.width; - damage.height = height; - region = XFixesCreateRegion(display, &damage, 1); - vwm_comp_damage_add(region); -} - - -/* this callback gets invoked at sample time for every process we've explicitly monitored (not autofollowed children/threads) - * It's where we update the cumulative data for all windows, including the graph masks, regardless of their visibility - * It's also where we compose the graphs and text for visible windows into a picture ready for compositing with the window contents */ -static void proc_sample_callback(vmon_t *vmon, vmon_proc_t *proc, vwm_xwindow_t *xwin) -{ - //VWM_TRACE("proc=%p xwin=%p", proc, xwin); - /* render the various always-updated overlays, this is the component we do regardless of the overlays mode and window visibility, - * essentially the incrementally rendered/historic components */ - maintain_overlay(xwin); - - /* render other non-historic things and compose the various layers into an updated overlay */ - /* this leaves everything ready to be composed with the window contents in paint_all() */ - /* paint_all() also enters compose_overlay() to update the overlays on windows which become mapped (desktop switches) */ - if (compositing_mode && vwm_xwin_is_mapped(xwin)) compose_overlay(xwin); -} - - -/* this callback gets invoked at sample time once "per sys" */ -static void sample_callback(vmon_t *_vmon) -{ - vmon_sys_stat_t *sys_stat = vmon.stores[VMON_STORE_SYS_STAT]; - this_total = sys_stat->user + sys_stat->nice + sys_stat->system + - sys_stat->idle + sys_stat->iowait + sys_stat->irq + - sys_stat->softirq + sys_stat->steal + sys_stat->guest; - - total_delta = this_total - last_total; - idle_delta = sys_stat->idle - last_idle; - iowait_delta = sys_stat->iowait - last_iowait; -} - - -/* these callbacks are invoked by the vmon library when process instances become monitored/unmonitored */ -static void vmon_ctor_cb(vmon_t *vmon, vmon_proc_t *proc) -{ - VWM_TRACE("proc->pid=%i", proc->pid); - proc->foo = calloc(1, sizeof(vwm_perproc_ctxt_t)); -} - - -static void vmon_dtor_cb(vmon_t *vmon, vmon_proc_t *proc) -{ - VWM_TRACE("proc->pid=%i", proc->pid); - if (proc->foo) { - free(proc->foo); - proc->foo = NULL; - } -} - - - /* Xinerama/multihead screen functions */ - -/* return what fraction (0.0-1.0) of vwin overlaps with scr */ -static float vwm_screen_overlaps_xwin(const vwm_screen_t *scr, vwm_xwindow_t *xwin) -{ - float pct = 0, xover = 0, yover = 0; - - if (scr->x_org + scr->width < xwin->attrs.x || scr->x_org > xwin->attrs.x + xwin->attrs.width || - scr->y_org + scr->height < xwin->attrs.y || scr->y_org > xwin->attrs.y + xwin->attrs.height) - goto _out; - - /* they overlap, by how much? */ - xover = MIN(scr->x_org + scr->width, xwin->attrs.x + xwin->attrs.width) - MAX(scr->x_org, xwin->attrs.x); - yover = MIN(scr->y_org + scr->height, xwin->attrs.y + xwin->attrs.height) - MAX(scr->y_org, xwin->attrs.y); - - pct = (xover * yover) / (xwin->attrs.width * xwin->attrs.height); -_out: - VWM_TRACE("xover=%f yover=%f width=%i height=%i pct=%.4f", xover, yover, xwin->attrs.width, xwin->attrs.height, pct); - return pct; -} - - -/* return the appropriate screen, don't use the return value across event loops because randr events reallocate the array. */ -typedef enum _vwm_screen_rel_t { - VWM_SCREEN_REL_XWIN, /* return the screen the supplied window most resides in */ - VWM_SCREEN_REL_POINTER, /* return the screen the pointer resides in */ - VWM_SCREEN_REL_TOTAL, /* return the bounding rectangle of all screens as one */ -} vwm_screen_rel_t; - -static const vwm_screen_t * vwm_screen_find(vwm_screen_rel_t rel, ...) -{ - static vwm_screen_t faux; - vwm_screen_t *scr, *best = &faux; /* default to faux as best */ - int i; - - faux.screen_number = 0; - faux.x_org = 0; - faux.y_org = 0; - faux.width = WidthOfScreen(DefaultScreenOfDisplay(display)); - faux.height = HeightOfScreen(DefaultScreenOfDisplay(display)); - - if (!xinerama_screens) goto _out; - -#define for_each_screen(_tmp) \ - for (i = 0, _tmp = xinerama_screens; i < xinerama_screens_cnt; _tmp = &xinerama_screens[++i]) - - switch (rel) { - case VWM_SCREEN_REL_XWIN: { - va_list ap; - vwm_xwindow_t *xwin; - float best_pct = 0, this_pct; - - va_start(ap, rel); - xwin = va_arg(ap, vwm_xwindow_t *); - va_end(ap); - - for_each_screen(scr) { - this_pct = vwm_screen_overlaps_xwin(scr, xwin); - if (this_pct > best_pct) { - best = scr; - best_pct = this_pct; - } - } - break; - } - - case VWM_SCREEN_REL_POINTER: { - int root_x, root_y, win_x, win_y; - unsigned int mask; - Window root, child; - - /* get the pointer coordinates and find which screen it's in */ - XQueryPointer(display, RootWindow(display, screen_num), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask); - - for_each_screen(scr) { - if (root_x >= scr->x_org && root_x < scr->x_org + scr->width && - root_y >= scr->y_org && root_y < scr->y_org + scr->height) { - best = scr; - break; - } - } - break; - } - - case VWM_SCREEN_REL_TOTAL: { - short x1 = MAXSHORT, y1 = MAXSHORT, x2 = MINSHORT, y2 = MINSHORT; - /* find the smallest x_org and y_org, the highest x_org + width and y_org + height, those are the two corners of the total rect */ - for_each_screen(scr) { - if (scr->x_org < x1) x1 = scr->x_org; - if (scr->y_org < y1) y1 = scr->y_org; - if (scr->x_org + scr->width > x2) x2 = scr->x_org + scr->width; - if (scr->y_org + scr->height > y2) y2 = scr->y_org + scr->height; - } - faux.x_org = x1; - faux.y_org = y1; - faux.width = x2 - x1; - faux.height = y2 - y1; - best = &faux; - break; - } - } -_out: - VWM_TRACE("Found Screen #%i: %hix%hi @ %hi,%hi", best->screen_number, best->width, best->height, best->x_org, best->y_org); - - return best; -} - - -/* check if a screen contains any windows (assuming the current desktop) */ -static int vwm_screen_is_empty(const vwm_screen_t *scr) -{ - vwm_xwindow_t *xwin; - int is_empty = 1; - - list_for_each_entry(xwin, &xwindows, xwindows) { - if (!xwin->mapped) continue; - if (!xwin->managed || (xwin->managed->desktop == focused_desktop && !xwin->managed->shelved && !xwin->managed->configuring)) { - /* XXX: it may make more sense to see what %age of the screen is overlapped by windows, and consider it empty if < some % */ - /* This is just seeing if any window is predominantly within the specified screen, the rationale being if you had a focusable - * window on the screen you would have used the keyboard to make windows go there; this function is only used in determining - * wether a new window should go where the pointer is or not. */ - if (vwm_screen_overlaps_xwin(scr, xwin) >= 0.05) { - is_empty = 0; - break; - } - } - } - - return is_empty; -} - - - /* startup logo */ - -/* animated \/\/\ logo done with simple XOR'd lines, a display of the WM being started and ready */ -#define VWM_LOGO_POINTS 6 -static void vwm_draw_logo(void) -{ - int i; - unsigned int width, height, yoff, xoff; - XPoint points[VWM_LOGO_POINTS]; - const vwm_screen_t *scr = vwm_screen_find(VWM_SCREEN_REL_POINTER); - - XGrabServer(display); - - /* use the dimensions of the pointer-containing screen */ - width = scr->width; - height = scr->height; - xoff = scr->x_org; - yoff = scr->y_org + ((float)height * .333); - height /= 3; - - /* the logo gets shrunken vertically until it's essentially a flat line */ - while (height -= 2) { - /* scale and center the points to the screen size */ - for (i = 0; i < VWM_LOGO_POINTS; i++) { - points[i].x = xoff + (i * .2 * (float)width); - points[i].y = (i % 2 * (float)height) + yoff; - } - - XDrawLines(display, RootWindow(display, screen_num), gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); - XFlush(display); - usleep(3333); - XDrawLines(display, RootWindow(display, screen_num), gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); - XFlush(display); - - /* the width is shrunken as well, but only by as much as it is tall */ - yoff++; - width -= 4; - xoff += 2; - } - - XUngrabServer(display); -} - - - /* launching of external processes / X clients */ - -/* launch a child command specified in argv, mode decides if we wait for the child to exit before returning. */ -typedef enum _vwm_launch_mode_t { - VWM_LAUNCH_MODE_FG, - VWM_LAUNCH_MODE_BG, -} vwm_launch_mode_t; - -static void vwm_launch(char **argv, vwm_launch_mode_t mode) -{ - /* XXX: in BG mode I double fork and let init inherit the orphan so I don't have to collect the return status */ - if (mode == VWM_LAUNCH_MODE_FG || !fork()) { - if (!fork()) { - /* child */ - setpriority(PRIO_PROCESS, getpid(), priority + LAUNCHED_RELATIVE_PRIORITY); - execvp(argv[0], argv); - } - if (mode == VWM_LAUNCH_MODE_BG) exit(0); - } - wait(NULL); /* TODO: could wait for the specific pid, particularly in FG mode ... */ -} - - - /* desktop/shelf context handling */ - -/* switch to the desired context if it isn't already the focused one, inform caller if anything happened */ -static int vwm_context_focus(vwm_context_focus_t desired_context) -{ - vwm_context_focus_t entry_context = focused_context; - - switch (focused_context) { - vwm_xwindow_t *xwin; - vwm_window_t *vwin; - - case VWM_CONTEXT_FOCUS_SHELF: - if (desired_context == VWM_CONTEXT_FOCUS_SHELF) break; - - /* desired == DESKTOP && focused == SHELF */ - - VWM_TRACE("unmapping shelf window \"%s\"", focused_shelf->xwindow->name); - vwm_win_unmap(focused_shelf); - XFlush(display); /* for a more responsive feel */ - - /* map the focused desktop, from the top of the stack down */ - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed)) continue; - if (vwin->desktop == focused_desktop && !vwin->shelved) { - VWM_TRACE("Mapping desktop window \"%s\"", xwin->name); - vwm_win_map(vwin); - } - } - - if (focused_desktop->focused_window) { - VWM_TRACE("Focusing \"%s\"", focused_desktop->focused_window->xwindow->name); - XSetInputFocus(display, focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); - } - - focused_context = VWM_CONTEXT_FOCUS_DESKTOP; - break; - - case VWM_CONTEXT_FOCUS_DESKTOP: - /* unmap everything, map the shelf */ - if (desired_context == VWM_CONTEXT_FOCUS_DESKTOP) break; - - /* desired == SHELF && focused == DESKTOP */ - - /* there should be a focused shelf if the shelf contains any windows, we NOOP the switch if the shelf is empty. */ - if (focused_shelf) { - /* unmap everything on the current desktop */ - list_for_each_entry(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed)) continue; - if (vwin->desktop == focused_desktop) { - VWM_TRACE("Unmapping desktop window \"%s\"", xwin->name); - vwm_win_unmap(vwin); - } - } - - XFlush(display); /* for a more responsive feel */ - - VWM_TRACE("Mapping shelf window \"%s\"", focused_shelf->xwindow->name); - vwm_win_map(focused_shelf); - vwm_win_focus(focused_shelf); - - focused_context = VWM_CONTEXT_FOCUS_SHELF; - } - break; - - default: - VWM_BUG("unexpected focused context %x", focused_context); - break; - } - - /* return if the context has been changed, the caller may need to branch differently if nothing happened */ - return (focused_context != entry_context); -} - - - /* virtual desktops */ - -/* make the specified desktop the most recently used one */ -static void vwm_desktop_mru(vwm_desktop_t *desktop) -{ - VWM_TRACE("MRU desktop: %p", desktop); - list_move(&desktop->desktops_mru, &desktops_mru); -} - - -/* focus a virtual desktop */ -/* this switches to the desktop context if necessary, maps and unmaps windows accordingly if necessary */ -static int vwm_desktop_focus(vwm_desktop_t *desktop) -{ - XGrabServer(display); - XSync(display, False); - - /* if the context switched and the focused desktop is the desired desktop there's nothing else to do */ - if ((vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP) && focused_desktop != desktop) || focused_desktop != desktop) { - vwm_xwindow_t *xwin; - vwm_window_t *vwin; - - /* unmap the windows on the currently focused desktop, map those on the newly focused one */ - list_for_each_entry(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed) || vwin->shelved) continue; - if (vwin->desktop == focused_desktop) vwm_win_unmap(vwin); - } - - XFlush(display); - - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed) || vwin->shelved) continue; - if (vwin->desktop == desktop) vwm_win_map(vwin); - } - - focused_desktop = desktop; - } - - /* directly focus the desktop's focused window if there is one, we don't use vwm_win_focus() intentionally XXX */ - if (focused_desktop->focused_window) { - VWM_TRACE("Focusing \"%s\"", focused_desktop->focused_window->xwindow->name); - XSetInputFocus(display, focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); - } - - XUngrabServer(display); - - return 1; -} - - -/* create a virtual desktop */ -static vwm_desktop_t * vwm_desktop_create(char *name) -{ - vwm_desktop_t *desktop; - - desktop = malloc(sizeof(vwm_desktop_t)); - if (desktop == NULL) { - VWM_PERROR("Failed to allocate desktop"); - goto _fail; - } - - desktop->name = name == NULL ? name : strdup(name); - desktop->focused_window = NULL; - - list_add_tail(&desktop->desktops, &desktops); - list_add_tail(&desktop->desktops_mru, &desktops_mru); - - return desktop; - -_fail: - return NULL; -} - - -/* destroy a virtual desktop */ -static void vwm_desktop_destroy(vwm_desktop_t *desktop) -{ - /* silently refuse to destroy a desktop having windows (for now) */ - /* there's _always_ a focused window on a desktop having mapped windows */ - /* also silently refuse to destroy the last desktop (for now) */ - if (desktop->focused_window || (desktop->desktops.next == desktop->desktops.prev)) return; - - /* focus the MRU desktop that isn't this one if we're the focused desktop */ - if (desktop == focused_desktop) { - vwm_desktop_t *next_desktop; - - list_for_each_entry(next_desktop, &desktops_mru, desktops_mru) { - if (next_desktop != desktop) { - vwm_desktop_focus(next_desktop); - break; - } - } - } - - list_del(&desktop->desktops); - list_del(&desktop->desktops_mru); -} - - - /* bare X windows stuff, there's a distinction between bare xwindows and the vwm managed windows */ - -/* send a client message to a window (currently used for WM_DELETE) */ -static void vwm_xwin_message(vwm_xwindow_t *xwin, Atom type, long foo) -{ - XEvent event; - - memset(&event, 0, sizeof(event)); - event.xclient.type = ClientMessage; - event.xclient.window = xwin->id; - event.xclient.message_type = type; - event.xclient.format = 32; - event.xclient.data.l[0] = foo; - event.xclient.data.l[1] = CurrentTime; /* XXX TODO: is CurrentTime actually correct to use for this purpose? */ - - XSendEvent(display, xwin->id, False, 0, &event); -} - - -/* look up the X window in the global xwindows list (includes unmanaged windows like override_redirect/popup menus) */ -static vwm_xwindow_t * vwm_xwin_lookup(Window win) -{ - vwm_xwindow_t *tmp, *xwin = NULL; - - list_for_each_entry(tmp, &xwindows, xwindows) { - if (tmp->id == win) { - xwin = tmp; - break; - } - } - - return xwin; -} - - -/* determine if a window is mapped (vwm-mapped) according to the current context */ -static inline int vwm_xwin_is_mapped(vwm_xwindow_t *xwin) -{ - int ret = 0; - - if (!xwin->mapped) return 0; - - if (xwin->managed) { - switch (focused_context) { - case VWM_CONTEXT_FOCUS_SHELF: - if (focused_shelf == xwin->managed) ret = xwin->mapped; - break; - - case VWM_CONTEXT_FOCUS_DESKTOP: - if (focused_desktop == xwin->managed->desktop && !xwin->managed->shelved) ret = xwin->mapped; - break; - - default: - VWM_BUG("Unsupported context"); - break; - } - } else { /* unmanaged xwins like popup dialogs when mapped are always visible */ - ret = 1; - } - - /* annoyingly, Xorg stops delivering VisibilityNotify events for redirected windows, so we don't conveniently know if a window is obscured or not :( */ - /* I could maintain my own data structure for answering this question, but that's pretty annoying when Xorg already has that knowledge. */ - - return ret; -} - - -/* bind the window to a "namewindowpixmap" and create a picture from it (compositing) */ -void vwm_xwin_bind_namewindow(vwm_xwindow_t *xwin) -{ - xwin->pixmap = XCompositeNameWindowPixmap(display, xwin->id); - xwin->picture = XRenderCreatePicture(display, xwin->pixmap, - XRenderFindVisualFormat(display, xwin->attrs.visual), - CPSubwindowMode, &pa_inferiors); - XFreePixmap(display, xwin->pixmap); -} - - -/* free the window's picture for accessing its redirected contents (compositing) */ -void vwm_xwin_unbind_namewindow(vwm_xwindow_t *xwin) -{ - XRenderFreePicture(display, xwin->picture); -} - - -/* install a monitor on the window if it doesn't already have one and has _NET_WM_PID set */ -static void vwm_xwin_monitor(vwm_xwindow_t *xwin) -{ - Atom type; - int fmt; - unsigned long nitems; - unsigned long nbytes; - long *foo = NULL; - int pid = -1; - - if (xwin->monitor) return; - - if (XGetWindowProperty(display, xwin->id, wm_pid_atom, 0, 1, False, XA_CARDINAL, - &type, &fmt, &nitems, &nbytes, (unsigned char **)&foo) != Success || !foo) return; - - pid = *foo; - XFree(foo); - - /* add the client process to the monitoring heirarchy */ - /* XXX note libvmon here maintains a unique callback for each unique callback+xwin pair, so multi-window processes work */ - xwin->monitor = vmon_proc_monitor(&vmon, NULL, pid, VMON_WANT_PROC_INHERIT, (void (*)(vmon_t *, vmon_proc_t *, void *))proc_sample_callback, xwin); - /* FIXME: count_rows() isn't returning the right count sometimes (off by ~1), it seems to be related to racing with the automatic child monitoring */ - /* the result is an extra row sometimes appearing below the process heirarchy */ - xwin->overlay.heirarchy_end = 1 + count_rows(xwin->monitor); - xwin->overlay.snowflakes_cnt = 0; -} - - -/* creates and potentially manages a new window (called in response to CreateNotify events, and during startup for all existing windows) */ -/* if the window is already mapped and not an override_redirect window, it becomes managed here. */ -typedef enum _vwm_grab_mode_t { - VWM_NOT_GRABBED = 0, - VWM_GRABBED -} vwm_grab_mode_t; - -static vwm_xwindow_t * vwm_xwin_create(Window win, vwm_grab_mode_t grabbed) -{ - XWindowAttributes attrs; - vwm_xwindow_t *xwin = NULL; - - VWM_TRACE("creating %#x", (unsigned int)win); - - /* prevent races */ - if (!grabbed) { - XGrabServer(display); - XSync(display, False); - } - - /* verify the window still exists */ - if (!XGetWindowAttributes(display, win, &attrs)) goto _out_grabbed; - - /* don't create InputOnly windows */ - if (attrs.class == InputOnly) goto _out_grabbed; - - if (!(xwin = (vwm_xwindow_t *)malloc(sizeof(vwm_xwindow_t)))) { - VWM_PERROR("Failed to allocate xwin"); - goto _out_grabbed; - } - - xwin->id = win; - xwin->attrs = attrs; - xwin->managed = NULL; - xwin->name = NULL; - XFetchName(display, win, &xwin->name); - - xwin->monitor = NULL; - xwin->overlay.width = xwin->overlay.height = xwin->overlay.phase = 0; - xwin->overlay.gen_last_composed = -1; - - /* This is so we get the PropertyNotify event and can get the pid when it's set post-create, - * with my _NET_WM_PID patch the property is immediately available */ - XSelectInput(display, win, PropertyChangeMask); - - vwm_xwin_monitor(xwin); - - /* we must track the mapped-by-client state of the window independent of managed vs. unmanaged because - * in the case of override_redirect windows they may be unmapped (invisible) or mapped (visible) like menus without being managed. - * otherwise we could just use !xwin.managed to indicate unmapped, which is more vwm2-like, but insufficient when compositing. */ - xwin->mapped = (attrs.map_state != IsUnmapped); - - if (compositing_mode) { - vwm_xwin_bind_namewindow(xwin); - xwin->damage = XDamageCreate(display, xwin->id, XDamageReportNonEmpty); - } - - list_add_tail(&xwin->xwindows, &xwindows); /* created windows are always placed on the top of the stacking order */ - -#ifdef HONOR_OVERRIDE_REDIRECT - if (!attrs.override_redirect && xwin->mapped) vwm_win_manage_xwin(xwin); -#else - if (xwin->mapped) vwm_win_manage_xwin(xwin); -#endif -_out_grabbed: - if (!grabbed) XUngrabServer(display); - - return xwin; -} - - -/* destroy a window, called in response to DestroyNotify events */ -/* if the window is also managed it will be unmanaged first */ -static void vwm_xwin_destroy(vwm_xwindow_t *xwin) -{ - XGrabServer(display); - XSync(display, False); - - if (xwin->managed) vwm_win_unmanage(xwin->managed); - - list_del(&xwin->xwindows); - - if (xwin->monitor) vmon_proc_unmonitor(&vmon, xwin->monitor, (void (*)(vmon_t *, vmon_proc_t *, void *))proc_sample_callback, xwin); - if (xwin->name) XFree(xwin->name); - - if (compositing_mode) { - vwm_xwin_unbind_namewindow(xwin); - XDamageDestroy(display, xwin->damage); - } - free(xwin); - - XUngrabServer(display); -} - - -/* maintain the stack-ordered xwindows list, when new_above is != None xwin is to be placed above new_above, when == None xwin goes to the bottom. */ -void vwm_xwin_restack(vwm_xwindow_t *xwin, Window new_above) -{ - Window old_above; -#ifdef TRACE - vwm_xwindow_t *tmp; - fprintf(stderr, "restack of %#x new_above=%#x\n", (unsigned int)xwin->id, (unsigned int)new_above); - fprintf(stderr, "restack pre:"); - list_for_each_entry(tmp, &xwindows, xwindows) { - fprintf(stderr, " %#x", (unsigned int)tmp->id); - } - fprintf(stderr, "\n"); -#endif - if (xwin->xwindows.prev != &xwindows) { - old_above = list_entry(xwin->xwindows.prev, vwm_xwindow_t, xwindows)->id; - } else { - old_above = None; - } - - if (old_above != new_above) { - vwm_xwindow_t *new; - - if (new_above == None) { /* to the bottom of the stack, so just above the &xwindows head */ - list_move(&xwin->xwindows, &xwindows); - } else if ((new = vwm_xwin_lookup(new_above))) { /* to just above new_above */ - list_move(&xwin->xwindows, &new->xwindows); - } - } -#ifdef TRACE - fprintf(stderr, "restack post:"); - list_for_each_entry(tmp, &xwindows, xwindows) { - fprintf(stderr, " %#x", (unsigned int)tmp->id); - } - fprintf(stderr, "\n\n"); -#endif -} - - - /* vwm "managed" windows (vwm_window_t) (which are built upon the "core" X windows (vwm_xwindow_t)) */ - -/* unmap the specified window and set the unmapping-in-progress flag so we can discard vwm-generated UnmapNotify events */ -static void vwm_win_unmap(vwm_window_t *vwin) -{ - if (!vwin->xwindow->mapped) { - VWM_TRACE("inhibited unmap of \"%s\", not mapped by client", vwin->xwindow->name); - return; - } - VWM_TRACE("Unmapping \"%s\"", vwin->xwindow->name); - vwin->unmapping = 1; - XUnmapWindow(display, vwin->xwindow->id); -} - - -/* map the specified window and set the mapping-in-progress flag so we can discard vwm-generated MapNotify events */ -static void vwm_win_map(vwm_window_t *vwin) -{ - if (!vwin->xwindow->mapped) { - VWM_TRACE("inhibited map of \"%s\", not mapped by client", vwin->xwindow->name); - return; - } - VWM_TRACE("Mapping \"%s\"", vwin->xwindow->name); - vwin->mapping = 1; - XMapWindow(display, vwin->xwindow->id); -} - - -/* make the specified window the most recently used one */ -static void vwm_win_mru(vwm_window_t *vwin) -{ - list_move(&vwin->windows_mru, &windows_mru); -} - - -/* look up the X window in the global managed windows list */ -static vwm_window_t * vwm_win_lookup(Window win) -{ - vwm_window_t *tmp, *vwin = NULL; - - list_for_each_entry(tmp, &windows_mru, windows_mru) { - if (tmp->xwindow->id == win) { - vwin = tmp; - break; - } - } - - return vwin; -} - - -/* return the currently focused window (considers current context...), may return NULL */ -static vwm_window_t * vwm_win_focused(void) -{ - vwm_window_t *vwin = NULL; - - switch (focused_context) { - case VWM_CONTEXT_FOCUS_SHELF: - vwin = focused_shelf; - break; - - case VWM_CONTEXT_FOCUS_DESKTOP: - if (focused_desktop) vwin = focused_desktop->focused_window; - break; - - default: - VWM_BUG("Unsupported context"); - break; - } - - return vwin; -} - - -/* "autoconfigure" windows (configuration shortcuts like fullscreen/halfscreen/quarterscreen) and restoring the window */ -typedef enum _vwm_win_autoconf_t { - VWM_WIN_AUTOCONF_NONE, /* un-autoconfigured window (used to restore the configuration) */ - VWM_WIN_AUTOCONF_QUARTER, /* quarter-screened */ - VWM_WIN_AUTOCONF_HALF, /* half-screened */ - VWM_WIN_AUTOCONF_FULL, /* full-screened */ - VWM_WIN_AUTOCONF_ALL /* all-screened (borderless) */ -} vwm_win_autoconf_t; - -typedef enum _vwm_side_t { - VWM_SIDE_TOP, - VWM_SIDE_BOTTOM, - VWM_SIDE_LEFT, - VWM_SIDE_RIGHT -} vwm_side_t; - -typedef enum _vwm_corner_t { - VWM_CORNER_TOP_LEFT, - VWM_CORNER_TOP_RIGHT, - VWM_CORNER_BOTTOM_RIGHT, - VWM_CORNER_BOTTOM_LEFT -} vwm_corner_t; - -static void vwm_win_autoconf(vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_autoconf_t conf, ...) -{ - const vwm_screen_t *scr; - va_list ap; - XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; - - /* remember the current configuration as the "client" configuration if it's not an autoconfigured one. */ - if (vwin->autoconfigured == VWM_WIN_AUTOCONF_NONE) vwin->client = vwin->xwindow->attrs; - - scr = vwm_screen_find(rel, vwin->xwindow); /* XXX FIXME: this becomes a bug when vwm_screen_find() uses non-xwin va_args */ - va_start(ap, conf); - switch (conf) { - case VWM_WIN_AUTOCONF_QUARTER: { - vwm_corner_t corner = va_arg(ap, vwm_corner_t); - changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); - switch (corner) { - case VWM_CORNER_TOP_LEFT: - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_CORNER_TOP_RIGHT: - changes.x = scr->x_org + scr->width / 2; - changes.y = scr->y_org; - break; - - case VWM_CORNER_BOTTOM_RIGHT: - changes.x = scr->x_org + scr->width / 2; - changes.y = scr->y_org + scr->height / 2; - break; - - case VWM_CORNER_BOTTOM_LEFT: - changes.x = scr->x_org; - changes.y = scr->y_org + scr->height / 2; - break; - } - break; - } - - case VWM_WIN_AUTOCONF_HALF: { - vwm_side_t side = va_arg(ap, vwm_side_t); - switch (side) { - case VWM_SIDE_TOP: - changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_SIDE_BOTTOM: - changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org; - changes.y = scr->y_org + scr->height / 2; - break; - - case VWM_SIDE_LEFT: - changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_SIDE_RIGHT: - changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org + scr->width / 2; - changes.y = scr->y_org; - break; - } - break; - } - - case VWM_WIN_AUTOCONF_FULL: - changes.width = scr->width - WINDOW_BORDER_WIDTH * 2; - changes.height = scr->height - WINDOW_BORDER_WIDTH * 2; - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_WIN_AUTOCONF_ALL: - changes.width = scr->width; - changes.height = scr->height; - changes.x = scr->x_org; - changes.y = scr->y_org; - changes.border_width = 0; - break; - - case VWM_WIN_AUTOCONF_NONE: /* restore window if autoconfigured */ - changes.width = vwin->client.width; - changes.height = vwin->client.height; - changes.x = vwin->client.x; - changes.y = vwin->client.y; - break; - } - va_end(ap); - - XConfigureWindow(display, vwin->xwindow->id, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); - vwin->autoconfigured = conf; -} - - -/* focus a window */ -/* this updates window border color as needed and the X input focus if mapped */ -static void vwm_win_focus(vwm_window_t *vwin) -{ - VWM_TRACE("focusing: %#x", (unsigned int)vwin->xwindow->id); - - if (vwm_xwin_is_mapped(vwin->xwindow)) { - /* if vwin is mapped give it the input focus */ - XSetInputFocus(display, vwin->xwindow->id, RevertToPointerRoot, CurrentTime); - } - - /* update the border color accordingly */ - if (vwin->shelved) { - /* set the border of the newly focused window to the shelved color */ - XSetWindowBorder(display, vwin->xwindow->id, vwin == console ? shelved_console_border_color.pixel : shelved_window_border_color.pixel); - /* fullscreen windows in the shelf when focused, since we don't intend to overlap there */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_POINTER, VWM_WIN_AUTOCONF_FULL); /* XXX TODO: for now the shelf follows the pointer, it's simple. */ - } else { - if (vwin->desktop->focused_window) { - /* set the border of the previously focused window on the same desktop to the unfocused color */ - XSetWindowBorder(display, vwin->desktop->focused_window->xwindow->id, unfocused_window_border_color.pixel); - } - - /* set the border of the newly focused window to the focused color */ - XSetWindowBorder(display, vwin->xwindow->id, focused_window_border_color.pixel); - - /* persist this on a per-desktop basis so it can be restored on desktop switches */ - vwin->desktop->focused_window = vwin; - } -} - - -/* focus the next window on a virtual desktop relative to the supplied window, in the specified context, respecting screen boundaries according to fence. */ -typedef enum _vwm_fence_t { - VWM_FENCE_IGNORE = 0, /* behave as if screen boundaries don't exist (like the pre-Xinerama code) */ - VWM_FENCE_RESPECT, /* confine operation to within the screen */ - VWM_FENCE_TRY_RESPECT, /* confine operation to within the screen, unless no options exist. */ - VWM_FENCE_VIOLATE, /* leave the screen for any other */ - VWM_FENCE_MASKED_VIOLATE /* leave the screen for any other not masked */ -} vwm_fence_t; - -static vwm_window_t * vwm_win_focus_next(vwm_window_t *vwin, vwm_fence_t fence) -{ - const vwm_screen_t *scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, vwin->xwindow), *next_scr = NULL; - vwm_window_t *next; - unsigned long visited_mask; - -_retry: - visited_mask = 0; - list_for_each_entry(next, &vwin->windows_mru, windows_mru) { - /* searching for the next mapped window in this context, using vwin->windows as the head */ - if (&next->windows_mru == &windows_mru) continue; /* XXX: skip the containerless head, we're leveraging the circular list implementation */ - - if ((vwin->shelved && next->shelved) || - ((!vwin->shelved && !next->shelved && next->desktop == vwin->desktop) && - (fence == VWM_FENCE_IGNORE || - ((fence == VWM_FENCE_RESPECT || fence == VWM_FENCE_TRY_RESPECT) && vwm_screen_find(VWM_SCREEN_REL_XWIN, next->xwindow) == scr) || - (fence == VWM_FENCE_VIOLATE && vwm_screen_find(VWM_SCREEN_REL_XWIN, next->xwindow) != scr) || - (fence == VWM_FENCE_MASKED_VIOLATE && (next_scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, next->xwindow)) != scr && - !((1UL << next_scr->screen_number) & fence_mask)) - ))) break; - - if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr && next_scr != scr) visited_mask |= (1UL << next_scr->screen_number); - } - - if (fence == VWM_FENCE_TRY_RESPECT && next == vwin) { - /* if we tried to respect the fence and failed to find a next, fallback to ignoring the fence and try again */ - fence = VWM_FENCE_IGNORE; - goto _retry; - } else if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr) { - /* if we did a masked violate update the mask with the potentially new screen number */ - if (next == vwin && visited_mask) { - /* if we failed to find a next window on a different screen but had candidates we've exhausted screens and need to reset the mask then retry */ - VWM_TRACE("all candidate screens masked @ 0x%lx, resetting mask", fence_mask); - fence_mask = 0; - goto _retry; - } - fence_mask |= (1UL << next_scr->screen_number); - VWM_TRACE("VWM_FENCE_MASKED_VIOLATE fence_mask now: 0x%lx\n", fence_mask); - } - - if (vwin->shelved) { - if (next != focused_shelf) { - /* shelf switch, unmap the focused shelf and take it over */ - /* TODO FIXME: this makes assumptions about the shelf being focused calling unmap/map directly.. */ - vwm_win_unmap(focused_shelf); - - XFlush(display); - - vwm_win_map(next); - focused_shelf = next; - vwm_win_focus(next); - } - } else { - if (next != next->desktop->focused_window) { - /* focus the changed window */ - vwm_win_focus(next); - XRaiseWindow(display, next->xwindow->id); - } - } - - VWM_TRACE("vwin=%p xwin=%p name=\"%s\"", next, next->xwindow, next->xwindow->name); - - return next; -} - - -/* shelves a window, if the window is focused we focus the next one (if possible) */ -static void vwm_win_shelve(vwm_window_t *vwin) -{ - /* already shelved, NOOP */ - if (vwin->shelved) return; - - /* shelving focused window, focus the next window */ - if (vwin == vwin->desktop->focused_window) { - vwm_win_mru(vwm_win_focus_next(vwin, VWM_FENCE_RESPECT)); - } - - if (vwin == vwin->desktop->focused_window) { - /* TODO: we can probably put this into vwm_win_focus_next() and have it always handled there... */ - vwin->desktop->focused_window = NULL; - } - - vwin->shelved = 1; - vwm_win_mru(vwin); - - /* newly shelved windows always become the focused shelf */ - focused_shelf = vwin; - - vwm_win_unmap(vwin); -} - - -/* helper for (idempotently) unfocusing a window, deals with context switching etc... */ -static void vwm_win_unfocus(vwm_window_t *vwin) -{ - /* if we're the focused shelved window, cycle the focus to the next shelved window if possible, if there's no more shelf, switch to the desktop */ - /* TODO: there's probably some icky behaviors for focused windows unmapping/destroying in unfocused contexts, we probably jump contexts suddenly. */ - if (vwin == focused_shelf) { - VWM_TRACE("unfocusing focused shelf"); - vwm_win_focus_next(vwin, VWM_FENCE_IGNORE); - - if (vwin == focused_shelf) { - VWM_TRACE("shelf empty, leaving"); - /* no other shelved windows, exit the shelf context */ - vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP); - focused_shelf = NULL; - } - } - - /* if we're the focused window cycle the focus to the next window on the desktop if possible */ - if (vwin->desktop->focused_window == vwin) { - VWM_TRACE("unfocusing focused window"); - vwm_win_focus_next(vwin, VWM_FENCE_TRY_RESPECT); - } - - if (vwin->desktop->focused_window == vwin) { - VWM_TRACE("desktop empty"); - vwin->desktop->focused_window = NULL; - } -} - - -/* demote an managed window to an unmanaged one */ -static vwm_xwindow_t * vwm_win_unmanage(vwm_window_t *vwin) -{ - vwm_win_mru(vwin); /* bump vwin to the mru head before unfocusing so we always move focus to the current head on unmanage of the focused window */ - vwm_win_unfocus(vwin); - list_del(&vwin->windows_mru); - - if (vwin == console) console = NULL; - if (vwin == focused_origin) focused_origin = NULL; - - vwin->xwindow->managed = NULL; - - return vwin->xwindow; -} - - -/* promote an unmanaged window to a managed one */ -static vwm_window_t * vwm_win_manage_xwin(vwm_xwindow_t *xwin) -{ - vwm_window_t *focused, *vwin = NULL; - - if (xwin->managed) { - VWM_TRACE("suppressed re-management of xwin=%p", xwin); - goto _fail; - } - - if (!(vwin = (vwm_window_t *)malloc(sizeof(vwm_window_t)))) { - VWM_PERROR("Failed to allocate vwin"); - goto _fail; - } - - XUngrabButton(display, AnyButton, AnyModifier, xwin->id); - XGrabButton(display, AnyButton, WM_GRAB_MODIFIER, xwin->id, False, (PointerMotionMask | ButtonPressMask | ButtonReleaseMask), GrabModeAsync, GrabModeAsync, None, None); - XGrabKey(display, AnyKey, WM_GRAB_MODIFIER, xwin->id, False, GrabModeAsync, GrabModeAsync); - XSetWindowBorder(display, xwin->id, unfocused_window_border_color.pixel); - - vwin->hints = XAllocSizeHints(); - if (!vwin->hints) { - VWM_PERROR("Failed to allocate WM hints"); - goto _fail; - } - XGetWMNormalHints(display, xwin->id, vwin->hints, &vwin->hints_supplied); - - xwin->managed = vwin; - vwin->xwindow = xwin; - - vwin->desktop = focused_desktop; - vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; - vwin->mapping = vwin->unmapping = vwin->configuring = 0; - vwin->shelved = (focused_context == VWM_CONTEXT_FOCUS_SHELF); /* if we're in the shelf when the window is created, the window is shelved */ - vwin->client = xwin->attrs; /* remember whatever the current attributes are */ - - VWM_TRACE("hints: flags=%lx x=%i y=%i w=%i h=%i minw=%i minh=%i maxw=%i maxh=%i winc=%i hinc=%i basew=%i baseh=%i grav=%x", - vwin->hints->flags, - vwin->hints->x, vwin->hints->y, - vwin->hints->width, vwin->hints->height, - vwin->hints->min_width, vwin->hints->min_height, - vwin->hints->max_width, vwin->hints->max_height, - vwin->hints->width_inc, vwin->hints->height_inc, - vwin->hints->base_width, vwin->hints->base_height, - vwin->hints->win_gravity); - - if ((vwin->hints_supplied & (USSize | PSize))) { - vwin->client.width = vwin->hints->base_width; - vwin->client.height = vwin->hints->base_height; - } - - /* put it on the global windows_mru list, if there's a focused window insert the new one after it */ - if (!list_empty(&windows_mru) && (focused = vwm_win_focused())) { - /* insert the vwin immediately after the focused window, so Mod1+Tab goes to the new window */ - list_add(&vwin->windows_mru, &focused->windows_mru); - } else { - list_add(&vwin->windows_mru, &windows_mru); - } - - /* always raise newly managed windows so we know about them. */ - XRaiseWindow(display, xwin->id); - - /* if the desktop has no focused window yet, automatically focus the newly managed one, provided we're on the desktop context */ - if (!focused_desktop->focused_window && focused_context == VWM_CONTEXT_FOCUS_DESKTOP) { - VWM_TRACE("Mapped new window \"%s\" is alone on desktop \"%s\", focusing", xwin->name, focused_desktop->name); - vwm_win_focus(vwin); - } - - return vwin; - -_fail: - if (vwin) { - if (vwin->hints) XFree(vwin->hints); - free(vwin); - } - return NULL; -} - - -/* migrate a window to a new desktop, focuses the destination desktop as well */ -static void vwm_win_migrate(vwm_window_t *vwin, vwm_desktop_t *desktop) -{ - vwm_win_unfocus(vwin); /* go through the motions of unfocusing the window if it is focused */ - vwin->shelved = 0; /* ensure not shelved */ - vwin->desktop = desktop; /* assign the new desktop */ - vwm_desktop_focus(desktop); /* currently we always focus the new desktop in a migrate */ - - vwm_win_focus(vwin); /* focus the window so borders get updated */ - vwm_win_mru(vwin); /* TODO: is this right? shouldn't the Mod1 release be what's responsible for this? I migrate so infrequently it probably doesn't matter */ - - XRaiseWindow(display, vwin->xwindow->id); /* ensure the window is raised */ -} - - - /* compositing manager stuff */ - -/* add damage to the global combined damage queue where we accumulate damage between calls to paint_all() */ -static void vwm_comp_damage_add(XserverRegion damage) -{ - if (combined_damage != None) { - XFixesUnionRegion(display, combined_damage, combined_damage, damage); - XFixesDestroyRegion(display, damage); - } else { - combined_damage = damage; - } -} - - -/* take what regions of the damaged window have been damaged, subtract them from the per-window damage object, and add them to the combined damage */ -static void vwm_comp_damage_event(XDamageNotifyEvent *ev) -{ - XserverRegion region; - vwm_xwindow_t *xwin; - - xwin = vwm_xwin_lookup(ev->drawable); - if (!xwin) { - VWM_ERROR("damaged unknown drawable %x", (unsigned int)ev->drawable); - return; - } - - region = XFixesCreateRegion(display, NULL, 0); - XDamageSubtract(display, xwin->damage, None, region); - XFixesTranslateRegion(display, region, xwin->attrs.x + xwin->attrs.border_width, xwin->attrs.y + xwin->attrs.border_width); - vwm_comp_damage_add(region); -} - - -/* helper to damage an entire window including the border */ -static void vwm_comp_damage_win(vwm_xwindow_t *xwin) -{ - XserverRegion region; - XRectangle rect = { xwin->attrs.x, - xwin->attrs.y, - xwin->attrs.width + xwin->attrs.border_width * 2, - xwin->attrs.height + xwin->attrs.border_width * 2 }; - region = XFixesCreateRegion(display, &rect, 1); - vwm_comp_damage_add(region); -} - - -/* throw away our double buffered root pictures so they get recreated on the next paint_all() */ -/* used in response to screen configuration changes... */ -static void vwm_comp_invalidate_root() -{ - if (root_picture) XRenderFreePicture(display, root_picture); - root_picture = None; - if (root_buffer) XRenderFreePicture(display, root_picture); - root_buffer = None; -} - - -/* consume combined_damage by iterating the xwindows list from the top down, drawing visible windows as encountered, subtracting their area from combined_damage */ -/* when compositing is active this is the sole function responsible for making things show up on the screen */ -static void vwm_comp_paint_all() -{ - vwm_xwindow_t *xwin; - XRenderColor bgcolor = {0x0000, 0x00, 0x00, 0xffff}; - Region occluded = XCreateRegion(); - static XserverRegion undamage_region = None; - - if (!undamage_region) undamage_region = XFixesCreateRegion(display, NULL, 0); - - /* (re)create the root picture from the root window and allocate a double buffer for the off-screen composition of the damaged contents */ - if (root_picture == None) { - Pixmap root_pixmap; - - XGetWindowAttributes(display, RootWindow(display, screen_num), &root_attrs); - root_picture = XRenderCreatePicture(display, RootWindow(display, screen_num), - XRenderFindVisualFormat(display, DefaultVisual(display, screen_num)), - CPSubwindowMode, &pa_inferiors); - root_pixmap = XCreatePixmap (display, RootWindow(display, screen_num), root_attrs.width, root_attrs.height, DefaultDepth (display, screen_num)); - root_buffer = XRenderCreatePicture (display, root_pixmap, XRenderFindVisualFormat (display, DefaultVisual (display, screen_num)), 0, 0); - XFreePixmap(display, root_pixmap); - } - - /* compose overlays for all visible windows up front in a separate pass (kind of lame, but it's simpler since compose_overlay() adds to combined_damage) */ - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - XRectangle r; - - if (!vwm_xwin_is_mapped(xwin)) continue; /* if !mapped skip */ - - /* Everything mapped next goes through an occlusion check. - * Since the composite extension stops delivery of VisibilityNotify events for redirected windows, - * (it assumes redirected windows should be treated as part of a potentially transparent composite, and provides no api to alter this assumption) - * we can't simply select the VisibilityNotify events on all windows and cache their visibility state in vwm_xwindow_t then skip - * xwin->state==VisibilityFullyObscured windows here to avoid the cost of pointlessly composing overlays and rendering fully obscured windows :(. - * - * Instead we accumulate an occluded region (starting empty) of painted windows from the top-down on every paint_all(). - * Before we compose_overlay() a window, we check if the window's rectangle fits entirely within the occluded region. - * If it does, no compose_overlay() is performed. - * If it doesn't, compose_overlay() is called, and the window's rect is added to the occluded region. - * The occluded knowledge is also cached for the XRenderComposite() loop immediately following, where we skip the rendering of - * occluded windows as well. - * This does technically break SHAPE windows (xeyes, xmms), but only when monitoring is enabled which covers them with rectangular overlays anyways. - */ - r.x = xwin->attrs.x; - r.y = xwin->attrs.y; - r.width = xwin->attrs.width + xwin->attrs.border_width * 2; - r.height = xwin->attrs.height + xwin->attrs.border_width * 2; - if (XRectInRegion(occluded, r.x, r.y, r.width, r.height) != RectangleIn) { - /* the window isn't fully occluded, compose it and add it to occluded */ - if (xwin->monitor && !xwin->attrs.override_redirect) compose_overlay(xwin); - XUnionRectWithRegion(&r, occluded, occluded); - xwin->occluded = 0; - } else { - xwin->occluded = 1; - VWM_TRACE("window %#x occluded, skipping compose_overlay()", (int)xwin->id); - } - } - XDestroyRegion(occluded); - - /* start with the clip regions set to the damaged area accumulated since the previous paint_all() */ - XFixesSetPictureClipRegion(display, root_buffer, 0, 0, combined_damage); /* this is the double buffer where the in-flight screen contents are staged */ - XFixesSetPictureClipRegion(display, root_picture, 0, 0, combined_damage); /* this is the visible root window */ - - /* since translucent windows aren't supported in vwm, I can do this more efficiently */ - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - XRectangle r; - - if (!vwm_xwin_is_mapped(xwin) || xwin->occluded) continue; /* if !mapped or occluded skip */ - - /* these coordinates + dimensions incorporate the border (since XCompositeNameWindowPixmap is being used) */ - r.x = xwin->attrs.x; - r.y = xwin->attrs.y; - r.width = xwin->attrs.width + xwin->attrs.border_width * 2; - r.height = xwin->attrs.height + xwin->attrs.border_width * 2; - - /* render the redirected window contents into root_buffer, note root_buffer has the remaining combined_damage set as the clip region */ - XRenderComposite(display, PictOpSrc, xwin->picture, None, root_buffer, - 0, 0, 0, 0, /* src x, y, mask x, y */ - r.x, r.y, /* dst x, y */ - r.width, r.height); - - if (xwin->monitor && !xwin->attrs.override_redirect && xwin->overlay.width) { - /* draw the monitoring overlay atop the window, note we stay within the window borders here. */ - XRenderComposite(display, PictOpOver, xwin->overlay.picture, None, root_buffer, - 0, 0, 0, 0, /* src x,y, maxk x, y */ - xwin->attrs.x + xwin->attrs.border_width, /* dst x */ - xwin->attrs.y + xwin->attrs.border_width, /* dst y */ - xwin->attrs.width, overlay_composed_height(xwin)); /* w, h */ - } - - /* subtract the region of the window from the combined damage and update the root_buffer clip region to reflect the remaining damage */ - XFixesSetRegion(display, undamage_region, &r, 1); - XFixesSubtractRegion(display, combined_damage, combined_damage, undamage_region); - XFixesSetPictureClipRegion(display, root_buffer, 0, 0, combined_damage); - } - - /* at this point all of the visible windows have been subtracted from the clip region, so paint any root window damage (draw background) */ - XRenderFillRectangle(display, PictOpSrc, root_buffer, &bgcolor, 0, 0, root_attrs.width, root_attrs.height); - - /* discard the root_buffer clip region and copy root_buffer to root_picture, root_picture still has the combined damage as its clip region */ - XFixesSetPictureClipRegion(display, root_buffer, 0, 0, None); - XRenderComposite(display, PictOpSrc, root_buffer, None, root_picture, 0, 0, 0, 0, 0, 0, root_attrs.width, root_attrs.height); - - /* fin */ - XFixesDestroyRegion(display, combined_damage); - combined_damage = None; -} - - -/* toggle compositing/monitoring overlays on/off */ -static void vwm_comp_toggle(void) -{ - vwm_xwindow_t *xwin; - - XGrabServer(display); - XSync(display, False); - - switch (compositing_mode) { - case VWM_COMPOSITING_OFF: - VWM_TRACE("enabling compositing"); - compositing_mode = VWM_COMPOSITING_MONITORS; - XCompositeRedirectSubwindows(display, RootWindow(display, screen_num), CompositeRedirectManual); - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - vwm_xwin_bind_namewindow(xwin); - xwin->damage = XDamageCreate(display, xwin->id, XDamageReportNonEmpty); - } - /* damage everything */ - /* TODO: keep a list of rects reflecting all the current screens and create a region from that... */ - vwm_comp_damage_add(XFixesCreateRegionFromWindow(display, RootWindow(display, screen_num), WindowRegionBounding)); - break; - - case VWM_COMPOSITING_MONITORS: { - XEvent ev; - - VWM_TRACE("disabling compositing"); - compositing_mode = VWM_COMPOSITING_OFF; - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - vwm_xwin_unbind_namewindow(xwin); - XDamageDestroy(display, xwin->damage); - } - XCompositeUnredirectSubwindows(display, RootWindow(display, screen_num), CompositeRedirectManual); - vwm_comp_invalidate_root(); - - /* if there's any damage queued up discard it so we don't enter paint_all() until compositing is reenabled again. */ - if (combined_damage) { - XFixesDestroyRegion(display, combined_damage); - combined_damage = None; - } - while (XCheckTypedEvent(display, damage_event + XDamageNotify, &ev) != False); - break; - } - } - - XUngrabServer(display); -} - - - /* input event handling stuff */ - -/* simple little state machine for managing mouse click/drag/release sequences so we don't need another event loop */ -typedef enum _vwm_adjust_mode_t { - VWM_ADJUST_RESIZE, - VWM_ADJUST_MOVE -} vwm_adjust_mode_t; - -typedef struct _vwm_clickety_t { - vwm_window_t *vwin; - vwm_adjust_mode_t mode; - XWindowAttributes orig, lastrect; - int impetus_x, impetus_y, impetus_x_root, impetus_y_root; -} vwm_clickety_t; - -/* helper function for resizing the window, how the motion is applied depends on where in the window the impetus event occurred */ -static void compute_resize(vwm_clickety_t *clickety, XEvent *terminus, XWindowAttributes *new) -{ - vwm_window_t *vwin; - int dw = (clickety->orig.width / 2); - int dh = (clickety->orig.height / 2); - int xdelta = (terminus->xbutton.x_root - clickety->impetus_x_root); - int ydelta = (terminus->xbutton.y_root - clickety->impetus_y_root); - int min_width = 0, min_height = 0; - int width_inc = 1, height_inc = 1; - - /* TODO: there's a problem here WRT border width, I should be considering it, I just haven't bothered to fix it since it doesn't seem to matter */ - if ((vwin = clickety->vwin) && vwin->hints) { - if ((vwin->hints_supplied & PMinSize)) { - min_width = vwin->hints->min_width; - min_height = vwin->hints->min_height; - VWM_TRACE("window size hints exist and minimum sizes are w=%i h=%i", min_width, min_height); - } - - if ((vwin->hints_supplied & PResizeInc)) { - width_inc = vwin->hints->width_inc; - height_inc = vwin->hints->height_inc; - VWM_TRACE("window size hints exist and resize increments are w=%i h=%i", width_inc, height_inc); - if (!width_inc) width_inc = 1; - if (!height_inc) height_inc = 1; - } - } - - xdelta = xdelta / width_inc * width_inc; - ydelta = ydelta / height_inc * height_inc; - - if (clickety->impetus_x < dw && clickety->impetus_y < dh) { - /* grabbed top left */ - new->x = clickety->orig.x + xdelta; - new->y = clickety->orig.y + ydelta; - new->width = clickety->orig.width - xdelta; - new->height = clickety->orig.height - ydelta; - } else if (clickety->impetus_x > dw && clickety->impetus_y < dh) { - /* grabbed top right */ - new->x = clickety->orig.x; - new->y = clickety->orig.y + ydelta; - new->width = clickety->orig.width + xdelta; - new->height = clickety->orig.height - ydelta; - } else if (clickety->impetus_x < dw && clickety->impetus_y > dh) { - /* grabbed bottom left */ - new->x = clickety->orig.x + xdelta; - new->y = clickety->orig.y; - new->width = clickety->orig.width - xdelta; - new->height = clickety->orig.height + ydelta; - } else if (clickety->impetus_x > dw && clickety->impetus_y > dh) { - /* grabbed bottom right */ - new->x = clickety->orig.x; - new->y = clickety->orig.y; - new->width = clickety->orig.width + xdelta; - new->height = clickety->orig.height + ydelta; - } - - /* constrain the width and height of the window according to the minimums */ - if (new->width < min_width) { - if (clickety->orig.x != new->x) new->x -= (min_width - new->width); - new->width = min_width; - } - - if (new->height < min_height) { - if (clickety->orig.y != new->y) new->y -= (min_height - new->height); - new->height = min_height; - } -} - - -static void vwm_clickety_motion(vwm_clickety_t *clickety, Window win, XMotionEvent *motion) -{ - XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; - - if (!clickety->vwin) return; - - /* TODO: verify win matches clickety->vwin? */ - switch (clickety->mode) { - case VWM_ADJUST_MOVE: - changes.x = clickety->orig.x + (motion->x_root - clickety->impetus_x_root); - changes.y = clickety->orig.y + (motion->y_root - clickety->impetus_y_root); - XConfigureWindow(display, win, CWX | CWY | CWBorderWidth, &changes); - break; - - case VWM_ADJUST_RESIZE: { - XWindowAttributes resized; - - /* XXX: it just so happens the XMotionEvent structure is identical to XButtonEvent in the fields - * needed by compute_resize... */ - compute_resize(clickety, (XEvent *)motion, &resized); - /* TODO: this is probably broken with compositing active, but it doesn't seem to be too messed up in practice */ - /* erase the last rectangle */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, clickety->lastrect.x, clickety->lastrect.y, clickety->lastrect.width, clickety->lastrect.height); - /* draw a frame @ resized coordinates */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, resized.x, resized.y, resized.width, resized.height); - /* remember the last rectangle */ - clickety->lastrect = resized; - break; - } - - default: - break; - } -} - - -static void vwm_clickety_released(vwm_clickety_t *clickety, Window win, XButtonPressedEvent *terminus) -{ - XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; - - if (!clickety->vwin) return; - - switch (clickety->mode) { - case VWM_ADJUST_MOVE: - changes.x = clickety->orig.x + (terminus->x_root - clickety->impetus_x_root); - changes.y = clickety->orig.y + (terminus->y_root - clickety->impetus_y_root); - XConfigureWindow(display, win, CWX | CWY | CWBorderWidth, &changes); - break; - - case VWM_ADJUST_RESIZE: { - XWindowAttributes resized; - compute_resize(clickety, (XEvent *)terminus, &resized); - /* move and resize the window @ resized */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, clickety->lastrect.x, clickety->lastrect.y, clickety->lastrect.width, clickety->lastrect.height); - changes.x = resized.x; - changes.y = resized.y; - changes.width = resized.width; - changes.height = resized.height; - XConfigureWindow(display, win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); - XUngrabServer(display); - break; - } - - default: - break; - } - /* once you manipulate the window it's no longer fullscreened, simply hitting Mod1+Return once will restore fullscreened mode */ - clickety->vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; - - clickety->vwin = NULL; /* reset clickety */ - - XFlush(display); - XUngrabPointer(display, CurrentTime); -} - - -/* on pointer buttonpress we initiate a clickety sequence; setup clickety with the window and impetus coordinates.. */ -static int vwm_clickety_pressed(vwm_clickety_t *clickety, Window win, XButtonPressedEvent *impetus) -{ - vwm_window_t *vwin; - - /* verify the window still exists */ - if (!XGetWindowAttributes(display, win, &clickety->orig)) goto _fail; - - if (!(vwin = vwm_win_lookup(win))) goto _fail; - - if (impetus->state & WM_GRAB_MODIFIER) { - - /* always set the input focus to the clicked window, note if we allow this to happen on the root window, it enters sloppy focus mode - * until a non-root window is clicked, which is an interesting hybrid but not how I prefer it. */ - if (vwin != focused_desktop->focused_window && vwin->xwindow->id != RootWindow(display, screen_num)) { - vwm_win_focus(vwin); - vwm_win_mru(vwin); - } - - switch (impetus->button) { - case Button1: - /* immediately raise the window if we're relocating, - * resizes are supported without raising (which also enables NULL resizes to focus without raising) */ - clickety->mode = VWM_ADJUST_MOVE; - XRaiseWindow(display, win); - break; - - case Button3: - /* grab the server on resize for the xor rubber-banding's sake */ - XGrabServer(display); - XSync(display, False); - - /* FIXME: none of the resize DrawRectangle() calls consider the window border. */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, clickety->orig.x, clickety->orig.y, clickety->orig.width, clickety->orig.height); - clickety->lastrect = clickety->orig; - - clickety->mode = VWM_ADJUST_RESIZE; - break; - - default: - goto _fail; - } - clickety->vwin = vwin; - clickety->impetus_x_root = impetus->x_root; - clickety->impetus_y_root = impetus->y_root; - clickety->impetus_x = impetus->x; - clickety->impetus_y = impetus->y; - } - - return 1; - -_fail: - XUngrabPointer(display, CurrentTime); - - return 0; -} - -/* Poll the keyboard state to see if _any_ keys are pressed */ -static int vwm_keyspressed() { - int i; - char state[32]; - - XQueryKeymap(display, state); - - for (i = 0; i < sizeof(state); i++) { - if (state[i]) return 1; - } - - return 0; -} - -/* Called in response to KeyRelease events, for now only interesting for detecting when Mod1 is released termintaing - * window cycling for application of MRU on the focused window */ -static void vwm_keyreleased(Window win, XEvent *keyrelease) -{ - vwm_window_t *vwin; - KeySym sym; - - switch ((sym = XLookupKeysym(&keyrelease->xkey, 0))) { - case XK_Alt_R: - case XK_Alt_L: /* TODO: actually use the modifier mapping, for me XK_Alt_[LR] is Mod1. XGetModifierMapping()... */ - VWM_TRACE("XK_Alt_[LR] released"); - - /* aborted? try restore focused_origin */ - if (key_is_grabbed > 1 && focused_origin) { - VWM_TRACE("restoring %p on %p", focused_origin, focused_origin->desktop); - vwm_desktop_focus(focused_origin->desktop); - vwm_win_focus(focused_origin); - } - - /* make the focused window the most recently used */ - if ((vwin = vwm_win_focused())) vwm_win_mru(vwin); - - /* make the focused desktop the most recently used */ - if (focused_context == VWM_CONTEXT_FOCUS_DESKTOP && focused_desktop) vwm_desktop_mru(focused_desktop); - - break; - - default: - VWM_TRACE("Unhandled keycode: %x", (unsigned int)sym); - break; - } - - if (key_is_grabbed && !vwm_keyspressed()) { - XUngrabKeyboard(display, CurrentTime); - XFlush(display); - key_is_grabbed = 0; - fence_mask = 0; /* reset the fence mask on release for VWM_FENCE_MASKED_VIOLATE */ - } -} - - -/* Called in response to KeyPress events, I currenly only grab Mod1 keypress events */ -static void vwm_keypressed(Window win, XEvent *keypress) -{ - vwm_window_t *vwin; - KeySym sym; - static KeySym last_sym; - static typeof(keypress->xkey.state) last_state; - static int repeat_cnt = 0; - int do_grab = 0; - char *quit_console_args[] = {"/bin/sh", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X quit", NULL}; - - sym = XLookupKeysym(&keypress->xkey, 0); - - /* detect repeaters, note repeaters do not span interrupted Mod1 sequences! */ - if (key_is_grabbed && sym == last_sym && keypress->xkey.state == last_state) { - repeat_cnt++; - } else { - repeat_cnt = 0; - } - - vwin = vwm_win_focused(); - - switch (sym) { - -#define launcher(_sym, _label, _argv)\ - case _sym: \ - { \ - char *args[] = {"/bin/sh", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X screen /bin/sh -i -x -c \"" _argv " || sleep 86400\"", NULL};\ - vwm_launch(args, VWM_LAUNCH_MODE_BG);\ - break; \ - } -#include "launchers.def" -#undef launcher - case XK_Alt_L: /* transaction abort */ - case XK_Alt_R: - if (key_is_grabbed) key_is_grabbed++; - VWM_TRACE("aborting with origin %p", focused_origin); - break; - - case XK_grave: /* toggle shelf visibility */ - vwm_context_focus(VWM_CONTEXT_FOCUS_OTHER); - break; - - case XK_Tab: /* cycle focused window */ - do_grab = 1; /* update MRU window on commit (Mod1 release) */ - - /* focus the next window, note this doesn't affect MRU yet, that happens on Mod1 release */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { - vwm_win_focus_next(vwin, VWM_FENCE_MASKED_VIOLATE); - } else { - vwm_win_focus_next(vwin, VWM_FENCE_RESPECT); - } - } - break; - - case XK_space: { /* cycle focused desktop utilizing MRU */ - vwm_desktop_t *next_desktop = list_entry(focused_desktop->desktops_mru.next == &desktops_mru ? focused_desktop->desktops_mru.next->next : focused_desktop->desktops_mru.next, vwm_desktop_t, desktops_mru); /* XXX: note the sensitivity to the desktops_mru head here, we want to look past it. */ - - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - /* migrate the focused window with the desktop focus to the most recently used desktop */ - if (vwin) vwm_win_migrate(vwin, next_desktop); - } else { - vwm_desktop_focus(next_desktop); - } - break; - } - - case XK_d: /* destroy focused */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { /* brutally destroy the focused window */ - XKillClient(display, vwin->xwindow->id); - } else { /* kindly destroy the focused window */ - vwm_xwin_message(vwin->xwindow, wm_protocols_atom, wm_delete_atom); - } - } else if (focused_context == VWM_CONTEXT_FOCUS_DESKTOP) { - /* destroy the focused desktop when destroy occurs without any windows */ - vwm_desktop_destroy(focused_desktop); - } - break; - - case XK_Escape: /* leave VWM rudely, after triple press */ - do_grab = 1; - - if (repeat_cnt == 2) { - vwm_launch(quit_console_args, VWM_LAUNCH_MODE_FG); - exit(42); - } - break; - - case XK_v: /* instantiate (and focus) a new (potentially empty, unless migrating) virtual desktop */ - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - if (vwin) { - /* migrate the focused window to a newly created virtual desktop, focusing the new desktop simultaneously */ - vwm_win_migrate(vwin, vwm_desktop_create(NULL)); - } - } else { - vwm_desktop_focus(vwm_desktop_create(NULL)); - vwm_desktop_mru(focused_desktop); - } - break; - - case XK_h: /* previous virtual desktop, if we're in the shelf context this will simply switch to desktop context */ - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - if (vwin && vwin->desktop->desktops.prev != &desktops) { - /* migrate the focused window with the desktop focus to the previous desktop */ - vwm_win_migrate(vwin, list_entry(vwin->desktop->desktops.prev, vwm_desktop_t, desktops)); - } - } else { - if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - /* focus the focused desktop instead of the shelf */ - vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP); - } else if (focused_desktop->desktops.prev != &desktops) { - /* focus the previous desktop */ - vwm_desktop_focus(list_entry(focused_desktop->desktops.prev, vwm_desktop_t, desktops)); - } - } - break; - - case XK_l: /* next virtual desktop, if we're in the shelf context this will simply switch to desktop context */ - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - if (vwin && vwin->desktop->desktops.next != &desktops) { - /* migrate the focused window with the desktop focus to the next desktop */ - vwm_win_migrate(vwin, list_entry(vwin->desktop->desktops.next, vwm_desktop_t, desktops)); - } - } else { - if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - /* focus the focused desktop instead of the shelf */ - vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP); - } else if (focused_desktop->desktops.next != &desktops) { - /* focus the next desktop */ - vwm_desktop_focus(list_entry(focused_desktop->desktops.next, vwm_desktop_t, desktops)); - } - } - break; - - case XK_k: /* raise or shelve the focused window */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { /* shelf the window and focus the shelf */ - if (focused_context != VWM_CONTEXT_FOCUS_SHELF) { - /* shelve the focused window while focusing the shelf */ - vwm_win_shelve(vwin); - vwm_context_focus(VWM_CONTEXT_FOCUS_SHELF); - } - } else { - do_grab = 1; - - XRaiseWindow(display, vwin->xwindow->id); - - if (repeat_cnt == 1) { - /* double: reraise & fullscreen */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - } else if (repeat_cnt == 2) { - /* triple: reraise & fullscreen w/borders obscured by screen perimiter */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_ALL); - } else if (xinerama_screens_cnt > 1) { - if (repeat_cnt == 3) { - /* triple: reraise & fullscreen across all screens */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_FULL); - } else if (repeat_cnt == 4) { - /* quadruple: reraise & fullscreen w/borders obscured by screen perimiter */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_ALL); - } - } - XFlush(display); - } - } - break; - - case XK_j: /* lower or unshelve the focused window */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { /* unshelf the window to the focused desktop, and focus the desktop */ - if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - /* unshelve the focused window, focus the desktop it went to */ - vwm_win_migrate(vwin, focused_desktop); - } - } else { - if (vwin->autoconfigured == VWM_WIN_AUTOCONF_ALL) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - } else { - XLowerWindow(display, vwin->xwindow->id); - } - XFlush(display); - } - } - break; - - case XK_Return: /* (full-screen / restore) focused window */ - if (vwin) { - if (vwin->autoconfigured) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_NONE); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - } - } - break; - - case XK_s: /* shelve focused window */ - if (vwin && !vwin->shelved) vwm_win_shelve(vwin); - break; - - case XK_bracketleft: /* reconfigure the focused window to occupy the left or top half of the screen or left quarters on repeat */ - if (vwin) { - do_grab = 1; - - if (keypress->xkey.state & ShiftMask) { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_TOP); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_LEFT); - } - } else { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_LEFT); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_LEFT); - } - } - } - break; - - case XK_bracketright: /* reconfigure the focused window to occupy the right or bottom half of the screen or right quarters on repeat */ - if (vwin) { - do_grab = 1; - - if (keypress->xkey.state & ShiftMask) { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_BOTTOM); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_RIGHT); - } - } else { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_RIGHT); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_RIGHT); - } - } - } - break; - - case XK_semicolon: /* toggle composited overlays */ - vwm_comp_toggle(); - break; - - case XK_apostrophe: /* reset snowflakes of the focused window, suppressed when not compositing */ - if (vwin && compositing_mode && vwin->xwindow->overlay.snowflakes_cnt) { - vwin->xwindow->overlay.snowflakes_cnt = 0; - vwm_comp_damage_win(vwin->xwindow); - } - break; - - case XK_Right: /* increase sampling frequency */ - if (sampling_interval + 1 < sizeof(sampling_intervals) / sizeof(sampling_intervals[0])) sampling_interval++; - break; - - case XK_Left: /* decrease sampling frequency, -1 pauses */ - if (sampling_interval >= 0) sampling_interval--; - break; - default: - VWM_TRACE("Unhandled keycode: %x", (unsigned int)sym); - break; - } - - /* if what we're doing requests a grab, if not already grabbed, grab keyboard */ - if (!key_is_grabbed && do_grab) { - VWM_TRACE("saving focused_origin of %p", vwin); - focused_origin = vwin; /* for returning to on abort */ - XGrabKeyboard(display, RootWindow(display, screen_num), False, GrabModeAsync, GrabModeAsync, CurrentTime); - key_is_grabbed = 1; - } - - /* remember the symbol for repeater detection */ - last_sym = sym; - last_state = keypress->xkey.state; -} - - - /* some randomish things called from main() */ - -/* manage all existing windows (for startup) */ -static int vwm_manage_existing(void) -{ - Window root, parent; - Window *children = NULL; - unsigned int n_children, i; - - XGrabServer(display); - XSync(display, False); - XQueryTree(display, RootWindow(display, screen_num), &root, &parent, &children, &n_children); - - for (i = 0; i < n_children; i++) { - if (children[i] == None) continue; - - if ((vwm_xwin_create(children[i], VWM_GRABBED) == NULL)) goto _fail_grabbed; - } - - XUngrabServer(display); - - if (children) XFree(children); - - return 1; - -_fail_grabbed: - XUngrabServer(display); - - if (children) XFree(children); - - return 0; -} - - -/* comvenience function for returning the time delta as a seconds.fraction float */ -static float delta(struct timeval *cur, struct timeval *prev) -{ - struct timeval res; - float delta; - - /* determine the # of whole.fractional seconds between prev and cur */ - timersub(cur, prev, &res); - - delta = res.tv_sec; - delta += (float)((float)res.tv_usec) / 1000000.0; - - return delta; -} - -static int errhandler(Display *display, XErrorEvent *err) -{ - /* TODO */ - return 1; -} - -int main(int argc, char *argv[]) -{ - int err = 0; - int done = 0; - XEvent event; - Cursor pointer; - struct pollfd pfd; - char *console_args[] = {"xterm", "-class", CONSOLE_WM_CLASS, "-e", "/bin/sh", "-c", "screen -D -RR " CONSOLE_SESSION_STRING, NULL}; - Window bitmask; - vwm_clickety_t clickety = { .vwin = NULL }; - -#define reterr_if(_cond, _fmt, _args...) \ - err++;\ - if (_cond) {\ - VWM_ERROR(_fmt, ##_args);\ - return err;\ - } - - /* open connection with the server */ - reterr_if((display = XOpenDisplay(NULL)) == NULL, "Cannot open display"); - - /* prevent children from inheriting the X connection */ - reterr_if(fcntl(ConnectionNumber(display), F_SETFD, FD_CLOEXEC) < 0, "Cannot set FD_CLOEXEC on X connection"); - - /* get our scheduling priority, clients are launched with a priority LAUNCHED_RELATIVE_PRIORITY nicer than this */ - reterr_if((priority = getpriority(PRIO_PROCESS, getpid())) == -1, "Cannot get scheduling priority"); - - XSetErrorHandler(errhandler); - - screen_num = DefaultScreen(display); - - reterr_if(!XQueryExtension (display, COMPOSITE_NAME, &composite_opcode, &composite_event, &composite_error), "No composite extension available"); - reterr_if(!XDamageQueryExtension(display, &damage_event, &damage_error), "No damage extension available"); - if (XSyncQueryExtension(display, &sync_event, &sync_error)) { - /* set the window manager to the maximum X client priority */ - XSyncSetPriority(display, RootWindow(display, screen_num), 0x7fffffff); - } - - if (XineramaQueryExtension(display, &xinerama_event, &xinerama_error)) { - xinerama_screens = XineramaQueryScreens(display, &xinerama_screens_cnt); - } - - if (XRRQueryExtension(display, &randr_event, &randr_error)) { - XRRSelectInput(display, RootWindow(display, screen_num), RRScreenChangeNotifyMask); - } - - /* allocate colors, I make assumptions about the X server's color capabilities since I'll only use this on modern-ish computers... */ - cmap = DefaultColormap(display, screen_num); - -#define color(_sym, _str) \ - XAllocNamedColor(display, cmap, _str, &_sym ## _color, &_sym ## _color); -#include "colors.def" -#undef color - - wm_delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", False); - wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False); - wm_pid_atom = XInternAtom(display, "_NET_WM_PID", False); - - XSelectInput(display, RootWindow(display, screen_num), - PropertyChangeMask | SubstructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); - XGrabKey(display, AnyKey, WM_GRAB_MODIFIER, RootWindow(display, screen_num), False, GrabModeAsync, GrabModeAsync); - - XFlush(display); - - XSetInputFocus(display, RootWindow(display, screen_num), RevertToPointerRoot, CurrentTime); - - /* initialize libvmon */ - vmon_init(&vmon, VMON_FLAG_2PASS, VMON_WANT_SYS_STAT, (VMON_WANT_PROC_STAT | VMON_WANT_PROC_FOLLOW_CHILDREN | VMON_WANT_PROC_FOLLOW_THREADS)); - vmon.proc_ctor_cb = vmon_ctor_cb; - vmon.proc_dtor_cb = vmon_dtor_cb; - vmon.sample_cb = sample_callback; - - /* get all the text and graphics stuff setup for overlays */ - reterr_if(!(overlay_font = XLoadQueryFont(display, OVERLAY_FIXED_FONT)), "failed to open font: " OVERLAY_FIXED_FONT); - - /* create a GC for rendering the text using Xlib into the text overlay stencils */ - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, OVERLAY_MASK_DEPTH); - text_gc = XCreateGC(display, bitmask, 0, NULL); - XSetForeground(display, text_gc, WhitePixel(display, screen_num)); - XFreePixmap(display, bitmask); - - /* create some repeating source fill pictures for drawing through the text and graph stencils */ - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_text_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_text_fill, &overlay_visible_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_shadow_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_shadow_fill, &overlay_shadow_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, OVERLAY_ROW_HEIGHT, 32); - overlay_bg_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_bg_fill, &overlay_bg_color, 0, 0, 1, OVERLAY_ROW_HEIGHT); - XRenderFillRectangle(display, PictOpSrc, overlay_bg_fill, &overlay_div_color, 0, OVERLAY_ROW_HEIGHT - 1, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_snowflakes_text_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_snowflakes_text_fill, &overlay_snowflakes_visible_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_grapha_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_grapha_fill, &overlay_grapha_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_graphb_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_graphb_fill, &overlay_graphb_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 2, 32); - overlay_finish_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_finish_fill, &overlay_visible_color, 0, 0, 1, 1); - XRenderFillRectangle(display, PictOpSrc, overlay_finish_fill, &overlay_trans_color, 0, 1, 1, 1); - - /* create initial virtual desktop */ - vwm_desktop_focus(vwm_desktop_create(NULL)); - vwm_desktop_mru(focused_desktop); - - /* manage all preexisting windows */ - vwm_manage_existing(); - - /* create GC for logo drawing and window rubber-banding */ - gc = XCreateGC(display, RootWindow(display, screen_num), 0, NULL); - XSetSubwindowMode(display, gc, IncludeInferiors); - XSetFunction(display, gc, GXxor); - - /* launch the console here so it's likely ready by the time the logo animation finishes (there's no need to synchronize with it currently) */ - vwm_launch(console_args, VWM_LAUNCH_MODE_BG); - - /* first the logo color is the foreground */ - XSetForeground(display, gc, logo_color.pixel); - vwm_draw_logo(); - - /* change to the rubber-banding foreground color */ - XSetForeground(display, gc, rubberband_color.pixel); - - XClearWindow(display, RootWindow(display, screen_num)); - - /* set the pointer */ - pointer = XCreateFontCursor(display, XC_X_cursor); - XDefineCursor(display, RootWindow(display, screen_num), pointer); - - pfd.events = POLLIN; - pfd.revents = 0; - pfd.fd = ConnectionNumber(display); - - gettimeofday(&this_sample, NULL); - while (!done) { - do { - static int sampling_paused = 0; - static int contiguous_drops = 0; - float this_delta; - - gettimeofday(&maybe_sample, NULL); - if ((sampling_interval == -1 && !sampling_paused) || /* XXX this is kind of a kludge to get the 0 Hz indicator drawn before pausing */ - (sampling_interval != -1 && ((this_delta = delta(&maybe_sample, &this_sample)) >= sampling_intervals[sampling_interval]))) { - vmon_sys_stat_t *sys_stat; - - /* automatically lower the sample rate if we can't keep up with the current sample rate */ - if (sampling_interval != -1 && sampling_interval <= prev_sampling_interval && - this_delta >= (sampling_intervals[sampling_interval] * 1.5)) { - contiguous_drops++; - /* require > 1 contiguous drops before lowering the rate, tolerates spurious one-off stalls */ - if (contiguous_drops > 2) sampling_interval--; - } else contiguous_drops = 0; - - /* age the sys-wide sample data into "last" variables, before the new sample overwrites them. */ - last_sample = this_sample; - this_sample = maybe_sample; - if ((sys_stat = vmon.stores[VMON_STORE_SYS_STAT])) { - last_user_cpu = sys_stat->user; - last_system_cpu = sys_stat->system; - last_total = sys_stat->user + - sys_stat->nice + - sys_stat->system + - sys_stat->idle + - sys_stat->iowait + - sys_stat->irq + - sys_stat->softirq + - sys_stat->steal + - sys_stat->guest; - - last_idle = sys_stat->idle; - last_iowait = sys_stat->iowait; - } - - vmon_sample(&vmon); /* XXX: calls proc_sample_callback() for explicitly monitored processes after sampling their descendants */ - /* XXX: also calls sample_callback() per invocation after sampling the sys wants */ - sampling_paused = (sampling_interval == -1); - prev_sampling_interval = sampling_interval; - } - - XFlush(display); - - if (!XPending(display)) { - /* TODO: make some effort to compute how long to sleep, but this is perfectly fine for now. */ - if (poll(&pfd, 1, sampling_interval != -1 ? sampling_intervals[sampling_interval] * 300.0 : -1) == 0) break; - } - - XNextEvent(display, &event); - switch (event.type) { - case KeyPress: - VWM_TRACE("keypress"); - vwm_keypressed(event.xkey.window, &event); - break; - - case KeyRelease: - VWM_TRACE("keyrelease"); - vwm_keyreleased(event.xkey.window, &event); - break; - - case ButtonPress: - VWM_TRACE("buttonpresss"); - vwm_clickety_pressed(&clickety, event.xbutton.window, &event.xbutton); - break; - - case MotionNotify: - //VWM_TRACE("motionnotify"); - vwm_clickety_motion(&clickety, event.xmotion.window, &event.xmotion); - break; - - case ButtonRelease: - VWM_TRACE("buttonrelease"); - vwm_clickety_released(&clickety, event.xbutton.window, &event.xbutton); - break; - - case CreateNotify: - VWM_TRACE("createnotify"); - vwm_xwin_create(event.xcreatewindow.window, VWM_NOT_GRABBED); - break; - - case DestroyNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("destroynotify"); - if ((xwin = vwm_xwin_lookup(event.xdestroywindow.window))) { - vwm_xwin_destroy(xwin); - } - break; - } - - case ConfigureRequest: { - XWindowChanges changes = { - .x = event.xconfigurerequest.x, /* TODO: for now I don't manipulate anything */ - .y = event.xconfigurerequest.y, - .width = event.xconfigurerequest.width, - .height = event.xconfigurerequest.height, - .border_width = WINDOW_BORDER_WIDTH /* except I do override whatever the border width may be */ - }; - unsigned long change_mask = (event.xconfigurerequest.value_mask & (CWX | CWY | CWWidth | CWHeight)) | CWBorderWidth; - vwm_xwindow_t *xwin; - - /* XXX: windows raising themselves is annoying, so discard CWSibling and CWStackMode. */ - VWM_TRACE("configurerequest x=%i y=%i w=%i h=%i", changes.x, changes.y, changes.width, changes.height); - - if ((xwin = vwm_xwin_lookup(event.xconfigure.window)) && - xwin->managed && - xwin->managed->autoconfigured == VWM_WIN_AUTOCONF_ALL) { - /* this is to allow auto-allscreen to succeed in getting a borderless window configured */ - change_mask &= ~CWBorderWidth; - } - - XConfigureWindow(display, event.xconfigurerequest.window, change_mask, &changes); - break; - } - - case ConfigureNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("configurenotify"); - if ((xwin = vwm_xwin_lookup(event.xconfigure.window))) { - XWindowAttributes attrs; - vwm_xwin_restack(xwin, event.xconfigure.above); - XGetWindowAttributes(display, event.xconfigure.window, &attrs); - if (compositing_mode) { - /* damage the old and new window areas */ - XserverRegion region; - XRectangle rects[2] = { { xwin->attrs.x, - xwin->attrs.y, - xwin->attrs.width + xwin->attrs.border_width * 2, - xwin->attrs.height + xwin->attrs.border_width * 2 }, - { attrs.x, - attrs.y, - attrs.width + attrs.border_width * 2, - attrs.height + attrs.border_width * 2 } }; - - region = XFixesCreateRegion(display, rects, 2); - vwm_comp_damage_add(region); - vwm_xwin_unbind_namewindow(xwin); - vwm_xwin_bind_namewindow(xwin); - } - VWM_TRACE("pre x=%i y=%i w=%i h=%i\n", xwin->attrs.x, xwin->attrs.y, xwin->attrs.width, xwin->attrs.height); - xwin->attrs = attrs; - VWM_TRACE("post x=%i y=%i w=%i h=%i\n", xwin->attrs.x, xwin->attrs.y, xwin->attrs.width, xwin->attrs.height); - } - break; - } - - case UnmapNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("unmapnotify"); - /* unlike MapRequest, we simply are notified when a window is unmapped. */ - if ((xwin = vwm_xwin_lookup(event.xunmap.window))) { - if (xwin->managed) { - if (xwin->managed->unmapping) { - VWM_TRACE("swallowed vwm-induced UnmapNotify"); - xwin->managed->unmapping = 0; - } else { - /* client requested unmap, demote the window and note the unmapped state */ - vwm_win_unmanage(xwin->managed); - xwin->mapped = 0; - } - } else { - /* if it's not managed, we can't have caused the map */ - xwin->mapped = 0; - } - - if (compositing_mode) vwm_comp_damage_win(xwin); - } - break; - } - - case MapNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("mapnotify"); - if ((xwin = vwm_xwin_lookup(event.xmap.window))) { - if (xwin->managed && xwin->managed->mapping) { - VWM_TRACE("swallowed vwm-induced MapNotify"); - xwin->managed->mapping = 0; - } else { - /* some windows like popup dialog boxes bypass MapRequest */ - xwin->mapped = 1; - } - - if (compositing_mode) { - vwm_comp_damage_win(xwin); - vwm_xwin_unbind_namewindow(xwin); - vwm_xwin_bind_namewindow(xwin); - } - } - break; - } - - case MapRequest: { - vwm_xwindow_t *xwin; - vwm_window_t *vwin = NULL; - int domap = 1; - VWM_TRACE("maprequest"); - if ((xwin = vwm_xwin_lookup(event.xmap.window)) && - ((vwin = xwin->managed) || (vwin = vwm_win_manage_xwin(xwin)))) { - XWindowAttributes attrs; - XWindowChanges changes = {.x = 0, .y = 0}; - unsigned changes_mask = (CWX | CWY); - XClassHint *classhint; - const vwm_screen_t *scr = NULL; - - xwin->mapped = 1; /* note that the client mapped the window */ - - /* figure out if the window is the console */ - if ((classhint = XAllocClassHint())) { - if (XGetClassHint(display, event.xmap.window, classhint) && !strcmp(classhint->res_class, CONSOLE_WM_CLASS)) { - console = vwin; - vwm_win_shelve(vwin); - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - domap = 0; - } - - if (classhint->res_class) XFree(classhint->res_class); - if (classhint->res_name) XFree(classhint->res_name); - XFree(classhint); - } - - /* TODO: this is a good place to hook in a window placement algo */ - - /* on client-requested mapping we place the window */ - if (!vwin->shelved) { - /* we place the window on the screen containing the the pointer only if that screen is empty, - * otherwise we place windows on the screen containing the currently focused window */ - /* since we query the geometry of windows in determining where to place them, a configuring - * flag is used to exclude the window being configured from those queries */ - scr = vwm_screen_find(VWM_SCREEN_REL_POINTER); - vwin->configuring = 1; - if (vwm_screen_is_empty(scr)) { - /* focus the new window if it isn't already focused when it's going to an empty screen */ - VWM_TRACE("window \"%s\" is alone on screen \"%i\", focusing", vwin->xwindow->name, scr->screen_number); - vwm_win_focus(vwin); - } else { - scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, focused_desktop->focused_window->xwindow); - } - vwin->configuring = 0; - - changes.x = scr->x_org; - changes.y = scr->y_org; - } else if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, focused_shelf->xwindow); - changes.x = scr->x_org; - changes.y = scr->y_org; - } - - /* XXX TODO: does this belong here? */ - XGetWMNormalHints(display, event.xmap.window, vwin->hints, &vwin->hints_supplied); - XGetWindowAttributes(display, event.xmap.window, &attrs); - - - /* if the window size is precisely the screen size then directly "allscreen" the window right here */ - if (!vwin->shelved && scr && - attrs.width == scr->width && - attrs.height == scr->height) { - VWM_TRACE("auto-allscreened window \"%s\"", vwin->xwindow->name); - changes.border_width = 0; - changes_mask |= CWBorderWidth; - vwin->autoconfigured = VWM_WIN_AUTOCONF_ALL; - } - - vwin->client.x = changes.x; - vwin->client.y = changes.y; - vwin->client.height = attrs.height; - vwin->client.width = attrs.width; - - XConfigureWindow(display, event.xmap.window, changes_mask, &changes); - } - - if (domap) { - XMapWindow(display, event.xmap.window); - if (vwin && vwin->desktop->focused_window == vwin) { - XSync(display, False); - XSetInputFocus(display, vwin->xwindow->id, RevertToPointerRoot, CurrentTime); - } - } - break; - } - - case PropertyNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("property notify"); - if ((xwin = vwm_xwin_lookup(event.xproperty.window)) && - event.xproperty.atom == wm_pid_atom && - event.xproperty.state == PropertyNewValue) vwm_xwin_monitor(xwin); - break; - } - - case MappingNotify: - VWM_TRACE("mapping notify"); - XRefreshKeyboardMapping(&event.xmapping); - break; - - case Expose: - VWM_TRACE("expose"); - break; - case GravityNotify: - VWM_TRACE("gravitynotify"); - break; - case ReparentNotify: - VWM_TRACE("reparentnotify"); - break; - default: - if (event.type == randr_event + RRScreenChangeNotify) { - VWM_TRACE("rrscreenchangenotify"); - if (xinerama_screens) XFree(xinerama_screens); - xinerama_screens = XineramaQueryScreens(display, &xinerama_screens_cnt); - - if (compositing_mode) vwm_comp_invalidate_root(); - } else if (event.type == damage_event + XDamageNotify) { - //VWM_TRACE("damagenotify"); - vwm_comp_damage_event((XDamageNotifyEvent *)&event); - } else { - VWM_ERROR("Unhandled X op %i", event.type); - } - break; - } - } while (QLength(display)); - - if (combined_damage != None) { /* if there's damage to repaint, do it, this only happens when compositing for overlays is enabled */ - vwm_comp_paint_all(); - XSync(display, False); - } - } - - /* close connection to server */ - XFlush(display); - XCloseDisplay(display); - - return 0; -} @@ -1,84 +0,0 @@ -#ifndef _VWM_H -#define _VWM_H - -#include <stdio.h> -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <errno.h> - -#include "list.h" - -#define VWM_ERROR(_fmt, _args...) fprintf(stderr, "%s:%i\t%s() "_fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##_args) -#define VWM_PERROR(_fmt, _args...) fprintf(stderr, "%s:%i\t%s() "_fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##_args, strerror(errno)) -#define VWM_BUG(_fmt, _args...) fprintf(stderr, "BUG %s:%i\t%s() "_fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##_args, strerror(errno)) - -#ifdef TRACE -#define VWM_TRACE(_fmt, _args...) fprintf(stderr, "%s:%i\t%s() "_fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##_args) -#else -#define VWM_TRACE(_fmt, _args...) do { } while(0) -#endif - -typedef struct _vwm_desktop_t { - list_head_t desktops; /* global list of (virtual) desktops */ - list_head_t desktops_mru; /* global list of (virtual) desktops in MRU order */ - char *name; /* name of the desktop (TODO) */ - struct _vwm_window_t *focused_window; /* the focused window on this virtual desktop */ -} vwm_desktop_t; - -/* everything needed by the per-window overlay's context */ -typedef struct _vwm_overlay_t { - Pixmap text_pixmap; /* pixmap for overlayed text (kept around for XDrawText usage) */ - Picture text_picture; /* picture representation of text_pixmap */ - Picture shadow_picture; /* text shadow layer */ - Picture grapha_picture; /* graph A layer */ - Picture graphb_picture; /* graph B layer */ - Picture tmp_picture; /* 1 row worth of temporary picture space */ - Picture picture; /* overlay picture derived from the pixmap, for render compositing */ - int width; /* current width of the overlay */ - int height; /* current height of the overlay */ - int phase; /* current position within the (horizontally scrolling) graphs */ - int heirarchy_end; /* row where the process heirarchy currently ends */ - int snowflakes_cnt; /* count of snowflaked rows (reset to zero to truncate snowflakes display) */ - int gen_last_composed; /* the last composed vmon generation */ -} vwm_overlay_t; - -/* every window gets this, even non-managed ones. For compositing vwm must track everything visible, even popup menus. */ -typedef struct _vwm_xwindow_t { - list_head_t xwindows; /* global list of all windows kept in X stacking order */ - - Window id; /* X Window backing this instance */ - XWindowAttributes attrs; /* X window's current attributes, kept up-to-date in handling of ConfigureNotify events */ - Damage damage; /* X damage object associated with the window (for compositing) */ - Picture picture; /* X picture object representing the window (for compositing) */ - Pixmap pixmap; /* X pixmap object representing the window (for compositing) */ - - vmon_proc_t *monitor; /* vmon process monitor handle, may be NULL if for example the X client doesn't supply a PID */ - vwm_overlay_t overlay; /* monitoring overlay state */ - - char *name; /* client name */ - unsigned int mapped:1; /* is the window currently mapped (by client) */ - unsigned int occluded:1; /* is the window occluded entirely by another window? (used and valid only during paint_all()) */ - /* if only Xorg could send VisibilityNotify events when requested for redirected windows :( */ - struct _vwm_window_t *managed; /* is the window "managed"? NULL or this points to the managed context of the window */ -} vwm_xwindow_t; - -/* the managed window we create for every mapped window we actually manage */ -typedef struct _vwm_window_t { - list_head_t windows_mru; /* global list of managed windows kept in MRU order */ - - vwm_xwindow_t *xwindow; /* window being managed */ - vwm_desktop_t *desktop; /* desktop this window belongs to currently */ - - XWindowAttributes client; /* attrs of the client-configured window */ - - XSizeHints *hints; /* hints the client supplied */ - long hints_supplied; /* bitfield reflecting the hints the client supplied */ - - unsigned int autoconfigured:3; /* autoconfigured window states (none/quarter/half/full/all) */ - unsigned int mapping:1; /* is the window being mapped? (by vwm) */ - unsigned int unmapping:1; /* is the window being unmapped? (by vwm) */ - unsigned int configuring:1; /* is the window being configured/placed? (by vwm) */ - unsigned int shelved:1; /* is the window shelved? */ -} vwm_window_t; - -#endif |