Merge lp:~azzar1/compiz/fix-781931 into lp:compiz/0.9.9

Proposed by Andrea Azzarone
Status: Merged
Approved by: Andrea Azzarone
Approved revision: 3491
Merged at revision: 3490
Proposed branch: lp:~azzar1/compiz/fix-781931
Merge into: lp:compiz/0.9.9
Prerequisite: lp:~compiz-team/compiz/compiz.fix_1084096
Diff against target: 159 lines (+100/-1)
3 files modified
src/privatewindow.h (+1/-0)
src/window.cpp (+12/-1)
tests/system/xorg-gtest/tests/compiz_xorg_gtest_test_window_stacking.cpp (+87/-0)
To merge this branch: bzr merge lp:~azzar1/compiz/fix-781931
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Sam Spilsbury Approve
Review via email: mp+136746@code.launchpad.net

This proposal supersedes a proposal from 2012-11-27.

Description of the change

== Problem ==
Bug #781931: New windows are moved to front but don't take focus.

When a window is not allowed to get the focus Compiz should stack it below the active window. Due to avoidStackingRelativeTo Compiz fails to do it:

bool
PrivateWindow::avoidStackingRelativeTo (CompWindow *w)
{
    if (w->overrideRedirect ())
 return true;

    if (w->destroyed ())
 return true;

    if (!w->priv->shaded && !w->priv->pendingMaps)
    {
 if (!w->isViewable () || !w->isMapped ())
     return true;
    }

    return false;
}

This is because avoidStackingRelativeTo is called before the window get mapped, so it returns TRUE.

== Fix ==
This branch adds receivedMapRequestAndAwaitingMap.

== Test ==
Xorg-gtest added.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

This feels a bit weird in its current state and could unintentionally cause other problems. Give me a day to look through some of the surrounding code before we merge this.

review: Abstain
Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal
Download full text (4.4 KiB)

OK, I just need a little bit of clarification here:

avoidStackingRelativeTo is used to determine whether or not a sibling is not a good candidate to put a window above, so for newly mapped windows we won't put those above windows that are:

1. override redirect
2. pending destruction
3. unmapped, where those windows are also not shaded or waiting the event from an XMapWindow

It shouldn't ever be called on the window that is being restacked.

Now one situation where you might have broken behaviour is where you've got one window that is not yet mapped (because show () was never called on it), and then a new window is created and is put underneath it (because it isn't yet mapped). Then show () is called on the first window a little later

The only place where avoidStackingRelativeTo with the window as the "sibling" parameter is called is here:

CompWindow *
PrivateWindow::findValidStackSiblingBelow (CompWindow *w,
        CompWindow *sibling)
{
    CompWindow *lowest, *last, *p;

    /* check whether we're allowed to stack under a sibling by finding
     * the above 'sibling' and checking whether or not we're allowed
     * to stack under that - if not, then there is no valid sibling
     * underneath it */

    for (p = sibling; p; p = p->serverNext)
    {
 if (!avoidStackingRelativeTo (p))
 {
     if (!validSiblingBelow (p, w))
  return NULL;
     break;
 }
    }

And that would probably be called from here in updateAttributes:

 if (sibling &&
     (stackingMode == CompStackingUpdateModeInitialMapDeniedFocus))
 {
     CompWindow *p;

     for (p = sibling; p; p = p->serverPrev)
  if (p->priv->id == screen->activeWindow ())
      break;

     /* window is above active window so we should lower it,
      * assuing that is allowed (if, for example, our window has
      * the "above" state, then lowering beneath the active
      * window may not be allowed). */
     if (p && PrivateWindow::validSiblingBelow (p, this))

Where we try and find /a/ valid sibling below the current window that we can go above, and then try and go underneath the active window by finding a window beneath that which we can go above.

I still feel like this is strange:

    for (p = sibling; p; p = p->serverNext)
    {
 if (!avoidStackingRelativeTo (p))
 {
     if (!validSiblingBelow (p, w))
  return NULL;
     break;
 }
    }

That was introduced here:

------------------------------------------------------------
revno: 2609.6.31
committer: Sam Spilsbury <email address hidden>
timestamp: Tue 2010-12-07 17:08:11 +0800
message:
  Make sure findValidStackSibling below doesn't return a sibling that
  we're not allowed to stack under.

  Forward port 1baf514fefb9177764af99cf508596686583244e to master

Which is a port of:

commit 1baf514fefb9177764af99cf508596686583244e
Author: Danny Baumann <email address hidden>
Date: Mon Aug 31 09:31:43 2009 +0200

    Make sure findValidStackSibling below doesn't return a sibling that
    we're not allowed to stack under.
    If the passed sibling window is not allowed to stack under the passed
    window, returning any window under sibling would result in an invalid
    stacking order.

diff --git a/src/window.c b/src/window.c
...

Read more...

Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

(Also I forgot to mention - thank you very much for looking into this, its been on my list for a while and I've been stumped every time I looked at it, so we're on the right track).

Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

Ah okay, now that I've had a look into this, this comment here is misleading:

    /* check whether we're allowed to stack under a sibling by finding
     * the above 'sibling' and checking whether or not we're allowed
     * to stack under that - if not, then there is no valid sibling
     * underneath it */

It really should say

    /* check whether we're allowed to stack under a sibling checking if
     * this window, or any other window above it is permitted to stack
     * above this window */

So I think you're definitely on the right track.

My only concern is that changing pendingMaps might have some unwanted side effects. Code that relies on pendingMaps could assume that show () can been called on the window, which has a few other invariants too (eg, not hidden, not shaded etc).

What might be better is to add a new variable to track whether or not the window is currently in map processing (eg, receivedMapRequestAndAwaitingMap). Then you can check that variable in avoidStackingRelativeTo in order to disregard the fact that the window is actually unmapped. Ideally receivedMapRequestAndAwaitingMap should only be set if we were to actually call show () on the window later (eg, !initiallyMinimized && !(priv->state & CompWindowStateHiddenMask))

Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

(I note that writing tests with xorg-gtest won't work right now until https://code.launchpad.net/~compiz-team/compiz/compiz.fix_1084096/+merge/136676 is merged. CMake appears to have changed the behaviour of execute_process to require the caller to strip whitespace from the command output, and its build is currently broken)

Revision history for this message
Andrea Azzarone (azzar1) wrote : Posted in a previous version of this proposal

> Ah okay, now that I've had a look into this, this comment here is misleading:
>
> /* check whether we're allowed to stack under a sibling by finding
> * the above 'sibling' and checking whether or not we're allowed
> * to stack under that - if not, then there is no valid sibling
> * underneath it */
>
> It really should say
>
> /* check whether we're allowed to stack under a sibling checking if
> * this window, or any other window above it is permitted to stack
> * above this window */
>

I agree. I will update the comment too.

>
> So I think you're definitely on the right track.
>
> My only concern is that changing pendingMaps might have some unwanted side
> effects. Code that relies on pendingMaps could assume that show () can been
> called on the window, which has a few other invariants too (eg, not hidden,
> not shaded etc).
>
> What might be better is to add a new variable to track whether or not the
> window is currently in map processing (eg, receivedMapRequestAndAwaitingMap).
> Then you can check that variable in avoidStackingRelativeTo in order to
> disregard the fact that the window is actually unmapped. Ideally
> receivedMapRequestAndAwaitingMap should only be set if we were to actually
> call show () on the window later (eg, !initiallyMinimized && !(priv->state &
> CompWindowStateHiddenMask))

Thank you for the review/suggestion. It shounds good to me and make the code more understandable.

Btw I'm working on an xorg-test.

Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

I am liking this a lot :)

21 + if (!w->priv->receivedMapRequestAndAwaitingMap && !w->priv->shaded && !w->priv->pendingMaps)
22 {

You can probably put those on separate lines, or even

bool allowRelativeToUnmmaped = w->priv->receivedMapRequestAndAwaitingMap ||
                               w->priv->shaded ||
                               w->priv->pendingMaps;

if (!allowRelativeToUnmapped)
...

78 + void SetUserTime (Display *dpy, Window w, Time time)
79 + {
80 + Atom _NET_WM_USER_TIME = XInternAtom (dpy, "_NET_WM_USER_TIME", false);
81 + unsigned int value = (unsigned int) time;
82 +
83 + XChangeProperty (dpy, w,
84 + _NET_WM_USER_TIME,
85 + XA_CARDINAL, 32, PropModeReplace,
86 + (unsigned char *) &value, 1);
87 + }
88 +
89 + void SetClientLeader (Display *dpy, Window w, Window leader)
90 + {
91 + Atom WM_CLIENT_LEADER = XInternAtom (dpy, "WM_CLIENT_LEADER", false);
92 +
93 + XChangeProperty (dpy, w,
94 + WM_CLIENT_LEADER,
95 + XA_WINDOW, 32, PropModeReplace,
96 + (unsigned char *) &leader, 1);
97 + }
98 +

Thanks for providing these :)

Good use of ASSERT_TRUE (ct::WaitForEventOfTypeOnWindowMatching (dpy, DefaultRootWindow (dpy), PropertyNotify, -1, -1, matcher));

All in all I am liking this a lot :)

review: Approve
Revision history for this message
Sam Spilsbury (smspillaz) wrote :

As above, nailed it, well done.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Sam Spilsbury (smspillaz) wrote :

(A note that Andrea mentioned to me on IRC that he is going to convert the compound if statement into a bool, so probably don't merge this until then).

lp:~azzar1/compiz/fix-781931 updated
3491. By Andrea Azzarone

Minor change.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/privatewindow.h'
2--- src/privatewindow.h 2012-09-10 15:17:57 +0000
3+++ src/privatewindow.h 2012-11-29 08:18:24 +0000
4@@ -360,6 +360,7 @@
5 typedef std::pair <XWindowChanges, unsigned int> XWCValueMask;
6
7 compiz::X11::PendingEventQueue pendingConfigures;
8+ bool receivedMapRequestAndAwaitingMap;
9
10 char *startupId;
11 char *resName;
12
13=== modified file 'src/window.cpp'
14--- src/window.cpp 2012-11-15 05:47:13 +0000
15+++ src/window.cpp 2012-11-29 08:18:24 +0000
16@@ -2611,7 +2611,11 @@
17 if (w->destroyed ())
18 return true;
19
20- if (!w->priv->shaded && !w->priv->pendingMaps)
21+ bool allowRelativeToUnmmaped = w->priv->receivedMapRequestAndAwaitingMap ||
22+ w->priv->shaded ||
23+ w->priv->pendingMaps;
24+
25+ if (!allowRelativeToUnmmaped)
26 {
27 if (!w->isViewable () || !w->isMapped ())
28 return true;
29@@ -5424,6 +5428,9 @@
30
31 priv->managed = true;
32
33+ if (!initiallyMinimized && !(priv->state & CompWindowStateHiddenMask))
34+ receivedMapRequestAndAwaitingMap = true;
35+
36 if (!priv->placed)
37 {
38 int gravity = priv->sizeHints.win_gravity;
39@@ -5474,7 +5481,10 @@
40 screen->setCurrentDesktop (priv->desktop);
41
42 if (!(priv->state & CompWindowStateHiddenMask))
43+ {
44 show ();
45+ receivedMapRequestAndAwaitingMap = false;
46+ }
47
48 if (allowFocus)
49 {
50@@ -6244,6 +6254,7 @@
51 pendingUnmaps (0),
52 pendingMaps (0),
53 pendingConfigures (screen->dpy ()),
54+ receivedMapRequestAndAwaitingMap (false),
55
56 startupId (0),
57 resName (0),
58
59=== modified file 'tests/system/xorg-gtest/tests/compiz_xorg_gtest_test_window_stacking.cpp'
60--- tests/system/xorg-gtest/tests/compiz_xorg_gtest_test_window_stacking.cpp 2012-09-18 16:53:59 +0000
61+++ tests/system/xorg-gtest/tests/compiz_xorg_gtest_test_window_stacking.cpp 2012-11-29 08:18:24 +0000
62@@ -91,6 +91,27 @@
63 return w;
64 }
65
66+ void SetUserTime (Display *dpy, Window w, Time time)
67+ {
68+ Atom _NET_WM_USER_TIME = XInternAtom (dpy, "_NET_WM_USER_TIME", false);
69+ unsigned int value = (unsigned int) time;
70+
71+ XChangeProperty (dpy, w,
72+ _NET_WM_USER_TIME,
73+ XA_CARDINAL, 32, PropModeReplace,
74+ (unsigned char *) &value, 1);
75+ }
76+
77+ void SetClientLeader (Display *dpy, Window w, Window leader)
78+ {
79+ Atom WM_CLIENT_LEADER = XInternAtom (dpy, "WM_CLIENT_LEADER", false);
80+
81+ XChangeProperty (dpy, w,
82+ WM_CLIENT_LEADER,
83+ XA_WINDOW, 32, PropModeReplace,
84+ (unsigned char *) &leader, 1);
85+ }
86+
87 class PropertyNotifyXEventMatcher :
88 public ct::XEventMatcher
89 {
90@@ -215,3 +236,69 @@
91 EXPECT_EQ (*it++, w2); /* second should be the top normal window */
92 EXPECT_EQ (*it++, dock); /* dock must always be on top */
93 }
94+
95+TEST_F (CompizXorgSystemStackingTest, TestMapWindowWithOldUserTime)
96+{
97+ ::Display *dpy = Display ();
98+ PropertyNotifyXEventMatcher matcher (dpy, "_NET_CLIENT_LIST_STACKING");
99+
100+ Window w1 = CreateNormalWindow (dpy);
101+ Window w2 = CreateNormalWindow (dpy);
102+ Window w3 = CreateNormalWindow (dpy);
103+
104+ XMapRaised (dpy, w1);
105+ XMapRaised (dpy, w2);
106+
107+ /* Wait for property change notify on the root window to happen twice */
108+ ASSERT_TRUE (ct::WaitForEventOfTypeOnWindowMatching (dpy, DefaultRootWindow (dpy), PropertyNotify, -1, -1, matcher));
109+ ASSERT_TRUE (ct::WaitForEventOfTypeOnWindowMatching (dpy, DefaultRootWindow (dpy), PropertyNotify, -1, -1, matcher));
110+
111+ SetUserTime (dpy, w2, 200);
112+ SetClientLeader (dpy, w2, w2);
113+ SetUserTime (dpy, w3, 100);
114+ SetClientLeader (dpy, w3, w3);
115+
116+ XMapRaised (dpy, w3);
117+ ASSERT_TRUE (ct::WaitForEventOfTypeOnWindowMatching (dpy, DefaultRootWindow (dpy), PropertyNotify, -1, -1, matcher));
118+
119+ /* Check the client list to see that w2 > w3 > w1 */
120+ std::list <Window> clientList = ct::NET_CLIENT_LIST_STACKING (dpy);
121+ ASSERT_EQ (clientList.size (), 3);
122+
123+ std::list <Window>::iterator it = clientList.begin ();
124+ EXPECT_EQ (*it++, w1);
125+ EXPECT_EQ (*it++, w3);
126+ EXPECT_EQ (*it++, w2);
127+}
128+
129+TEST_F (CompizXorgSystemStackingTest, TestMapWindowAndDenyFocus)
130+{
131+ ::Display *dpy = Display ();
132+ PropertyNotifyXEventMatcher matcher (dpy, "_NET_CLIENT_LIST_STACKING");
133+
134+ Window w1 = CreateNormalWindow (dpy);
135+ Window w2 = CreateNormalWindow (dpy);
136+ Window w3 = CreateNormalWindow (dpy);
137+
138+ XMapRaised (dpy, w1);
139+ XMapRaised (dpy, w2);
140+
141+ /* Wait for property change notify on the root window to happen twice */
142+ ASSERT_TRUE (ct::WaitForEventOfTypeOnWindowMatching (dpy, DefaultRootWindow (dpy), PropertyNotify, -1, -1, matcher));
143+ ASSERT_TRUE (ct::WaitForEventOfTypeOnWindowMatching (dpy, DefaultRootWindow (dpy), PropertyNotify, -1, -1, matcher));
144+
145+ SetUserTime (dpy, w3, 0);
146+ SetClientLeader (dpy, w3, w3);
147+
148+ XMapRaised (dpy, w3);
149+ ASSERT_TRUE (ct::WaitForEventOfTypeOnWindowMatching (dpy, DefaultRootWindow (dpy), PropertyNotify, -1, -1, matcher));
150+
151+ /* Check the client list to see that w2 > w3 > w1 */
152+ std::list <Window> clientList = ct::NET_CLIENT_LIST_STACKING (dpy);
153+ ASSERT_EQ (clientList.size (), 3);
154+
155+ std::list <Window>::iterator it = clientList.begin ();
156+ EXPECT_EQ (*it++, w1);
157+ EXPECT_EQ (*it++, w3);
158+ EXPECT_EQ (*it++, w2);
159+}

Subscribers

People subscribed via source and target branches