diff options
Diffstat (limited to 'src/composite.c')
-rw-r--r-- | src/composite.c | 331 |
1 files changed, 331 insertions, 0 deletions
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); +} |