Bug 150716 - two successive focus requests get in a race
Summary: two successive focus requests get in a race
Status: RESOLVED INTENTIONAL
Alias: None
Product: kwin
Classification: Plasma
Component: core (show other bugs)
Version: unspecified
Platform: Fedora RPMs Linux
: NOR normal
Target Milestone: ---
Assignee: KWin default assignee
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-10-11 16:27 UTC by Anton V. Tarasov
Modified: 2024-05-14 21:37 UTC (History)
5 users (show)

See Also:
Latest Commit:
Version Fixed In:
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Anton V. Tarasov 2007-10-11 16:27:34 UTC
Version:            (using KDE KDE 3.5.4)
Installed from:    Fedora RPMs
Compiler:          gcc 3.2.3 
OS:                Linux

There're two windows that have Global Active focus model set.
The windows is mapped one after another. 
On receiving WM_TAKE_FOCUS on the first window, focus is requested to it.
On receiving WM_TAKE_FOCUS on the second window, focus is requested to it
and immediately after that focus is requested back to the first window.
In all the cases the time stamp is taken from the WM_TAKE_FOCUS message.

A testcase is provided below.

When the windows are shown on the screen the behavior is as expected.
Focus returns back to the first window right after it is set on the second
window. Here's the output:

event 0: WM_TAKE_FOCUS on Win-1 [-1346778203]
Requesting focus to Win-1
event 1: FocusIn on Win-1
event 2: WM_TAKE_FOCUS on Win-2 [-1346778187]
Requesting focus to Win-2
Requesting focus to Win-1
event 3: FocusOut on Win-1
event 4: FocusIn on Win-2
event 5: FocusOut on Win-2
event 6: FocusIn on Win-1

Now if I click in the second window it all goes into an endless loop:

event 7: WM_TAKE_FOCUS on Win-2 [-1346126781]
Requesting focus to Win-2
Requesting focus to Win-1
event 8: ButtonPress on Win-2 [-1346126781]
event 9: FocusOut on Win-1
event 10: FocusIn on Win-2
event 11: FocusOut on Win-2
event 12: FocusIn on Win-1
event 13: WM_TAKE_FOCUS on Win-2 [-1346126671]
Requesting focus to Win-2
Requesting focus to Win-1
event 14: FocusOut on Win-1
event 15: FocusIn on Win-2
event 16: FocusOut on Win-2
event 17: FocusIn on Win-1
event 18: WM_TAKE_FOCUS on Win-2 [-1346126625]
Requesting focus to Win-2
Requesting focus to Win-1
event 19: FocusOut on Win-1
event 20: FocusIn on Win-2
event 21: FocusOut on Win-2
event 22: FocusIn on Win-1
<...>


It's not clear why Win-2 receives WM_TAKE_FOCUS the second time
and all the subsequent times (13, 18, etc) after it has been clicked.
With this message being sent only ones (as it's actually expected) all
would be fine...


Another question. Modifying this test so that it requests focus on Win-1
on receiving ButtonPress on Win-2 doesn't fix the problem. It's this
(moved XSetInputFocus(...win1...) from the ClientMessage case to
the ButtonPress case):

============================================
            case ButtonPress:
            {
                XButtonEvent *be = (XButtonEvent *)&event;
                fprintf(stderr, "event %i: ButtonPress on %s [%d]\n",
                        n, win2name(be->window), be->time);

                if (be->window == win2) {
                     fprintf(stderr, "Requesting focus to Win-1\n");
                     XSetInputFocus(display, win1, RevertToParent, be->time);
                }
                break;
            }
============================================

Here's what I got after I clicked Win-2:

event 9: WM_TAKE_FOCUS on Win-2 [-1345003390]
Requesting focus to Win-2
event 10: ButtonPress on Win-2 [-1345003390]
Requesting focus to Win-1
event 11: FocusOut on Win-1
event 12: FocusIn on Win-2
event 13: FocusOut on Win-2
event 14: FocusIn on Win-1
event 15: WM_TAKE_FOCUS on Win-2 [-1345003265]
Requesting focus to Win-2
event 16: FocusOut on Win-1
event 17: FocusIn on Win-2

After that I see Win-1 blinking in the taskbar. It's not focused.
The same problem: what's the cause of sending 15th event?



==========================
      The testcase
==========================

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <stdio.h>
#include <stdlib.h>

Display* display;
int screen_num;
Atom wm_protocols;
Atom wm_take_focus;
XWMHints wm_hints;
Window win1, win2;
char win1_name[] = "Win-1\0", win2_name[] = "Win-2\0";

Window
create_window(int x, int y, char* name)
{
    Window win =  XCreateSimpleWindow(display, RootWindow(display, screen_num),
                                      x, y, 200, 100, 2,
                                      BlackPixel(display, screen_num),
                                      WhitePixel(display, screen_num));

    if (!XSetWMProtocols(display, win, &wm_take_focus, 1)) {
        fprintf(stderr, "Cannot set the WM_TAKE_FOCUS protocol\n");
        exit(-1);
    }

    XSetWMHints(display, win, &wm_hints);
    XSelectInput(display, win, FocusChangeMask | ButtonPressMask);
    XStoreName(display, win, name);

    return win;
}

char* win2name(Window win)
{
    if (win == win1)
        return win1_name;
    else
        return win2_name;
}

int
main(int argc, char* argv[])
{
    char *display_name = getenv("DISPLAY");

    display = XOpenDisplay(display_name);
    if (display == NULL) {
        fprintf(stderr, "Cannot connect to X server '%s'\n", display_name);
        exit (-1);
    }

    screen_num = DefaultScreen(display);

    wm_protocols = XInternAtom(display, "WM_PROTOCOLS", True);    
    wm_take_focus = XInternAtom(display, "WM_TAKE_FOCUS", True);

    if (wm_protocols == None || wm_take_focus == None) {
        fprintf(stderr, "Cannot retrieve the atoms\n", argv[0]);
        exit(-1);
    }

    wm_hints.flags = InputHint;
    wm_hints.input = False;

    win1 = create_window(0, 0, win1_name);
    win2 = create_window(500, 0, win2_name);

    XMapWindow(display, win1);
    XSync(display, False);

    XMapWindow(display, win2);
    XSync(display, False);

    int n = 0;
    XEvent event;
    long time;
    while (True) {
        XNextEvent(display, &event);
        switch (event.type) {
            case ClientMessage:
            {
                XClientMessageEvent* cle = (XClientMessageEvent *)&event;
                if (cle->message_type == wm_protocols &&
                    cle->data.l[0] == wm_take_focus)
                {
                    time = cle->data.l[1];
                    fprintf(stderr, "event %i: WM_TAKE_FOCUS on %s [%d]\n",
                            n, win2name(cle->window), time);

                    fprintf(stderr, "Requesting focus to %s\n",
                            win2name(cle->window));
                    XSetInputFocus(display, cle->window, RevertToParent, time);

                    if (cle->window == win2) {
                        fprintf(stderr, "Requesting focus to Win-1\n");
                        XSetInputFocus(display, win1, RevertToParent, time);
                    }
                }
                break;
            }
            case FocusIn:
            {
                XFocusChangeEvent* fe = (XFocusChangeEvent *)&event;
                fprintf(stderr, "event %i: FocusIn on %s\n",
                        n, win2name(fe->window));
                break;
            }
            case FocusOut:
            {
                XFocusChangeEvent* fe = (XFocusChangeEvent *)&event;
                fprintf(stderr, "event %i: FocusOut on %s\n", n,
                        win2name(fe->window));
                break;
            }
            case ButtonPress:
            {
                XButtonEvent *be = (XButtonEvent *)&event;
                fprintf(stderr, "event %i: ButtonPress on %s [%d]\n",
                        n, win2name(be->window), be->time);
                break;
            }
            default:
                break;
        }
        n++;
    }

    XCloseDisplay(display);
    return 0;
}
Comment 1 FiNeX 2010-10-10 17:11:20 UTC
Hi! Is the testcase still valid with KDE 4.5? Thanks!
Comment 2 Anton V. Tarasov 2010-10-19 10:02:22 UTC
Hi FiNeX,

Unfortunately I don't have any KDE 4.5 nearby nor can I upgrade to it (due to some reasons). I'd really appreciate if you could run the testcase with that version on your own.

Thanks!
Comment 3 Thomas Lübking 2010-10-19 18:13:37 UTC
problem remains but the race ends after "some" iterations
it's a matter of the focus policy which (@ "Low", the default) in doubt chooses whether to allow focus changes depending on the timestamp.

Focus policies above low (>= medium) will entirely prevent the focus "stealing" of "Window #1" while "None" (the NETWM standard behaviour) will work correctly.

Lying about the second time (eg. "+100") would prevent the race condition, but not fix the behaviour.

To "fix" the testcase against the focus policies, it's sufficient to either
- ensure that the usertime stamp on the steal is more recent than the one on the activation //low

- propagate a relation between the windows by setting _NET_WM_PID's or eg. "Window #1" as "WM_CLIENT_LEADER" of "Window #2" // medium

- just sanitize the testcase ;-P - ie. hardcode "XSetInputFocus(display, win1, RevertToParent, time);" (but i guess the real case is more complex...)

----
Bug resolution:
a) this can be "worked around" by a focus rule ("none") for those clients
b) the implementation looks "wrong" to me, i'd just unconditionally set the focus on "Window 1" - also it's recursion prone...
c) However the (non standard, yet default) focusprotection + the client are in this case able to generate a race condition*, maybe kwin should attempt to detect this and give up after a while?

*client attempts to switch focus, kwin says no and sets back the focus, client re-attempts ...

So
- i can confirm the issue, but
- my personal feeling is not that this is a direct kwin bug (the implementation just clashes with kwin's fp attempts...), yet
- kwin should try to detect and prevent focus races (and by giving up, implicitly fix your case)

-> Lubos: got a comment?
Comment 4 Mathieu Jobin 2021-01-02 23:11:52 UTC
I am curious if this is still relevant 10 years later?