chjj / compton

A compositor for X11.
Other
2.25k stars 500 forks source link

Maximized Chromium window sometimes gets partially hidden (& another Chromium issue & research results & fixes) #36

Closed richardgv closed 12 years ago

richardgv commented 12 years ago

@chjj: I hope you don't mind if I create a report for the issue you discovered. I would like to post my research result of the maximized Chromium window partially hidden bug you mentioned, and I don't want to do it in a different bug report. The last of your 3 pending issues, right? Although it is slightly less baffling than the ghost window one, it could be more frustrating as unfortunately this time only Chromium devs instead of you could produce a perfect fix, I guess.

The cause of the issue:

compton/xcompmgr uses border_size() to determine the exact size of a window with its borders -- despite its deceptive name. The very line of code that does this work is:

border = XFixesCreateRegionFromWindow (dpy, w->id, WindowRegionBounding);

It returns a region of one or more rectangles together covering the whole window precisely. When applying to rectangular windows, XFixesCreateRegionFromWindow() returns a region with single rectangle beautifully. Probably because Chrom{ium,e} has rounded corners on its main windows when it's not maximized, XFixesCreateRegionFromWindow() returns 5 rectangles that together form the region of the Chrom{ium,e} window, something like this:

(The format is, <x>, <y>, <width>, <height>
Rec #4: 3, 501, 584, 1
Rec #3: 1, 499, 588, 2
Rec #2: 0, 3, 590, 496
Rec #1: 1, 1, 588, 2
Rec #0: 3, 0, 584, 1

Of course, compton/xcompmgr is able to handle this situation. But when the Chrom{ium,e} window is maximized, Chrom{ium,e} stills returns these 5 small rectangles as its bounding rectangles, during a very very short period of time -- short enough for probably millions or tens of millions of instructions to be executed. A person's one second is more than a million year for a modern CPU. If you just wait for much less than a human second (or minimize the Chrom{ium,e} window now and restore it to maximized state), it will start to return the correct bounding rectangle, like:

Rec #0: 0, 0, 1440, 870

When Chrom{ium,e} starts maximized, it, too, creates a normal window firstly then maximizes it automatically, thus the problem could appear, too.

compton/xcompmgr doesn't ask a window for its bounding rectangles every time it paints, but maintains a cached result in w->border_size. If compton/xcompmgr happen to asked Chrom{ium,e} during the period of time in which it reports wrong bounding rectangles, the bug appears.

Moreover, there's another issue caused by a similar reason (it will be called "the second issue" later): If you restore a Chrom{ium,e} window from maximized state, chances are some parts of the content in window won't be cleared but left outside the resized window. It's caused by Chromium window returning the old bounding rectangle, a single rectangle of your screen size, after it restored from maximized state, for again a very very short period of time.

Here's the debug output of compton when the bugs appear, with comments: https://gist.github.com/3683925

The details:

  1. Chromium creates a ConfigureNotify when it changes window size. When receiving it, compton/xcompmgr marks the area of the Chromium window (a rectangle formed with the dimensions returned by win_extents(), calculated using the window size reported in window attributes, this one is actually correct, it just does not handle windows with non-rectangular shapes, unlike border_size()) as damaged (add the rectangle to the damaged area damage with add_damage()), and turn on the flag clip_changed to indicate window sizes and window bounding rectangles need updates;
  2. compton/xcompton starts painting after it finishes doing other things, calling paint_all(), and it requests new window bounding rectangles of the Chromium window from border_size();
  3. border_size() retrieves the incorrect bounding rectangles of the Chromium window; paint_all() paints the non-transparent windows in the order of the window stack, from the highest to the lowest, in each step it restricts painting to the damaged area (argument region), removes the border_size rectangles of the window being painted from the damaged area, and set the remaining damaged area as the border_clip of this window, later used to restrict the area where the shadow of the window is painted. As the damage area of the resized Chromium window is an area returned by win_extents(), it does contain the whole Chromium window after resize, so the Chromium window is actually completely painted, it's just that later some parts of the window (the parts outside of the wrong bounding rectangles of the window border_size() got) are overlapped by windows behind it (including the root window) and possibly by shadows, because they are not removed from the damaged area, so lower windows could still be painted in the area, and they are kept in the border_clip of the Chromium window and possibly other windows, upon which shadows could be painted. Oh, well, surely xcompmgr has some confusing painting mechanisms.
  4. And before xcompmgr/compton receives an event that enable the clip_changed flag (a CirculateNotify, ConfigureNotify, etc.) or the window gets unmapped, the wrong result will remain cached, so the problem persists until you do something to the windows.
  5. The second bug is pretty much similar: Chromium sends the old bounding rectangle, the window gets an abnormally large bounding rectangle cached; xcompmgr/compton paints the content of the new window, of a smaller size, leaving some parts of the original window untouched in the screen buffer; these parts get removed from the damage area because of the wrong bounding rectangle, so no other windows, not even root window, could be painted on them, and the original content in the area is simply left there. These old content will only be wiped from the screen when they become a part of the damaged area -- for example, when you move another window over the area.

Possible fixes:

  1. Ask Chromium developers to change the maximize and restore routine, update the window bounding rectangles before it changes window size. Well, I'm not sure about how X FIXES extension determines the bounding rectangles of a window, so it may not be Chromium's fault. The problem is, whether they are cooperative is unpredictable.
  2. (Fixes the first issue only) Update window bounding rectangles every time compton paints things. Of course, it's bad performance-wise. I hope the performance penalty is acceptable on modern hardware.

    diff --git a/src/compton.c b/src/compton.c
    index b72dc59..98116ff 100644
    --- a/src/compton.c
    +++ b/src/compton.c
    @@ -1028,12 +1028,13 @@ paint_all(Display *dpy, XserverRegion region) {
        printf(" 0x%x", w->id);
    #endif
    
    +    if (w->border_size) {
    +      set_ignore(dpy, NextRequest(dpy));
    +      XFixesDestroyRegion(dpy, w->border_size);
    +      w->border_size = None;
    +    }
    +
        if (clip_changed) {
    -      if (w->border_size) {
    -        set_ignore(dpy, NextRequest(dpy));
    -        XFixesDestroyRegion(dpy, w->border_size);
    -        w->border_size = None;
    -      }
          if (w->extents) {
            XFixesDestroyRegion(dpy, w->extents);
            w->extents = None;
    @@ -1044,9 +1045,7 @@ paint_all(Display *dpy, XserverRegion region) {
          }
        }
    
    -    if (!w->border_size) {
    -      w->border_size = border_size(dpy, w);
    -    }
    +    w->border_size = border_size(dpy, w);
    
        if (!w->extents) {
          w->extents = win_extents(dpy, w);
  3. (Fixes the second issue only, to be combined with fix 2) Forces a full screen refresh every time by always making the whole screen the damaged zone. This needs to be combined with fix 2 to ensure the bounding rectangles get updated. The drawback is performance, painting all the time is very costly.

    diff --git a/src/compton.c b/src/compton.c
    index b72dc59..d656f07 100644
    --- a/src/compton.c
    +++ b/src/compton.c
    @@ -1028,12 +1028,13 @@ paint_all(Display *dpy, XserverRegion region) {
        printf(" 0x%x", w->id);
    #endif
    
    +    if (w->border_size) {
    +      set_ignore(dpy, NextRequest(dpy));
    +      XFixesDestroyRegion(dpy, w->border_size);
    +      w->border_size = None;
    +    }
    +
        if (clip_changed) {
    -      if (w->border_size) {
    -        set_ignore(dpy, NextRequest(dpy));
    -        XFixesDestroyRegion(dpy, w->border_size);
    -        w->border_size = None;
    -      }
          if (w->extents) {
            XFixesDestroyRegion(dpy, w->extents);
            w->extents = None;
    @@ -1044,9 +1045,7 @@ paint_all(Display *dpy, XserverRegion region) {
          }
        }
    
    -    if (!w->border_size) {
    -      w->border_size = border_size(dpy, w);
    -    }
    +    w->border_size = border_size(dpy, w);
    
        if (!w->extents) {
          w->extents = win_extents(dpy, w);
    @@ -2586,13 +2585,11 @@ main(int argc, char **argv) {
          ev_handle((XEvent *)&ev);
        } while (QLength(dpy));
    
    -    if (all_damage) {
    -      static int paint;
    -      paint_all(dpy, all_damage);
    -      paint++;
    -      XSync(dpy, False);
    -      all_damage = None;
    -      clip_changed = False;
    -    }
    +    static int paint;
    +    paint_all(dpy, None);
    +    paint++;
    +    XSync(dpy, False);
    +    all_damage = None;
    +    clip_changed = False;
      }
    }
  4. Change paint_all() so it paints in a different sequence: Paints the root window, paint the lowest window's shadow on it, paint the lowest window, paint the shadow of the second-lowest window... It looks a lot more intuitive, and it should be much easier to implement, perhaps helpful in performance. I'm afraid, nonetheless, these xcompmgr developers, with infinite wisdom, might choose the current seemingly-clumsy painting method on purpose. I believe it's the most promising fix, and it requires most code modification.
  5. Let compton wait for a couple of milliseconds before it requests for the bounding rectangles of a window to make sure Chromium has enough time to react. Apparently it does not sound smart -- what if compton and Chromium are running on a 80386?
  6. Add special code handling Chromium windows. Well, if you can manage to to identify them with 100% accuracy...
  7. Remove area of a painted window from the damaged area according to the window dimension attributes instead of w->border_size. Good for rectangular or close-to-rectangular windows, disaster for specially-shaped windows without special handling -- more exactly, for the windows and shadows under a specially-shaped window.
  8. (Fixes the second issue only) A milder version of the fix 7, cut the part of the bounding rectangles if they outside of the border determined by window dimension attributes. Some performance penalty yet not too bad. The rounded corners of Chromium window will not appear right but hardly noticeable.

    diff --git a/src/compton.c b/src/compton.c
    index b72dc59..a1483f5 100644
    --- a/src/compton.c
    +++ b/src/compton.c
    @@ -866,6 +866,19 @@ border_size(Display *dpy, win *w) {
        w->a.x + w->a.border_width,
        w->a.y + w->a.border_width);
    
    +  {
    +    XserverRegion reg = XFixesCreateRegion(dpy, NULL, 0);
    +    XRectangle r;
    +  
    +    r.x = w->a.x;
    +    r.y = w->a.y;
    +    r.width = w->a.width + w->a.border_width * 2;
    +    r.height = w->a.height + w->a.border_width * 2;
    +
    +    XFixesInvertRegion(dpy, reg, &r, border);
    +    XFixesInvertRegion(dpy, border, &r, reg);
    +  }
    +
      return border;
    }
  9. Improved version of fix 8, detect abnormal results border_size() returns (compare the sizes and positions of the rectangular shapes with the window dimension attributes), then try to calculate a region closest to the actual region a Chromium window occupies. It requires quite some twists to fix both issues with this approach. Less time-efficient, does not look graceful, have the rounded corner issue as fix 6.
  10. Minimize and restore every window when it maximizes, and wait for the event before proceeding. I must be insane...

By the way:

I couldn't reproduce this bug on fvwm as under fvwm without a border Chromium does not seem to maximize correctly.

And I hope you won't simply stop updating compton after the 3 issues get resolved. There are other bugs -- like menu transparency does not work without -f -- and other features that could be added -- at least review my colored shadow patch. :-)

chjj commented 12 years ago

@richardgv, thanks for the good work again. I feel really uneasy about performance hits. I want to leave this issue open for now. This was honestly the most minor of the 3 major bugs.

I've made you a core committer on compton. Ideally it would be good to discuss changes through pull requests, but you have commit access if you need it. Just be careful, a lot of packages point to this repo as a release since I don't do formal releases for compton.

And I hope you won't simply stop updating compton after the 3 issues get resolved.

I plan to maintain compton for as long as I can. It's just the past few months have been busy for me.

Change paint_all() so it paints in a different sequence: Paints the root window, paint the lowest window's shadow on it, paint the lowest window, paint the shadow of the second-lowest window... It looks a lot more intuitive, and it should be much easier to implement, perhaps helpful in performance. I'm afraid, nonetheless, these xcompmgr developers, with infinite wisdom, might choose the current seemingly-clumsy painting method on purpose. I believe it's the most promising fix, and it requires most code modification.

It might be interesting to start up a separate branch for this. I'm all for rewriting everything honestly. If we can make it better, let's do it.

richardgv commented 12 years ago

I feel really uneasy about performance hits. I want to leave this issue open for now. This was honestly the most minor of the 3 major bugs.

If you need a quick fix right now, my fix 2 combined with fix 8 shouldn't cause too aggressive a performance hit.

I looked into the source code of libXfixes and xorg-server, and seemingly the rounding shapes are returned by the program creating the window. Chromium has quite a huge amount of code, looks like ./ui/aura/root_window_host_linux.cc contains the code that handles the window, I'm still searching for where exactly the bounding shapes are added and modified. Maybe it's better to ask Chromium devs firstly before I work on a patch to Chromium?

Update: chrome/browser/ui/gtk/rounded_window.cc andchrome/browser/ui/gtk/browser_window_gtk.cc may actually be the key files.

I've made you a core committer on compton. Ideally it would be good to discuss changes through pull requests, but you have commit access if you need it. Just be careful, a lot of packages point to this repo as a release since I don't do formal releases for compton.

I usually won't commit without prior discussion, unless my GitHub account is hacked, I assume.

I personally believe compton is stable enough for a formal release if it could be verified that my recent patches don't do something awful. With a formal release it helps to keep the snapshots of various distros in sync. Right now at least Fedora and Gentoo come with snapshots of different dates in their official repo.

I plan to maintain compton for as long as I can. It's just the past few months have been busy for me.

Awesome.

It might be interesting to start up a separate branch for this. I'm all for rewriting everything honestly. If we can make it better, let's do it.

xcompmgr has quite some ugly code that got inherited to compton, indeed. I will see what I can help.

richardgv commented 12 years ago

In Chromium's chrome/browser/ui/gtk/browser_window_gtk.cc I found the code to update the bounding region:

void BrowserWindowGtk::UpdateWindowShape(int width, int height) {
  GdkRegion* mask = GetWindowShape(width, height);
  gdk_window_shape_combine_region(
      gtk_widget_get_window(GTK_WIDGET(window_)), mask, 0, 0);
  if (mask)
    gdk_region_destroy(mask);

  if (UseCustomFrame() && !IsFullscreen() && !IsMaximized()) {
    gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 1,
        kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness);
  } else {
    gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 0, 0, 0, 0);
  }
}

GdkRegion* BrowserWindowGtk::GetWindowShape(int width, int height) const {
  if (UseCustomFrame() && !IsFullscreen() && !IsMaximized()) {
    // Make the corners rounded.  We set a mask that includes most of the
    // window except for a few pixels in each corner.
    GdkRectangle top_top_rect = { 3, 0, width - 6, 1 };
    GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 };
    GdkRectangle mid_rect = { 0, 3, width, height - 6 };
    // The bottom two rects are mirror images of the top two rects.
    GdkRectangle bot_mid_rect = top_mid_rect;
    bot_mid_rect.y = height - 3;
    GdkRectangle bot_bot_rect = top_top_rect;
    bot_bot_rect.y = height - 1;
    GdkRegion* mask = gdk_region_rectangle(&top_top_rect);
    gdk_region_union_with_rect(mask, &top_mid_rect);
    gdk_region_union_with_rect(mask, &mid_rect);
    gdk_region_union_with_rect(mask, &bot_mid_rect);
    gdk_region_union_with_rect(mask, &bot_bot_rect);
    return mask;
  } else if (UseCustomFrame()) {
    // Disable rounded corners.  Simply passing in a NULL region doesn't
    // seem to work on KWin, so manually set the shape to the whole window.
    GdkRectangle rect = { 0, 0, width, height };
    GdkRegion* mask = gdk_region_rectangle(&rect);
    return mask;
  } else {
    // XFCE disables the system decorations if there's an xshape set. Do not
    // use the KWin hack when the custom frame is not enabled.
    return NULL;
  }
}

UpdateWindowShape() is called upon window changes, theme changes, etc. Unfortunately, I didn't find a way to update the bounding region before the window geometry changes -- I doubt if it's possible, actually, considering the design of X. I've reported this to Chromium, however I guess it's questionable whether they will fix it somehow or provide a workaround: https://code.google.com/p/chromium/issues/detail?id=147533

richardgv commented 12 years ago

Sorry that I didn't know there's a thing called ShapeNotify previously. I'm able to write a patch with it that probably solved these two issues with minimal performance impact:

(Note this patched included and improved my "better debug" patch as I needed to include event debugging support for Shape extension, and don't feel like pushing out another patch just for "better debug with ShapeNotify support".)

diff --git a/Makefile b/Makefile
index b0c9d5e..60a5c7a 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ PREFIX ?= /usr
 BINDIR ?= $(PREFIX)/bin
 MANDIR ?= $(PREFIX)/share/man/man1

-PACKAGES = x11 xcomposite xfixes xdamage xrender
+PACKAGES = x11 xcomposite xfixes xdamage xrender xext
 LIBS = $(shell pkg-config --libs $(PACKAGES)) -lm
 INCS = $(shell pkg-config --cflags $(PACKAGES))
 CFLAGS += -Wall
diff --git a/src/compton.c b/src/compton.c
index b72dc59..7d66ec1 100644
--- a/src/compton.c
+++ b/src/compton.c
@@ -10,6 +10,10 @@

 #include "compton.h"

+#if DEBUG_EVENTS
+static int window_get_name(Window w, char **name);
+#endif
+
 /**
  * Shared
  */
@@ -36,6 +40,10 @@ ignore *ignore_head, **ignore_tail = &ignore_head;
 int xfixes_event, xfixes_error;
 int damage_event, damage_error;
 int composite_event, composite_error;
+/// Whether the Shape extension exists
+Bool shape_exists = True;
+/// event_base and error_base for Shape extension
+int shape_event, shape_error;
 int render_event, render_error;
 int composite_opcode;

@@ -954,12 +962,7 @@ paint_all(Display *dpy, XserverRegion region) {
   win *t = 0;

   if (!region) {
-    XRectangle r;
-    r.x = 0;
-    r.y = 0;
-    r.width = root_width;
-    r.height = root_height;
-    region = XFixesCreateRegion(dpy, &r, 1);
+    region = get_screen_region(dpy);
   }

 #if MONITOR_REPAINT
@@ -1029,11 +1032,7 @@ paint_all(Display *dpy, XserverRegion region) {
 #endif

     if (clip_changed) {
-      if (w->border_size) {
-        set_ignore(dpy, NextRequest(dpy));
-        XFixesDestroyRegion(dpy, w->border_size);
-        w->border_size = None;
-      }
+      win_free_border_size(dpy, w);
       if (w->extents) {
         XFixesDestroyRegion(dpy, w->extents);
         w->extents = None;
@@ -1372,6 +1371,10 @@ map_win(Display *dpy, Window id,
      so that no property changes are lost */
   if (!override_redirect) {
     XSelectInput(dpy, id, PropertyChangeMask | FocusChangeMask);
+    // Notify compton when the shape of a window changes
+    if (shape_exists) {
+      XShapeSelectInput(dpy, id, ShapeNotifyMask);
+    }
   }

   // this causes problems for inactive transparency
@@ -1425,11 +1428,7 @@ finish_unmap_win(Display *dpy, win *w) {
     w->picture = None;
   }

-  if (w->border_size) {
-    set_ignore(dpy, NextRequest(dpy));
-    XFixesDestroyRegion(dpy, w->border_size);
-    w->border_size = None;
-  }
+  win_free_border_size(dpy, w);

   if (w->shadow) {
     XRenderFreePicture(dpy, w->shadow);
@@ -1692,6 +1691,33 @@ restack_win(Display *dpy, win *w, Window new_above) {

     w->next = *prev;
     *prev = w;
+
+#if DEBUG_RESTACK
+    {
+      const char *desc;
+      char *window_name;
+      Bool to_free;
+      win* c = list;
+  
+      printf("restack_win(%#010lx, %#010lx): Window stack modified. Current stack:\n", w->id, new_above);
+      for (; c; c = c->next) {
+        window_name = "(Failed to get title)";
+        if (root == c->id)
+          window_name = "(Root window)";
+        else
+          to_free = window_get_name(c->id, &window_name);
+        desc = "";
+        if (c->destroyed)
+          desc = "(D) ";
+        printf("%#010lx \"%s\" %s-> ", c->id, window_name, desc);
+        if (to_free) {
+          XFree(window_name);
+          window_name = NULL;
+        }
+      }
+      fputs("\n", stdout);
+    }
+#endif
   }
 }

@@ -1957,6 +1983,60 @@ error(Display *dpy, XErrorEvent *ev) {
       break;
   }

+  switch (ev->error_code) {
+    case BadAccess:
+      name = "BadAccess";
+      break;
+    case BadAlloc:
+      name = "BadAlloc";
+      break;
+    case BadAtom:
+      name = "BadAtom";
+      break;
+    case BadColor:
+      name = "BadColor";
+      break;
+    case BadCursor:
+      name = "BadCursor";
+      break;
+    case BadDrawable:
+      name = "BadDrawable";
+      break;
+    case BadFont:
+      name = "BadFont";
+      break;
+    case BadGC:
+      name = "BadGC";
+      break;
+    case BadIDChoice:
+      name = "BadIDChoice";
+      break;
+    case BadImplementation:
+      name = "BadImplementation";
+      break;
+    case BadLength:
+      name = "BadLength";
+      break;
+    case BadMatch:
+      name = "BadMatch";
+      break;
+    case BadName:
+      name = "BadName";
+      break;
+    case BadPixmap:
+      name = "BadPixmap";
+      break;
+    case BadRequest:
+      name = "BadRequest";
+      break;
+    case BadValue:
+      name = "BadValue";
+      break;
+    case BadWindow:
+      name = "BadWindow";
+      break;
+  }
+
   printf("error %d (%s) request %d minor %d serial %lu\n",
     ev->error_code, name, ev->request_code,
     ev->minor_code, ev->serial);
@@ -1970,10 +2050,36 @@ expose_root(Display *dpy, Window root, XRectangle *rects, int nrects) {
   add_damage(dpy, region);
 }

-#if DEBUG_EVENTS
+#if DEBUG_EVENTS || DEBUG_RESTACK
+static int window_get_name(Window w, char **name) {
+  Atom prop = XInternAtom(dpy, "_NET_WM_NAME", False);
+  Atom utf8_type = XInternAtom(dpy, "UTF8_STRING", False);
+  Atom actual_type;
+  int actual_format;
+  unsigned long nitems;
+  unsigned long leftover;
+  char *data = NULL;
+  Status ret;
+
+  set_ignore(dpy, NextRequest(dpy));
+  if (Success != (ret = XGetWindowProperty(dpy, w, prop, 0L, (long) BUFSIZ,
+        False, utf8_type, &actual_type, &actual_format, &nitems,
+        &leftover, (unsigned char **) &data))) {
+    if (BadWindow == ret)
+      return 0;
+   set_ignore(dpy, NextRequest(dpy));
+    printf("Window %#010lx: _NET_WM_NAME unset, falling back to WM_NAME.\n", w);
+    if (!XFetchName(dpy, w, &data))
+      return 0;
+  }
+  // if (actual_type == utf8_type && actual_format == 8)
+  *name = (char *) data;
+  return 1;
+}
+
 static int
 ev_serial(XEvent *ev) {
-  if (ev->type & 0x7f != KeymapNotify) {
+  if ((ev->type & 0x7f) != KeymapNotify) {
     return ev->xany.serial;
   }
   return NextRequest(ev->xany.display);
@@ -1983,8 +2089,16 @@ static char *
 ev_name(XEvent *ev) {
   static char buf[128];
   switch (ev->type & 0x7f) {
-    case Expose:
-      return "Expose";
+    case FocusIn:
+      return "FocusIn";
+    case FocusOut:
+      return "FocusOut";
+    case CreateNotify:
+      return "CreateNotify";
+    case ConfigureNotify:
+      return "ConfigureNotify";
+    case DestroyNotify:
+      return "DestroyNotify";
     case MapNotify:
       return "Map";
     case UnmapNotify:
@@ -1993,10 +2107,16 @@ ev_name(XEvent *ev) {
       return "Reparent";
     case CirculateNotify:
       return "Circulate";
+    case Expose:
+      return "Expose";
+    case PropertyNotify:
+      return "PropertyNotify";
     default:
       if (ev->type == damage_event + XDamageNotify) {
         return "Damage";
       }
+      if (shape_exists && ev->type == shape_event)
+        return "ShapeNotify";
       sprintf(buf, "Event %d", ev->type);
       return buf;
   }
@@ -2005,8 +2125,13 @@ ev_name(XEvent *ev) {
 static Window
 ev_window(XEvent *ev) {
   switch (ev->type) {
-    case Expose:
-      return ev->xexpose.window;
+    case FocusIn:
+    case FocusOut:
+      return ev->xfocus.window;
+    case CreateNotify:
+      return ev->xcreatewindow.window;
+    case ConfigureNotify:
+      return ev->xconfigure.window;
     case MapNotify:
       return ev->xmap.window;
     case UnmapNotify:
@@ -2015,10 +2140,16 @@ ev_window(XEvent *ev) {
       return ev->xreparent.window;
     case CirculateNotify:
       return ev->xcirculate.window;
+    case Expose:
+      return ev->xexpose.window;
+    case PropertyNotify:
+      return ev->xproperty.window;
     default:
       if (ev->type == damage_event + XDamageNotify) {
         return ((XDamageNotifyEvent *)ev)->drawable;
       }
+      if (shape_exists && ev->type == shape_event)
+        return ((XShapeEvent *) ev)->window;
       return 0;
   }
 }
@@ -2064,6 +2195,9 @@ ev_create_notify(XCreateWindowEvent *ev) {

 inline static void
 ev_configure_notify(XConfigureEvent *ev) {
+#if DEBUG_EVENTS
+  printf("{ send_event: %d, above: %08lx, override_redirect: %d }\n", ev->send_event, ev->above, ev->override_redirect);
+#endif
   configure_win(dpy, ev);
 }

@@ -2164,16 +2298,56 @@ ev_damage_notify(XDamageNotifyEvent *ev) {
   damage_win(dpy, ev);
 }

+static void ev_shape_notify(XShapeEvent *ev) {
+  win *w = find_win(dpy, ev->window);
+
+  /*
+   * Empty border size may indicated an
+   * unmapped/destoried window, in which case
+   * seemingly BadRegion errors would be triggered
+   * if we attempt to rebuild border_size
+   */
+  if (w->border_size) {
+    // Mark the old border_size as damaged
+    add_damage(dpy, w->border_size);
+
+    w->border_size = border_size(dpy, w);
+
+    // Mark the new border_size as damaged
+    add_damage(dpy, copy_region(dpy, w->border_size));
+  }
+
+}
+
 inline static void
 ev_handle(XEvent *ev) {
+
+#if DEBUG_EVENTS
+  Window w;
+  char *window_name;
+  Bool to_free = False;
+#endif
+
   if ((ev->type & 0x7f) != KeymapNotify) {
     discard_ignore(dpy, ev->xany.serial);
   }

 #if DEBUG_EVENTS
+  w = ev_window(ev);
+  window_name = "(Failed to get title)";
+  if (w) {
+    if (root == w)
+      window_name = "(Root window)";
+    else
+      to_free = window_get_name(w, &window_name);
+  }
   if (ev->type != damage_event + XDamageNotify) {
-    printf("event %10.10s serial 0x%08x window 0x%08x\n",
-      ev_name(ev), ev_serial(ev), ev_window(ev));
+    printf("event %10.10s serial %#010x window %#010lx \"%s\"\n",
+      ev_name(ev), ev_serial(ev), w, window_name);
+  }
+  if (to_free) {
+    XFree(window_name);
+    window_name = NULL;
   }
 #endif

@@ -2212,6 +2386,10 @@ ev_handle(XEvent *ev) {
       ev_property_notify((XPropertyEvent *)ev);
       break;
     default:
+    if (shape_exists && ev->type == shape_event) {
+      ev_shape_notify((XShapeEvent *) ev);
+      break;
+    }
       if (ev->type == damage_event + XDamageNotify) {
         ev_damage_notify((XDamageNotifyEvent *)ev);
       }
@@ -2524,6 +2702,9 @@ main(int argc, char **argv) {
     exit(1);
   }

+  if (!XShapeQueryExtension(dpy, &shape_event, &shape_error))
+    shape_exists = False;
+
   register_cm(scr);

   if (fork_after_register) fork_after();
diff --git a/src/compton.h b/src/compton.h
index 18e3859..6f30391 100644
--- a/src/compton.h
+++ b/src/compton.h
@@ -20,6 +20,7 @@
 #include <X11/extensions/Xcomposite.h>
 #include <X11/extensions/Xdamage.h>
 #include <X11/extensions/Xrender.h>
+#include <X11/extensions/shape.h>

 #if COMPOSITE_MAJOR > 0 || COMPOSITE_MINOR >= 2
 #define HAS_NAME_WINDOW_PIXMAP 1
@@ -28,6 +29,7 @@
 #define CAN_DO_USABLE 0
 #define DEBUG_REPAINT 0
 #define DEBUG_EVENTS 0
+#define DEBUG_RESTACK 0
 #define DEBUG_WINTYPE 0
 #define MONITOR_REPAINT 0

@@ -124,6 +126,7 @@ typedef struct _fade {
   Display *dpy;
 } fade;

+extern int root_height, root_width;
 /**
  * Functions
  */
@@ -345,6 +348,42 @@ ev_property_notify(XPropertyEvent *ev);
 inline static void
 ev_damage_notify(XDamageNotifyEvent *ev);

+/**
+ * Destory the cached border_size of a window.
+ */
+inline static void win_free_border_size(Display *dpy, win *w) {
+  if (w->border_size) {
+    set_ignore(dpy, NextRequest(dpy));
+    XFixesDestroyRegion(dpy, w->border_size);
+    w->border_size = None;
+  }
+}
+
+/**
+ * Get a region of the screen size.
+ */
+inline static XserverRegion get_screen_region(Display *dpy) {
+  XRectangle r;
+
+  r.x = 0;
+  r.y = 0;
+  r.width = root_width;
+  r.height = root_height;
+  return XFixesCreateRegion(dpy, &r, 1);
+}
+
+/**
+ * Copies a region
+ */
+inline static XserverRegion copy_region(Display *dpy,
+    XserverRegion oldregion) {
+  XserverRegion region = XFixesCreateRegion(dpy, NULL, 0);
+
+  XFixesCopyRegion(dpy, region, oldregion);
+
+  return region;
+}
+
 inline static void
 ev_handle(XEvent *ev);

It adds another dependency, libXext, for the X Shape extension function calls, which I suppose every sane X user has installed. I tried to make it work without X Shape extension, yet I did not test the support.

As the issues are tied to timing, I could not state that this patch absolutely resolves the issues. I could only confirm that I tried 30+ times without being able to reproduce the issue.

I personally use DoxyGen comment format. Change it to other styles if you don't like it.

A depressing news is, I rewrote paint_all() to adapt the new painting sequence, and so far it does not render shaped windows correctly if I don't use the bounding regions. Much annoyance with how XRenderCreatePicture() and XRenderComposite() works. I'm still looking into the exact reasons.

By the way, I discovered two possible bugs in compton:

  1. If you set transparency of a window before compton starts running with compton-trans, after you run compton the window is rendered solid, i.e. compton-trans looks only effective for compton when it's running. xcompmgr respects transparency set when it's not running;
  2. When you make a specially shaped window (I triggered it with oclock) transparent, compton will render the completely tranparent parts (the corners of an oclock window, for example) incorrectly. xcompmgr doesn't show anything wrong. It's probably related to why my rewritting paint_all() does not work.

I don't have to time to look at them today. Too sleepy.