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; }
Hi! Is the testcase still valid with KDE 4.5? Thanks!
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!
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?
I am curious if this is still relevant 10 years later?