'Can I change the focus behavior of child windows?
Using fairly run of the mill window creation code for child windows I get (the infamous) "focus follows mouse" behavior in Xwindow, that is, the keyboard entries follow what window the mouse is over:
X Event: 40 Window: 4e00002 FocusIn
X Event: 40 Window: 4e00002 PropertyNotify: atom: _NET_WM_STATE
X Event: 40 Window: 4e00002 PropertyNotify: atom: _GTK_EDGE_CONSTRAINTS
X Event: 40 Window: 4e00002 KeyPress
X Event: 40 Window: 4e00003 KeyPress
That is, starting with clicking the parent window I get FocusIn, then Keypress to window 1 when the mouse is over the parent, then KeyPress to window 2 when the mouse is over the child window, without clicking the child window (see example program below).
This is "focus (keyboard entry) follows mouse" behavior, even if the rest of the system is using "click to focus" behavior. Is this some kind of default, and do I need to make call or provide a parameter to override it?
Looking at the GTK examples, they clearly show child windows, say GtkEntry (text box entry) shows "click to focus" behavior, you must click the text entry box to enter text keys, and moving the mouse outside of the child window does not change that.
Thanks.
Scott Franco system: Ubuntu 20.04 Windows manager: GDM3
Afternote: I modified the program for the suggestion below to capture "WM_TAKE_FOCUS" events. It prints them. Otherwise unchanged. I set the WM_TAKE_FOCUS protocol into each of the two windows. However, each of the take focus events goes to window 1 (the root window) even when clicking into the child window. I was able to prove that by turning on pointer motions, and the trace clearly shows that we are in the child window, 0x3a00003, but the take focus is sent to the root, 0x3a00002.
Trace:
X Event: 44 Window: 3a00003 MotionNotify: x: 329 y: 77
X Event: 44 Window: 3a00003 MotionNotify: x: 329 y: 76
X Event: 44 Window: 3a00003 MotionNotify: x: 329 y: 75
X Event: 44 Window: 3a00003 MotionNotify: x: 329 y: 75
X Event: 44 Window: 3a00002 ClientMessage
Window take focus
What am I missing here?
/*
* Study for Xwindow events.
*/
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#define BACKGROUND_PAINT
void prtxevtt(int type)
{
switch (type) {
case 2: fprintf(stderr, "KeyPress"); break;
case 3: fprintf(stderr, "KeyRelease"); break;
case 4: fprintf(stderr, "ButtonPress"); break;
case 5: fprintf(stderr, "ButtonRelease"); break;
case 6: fprintf(stderr, "MotionNotify"); break;
case 7: fprintf(stderr, "EnterNotify"); break;
case 8: fprintf(stderr, "LeaveNotify"); break;
case 9: fprintf(stderr, "FocusIn"); break;
case 10: fprintf(stderr, "FocusOut"); break;
case 11: fprintf(stderr, "KeymapNotify"); break;
case 12: fprintf(stderr, "Expose"); break;
case 13: fprintf(stderr, "GraphicsExpose"); break;
case 14: fprintf(stderr, "NoExpose"); break;
case 15: fprintf(stderr, "VisibilityNotify"); break;
case 16: fprintf(stderr, "CreateNotify"); break;
case 17: fprintf(stderr, "DestroyNotify"); break;
case 18: fprintf(stderr, "UnmapNotify"); break;
case 19: fprintf(stderr, "MapNotify"); break;
case 20: fprintf(stderr, "MapRequest"); break;
case 21: fprintf(stderr, "ReparentNotify"); break;
case 22: fprintf(stderr, "ConfigureNotify"); break;
case 23: fprintf(stderr, "ConfigureRequest"); break;
case 24: fprintf(stderr, "GravityNotify"); break;
case 25: fprintf(stderr, "ResizeRequest"); break;
case 26: fprintf(stderr, "CirculateNotify"); break;
case 27: fprintf(stderr, "CirculateRequest"); break;
case 28: fprintf(stderr, "PropertyNotify"); break;
case 29: fprintf(stderr, "SelectionClear"); break;
case 30: fprintf(stderr, "SelectionRequest"); break;
case 31: fprintf(stderr, "SelectionNotify"); break;
case 32: fprintf(stderr, "ColormapNotify"); break;
case 33: fprintf(stderr, "ClientMessage"); break;
case 34: fprintf(stderr, "MappingNotify"); break;
case 35: fprintf(stderr, "GenericEvent"); break;
default: fprintf(stderr, "???"); break;
}
}
void prtxevt(Display* d, XEvent* e)
{
fprintf(stderr, "X Event: %5ld Window: %lx ", e->xany.serial,
e->xany.window);
prtxevtt(e->type);
switch (e->type) {
case Expose: fprintf(stderr, ": x: %d y: %d w: %d h: %d",
e->xexpose.x, e->xexpose.y,
e->xexpose.width, e->xexpose.height); break;
case ConfigureNotify: fprintf(stderr, ": x: %d y: %d w: %d h: %d",
e->xconfigure.x, e->xconfigure.y,
e->xconfigure.width, e->xconfigure.height); break;
case MotionNotify: fprintf(stderr, ": x: %d y: %d",
e->xmotion.x, e->xmotion.y); break;
case PropertyNotify: fprintf(stderr, ": atom: %s",
XGetAtomName(d, e->xproperty.atom));
}
fprintf(stderr, "\n"); fflush(stderr);
}
int main(void) {
Window w1, w2;
GC gracxt;
XEvent e;
const char* msg1 = "Hello, window 1";
const char* msg2 = "Hello, window 2";
int s;
Display* d;
XFontStruct* font;
Atom wm_take_focus;
d = XOpenDisplay(NULL);
if (d == NULL) {
fprintf(stderr, "Cannot open display\n");
exit(1);
}
s = DefaultScreen(d);
font = XLoadQueryFont(d,
"-bitstream-courier 10 pitch-bold-r-normal--0-0-200-200-m-0-iso8859-1");
if (!font) {
fprintf(stderr, "*** No font ***\n");
exit(1);
}
gracxt = XDefaultGC(d, s);
XSetFont(d, gracxt, font->fid);
w1 = XCreateWindow(d, RootWindow(d, s), 0, 0, 1000, 1000, 0, CopyFromParent,
InputOutput, CopyFromParent, 0, NULL);
XSelectInput(d, w1, ExposureMask|KeyPressMask|PointerMotionMask|
StructureNotifyMask|PropertyChangeMask|FocusChangeMask/*|ButtonPressMask*/);
XMapWindow(d, w1);
printf("w1: %08lx\n", w1);
wm_take_focus = XInternAtom(d, "WM_TAKE_FOCUS", False);
XSetWMProtocols(d, w1, &wm_take_focus, 1);
w2 = XCreateWindow(d, /*RootWindow(d, s)*/w1, 100, 100, 400, 100, 0, CopyFromParent,
InputOutput, CopyFromParent, 0, NULL);
XSelectInput(d, w2, ExposureMask|KeyPressMask|PointerMotionMask|
StructureNotifyMask|PropertyChangeMask|FocusChangeMask/*|ButtonPressMask*/);
XMapWindow(d, w2);
printf("w2: %08lx\n", w2);
XSetWMProtocols(d, w2, &wm_take_focus, 1);
while (1) {
XNextEvent(d, &e);
prtxevt(d, &e);
if (e.type == Expose && e.xany.window == w1) {
XSetForeground(d, gracxt, WhitePixel(d, s));
XFillRectangle(d, e.xany.window, gracxt,
e.xexpose.x, e.xexpose.y,
e.xexpose.width, e.xexpose.height);
XSetForeground(d, gracxt, BlackPixel(d, s));
XDrawString(d, e.xany.window, gracxt, 10, 50, msg1, strlen(msg1));
} else if (e.type == Expose && e.xany.window == w2) {
XSetForeground(d, gracxt, 0x00ffff/*WhitePixel(d, s)*/);
XFillRectangle(d, e.xany.window, gracxt,
e.xexpose.x, e.xexpose.y,
e.xexpose.width, e.xexpose.height);
XSetForeground(d, gracxt, BlackPixel(d, s));
XDrawString(d, e.xany.window, gracxt, 10, 50, msg2, strlen(msg2));
} else if (e.type == ClientMessage) {
if ((Atom)e.xclient.data.l[0] == wm_take_focus)
printf("Window take focus\n");
}
}
XCloseDisplay(d);
return 0;
}
Solution 1:[1]
I found this looking for an answer and was disappointed to see none.... but I think I found some answers for both of us, so I hope you'll be patient reading this since I want to give background and other info for future web searchers. It is kinda hard to find some of these lower level X tricks and knowledge so I'll write a bit more than strictly necessary..
X has two focuses: the pointer focus and the explicit focus. You'll see the event.xfocus.detail member in the FocusIn/Out events holds this, NotifyPointer vs the others. The pointer focus always follows the pointer, the explicit focus is only set by XSetInputFocus, which is traditionally done only be either the window manager (including on most "sloppy focus" models by the window manager btw) or by your own top level application upon request by the window manager (when you receive a WM_TAKE_FOCUS client message, then you can give the focus to your child. you must opt into this similarly to how you opt into the close button with the WM_DELETE_WINDOW atom, you didn't do this in the sample, but tutorials for this are on the web).
The PointerNotify is kinda weird... move your child window over to x = 0 and then bring the mouse in from the left side of the window. It will sometimes notify that... at least with my window manager. I think the X server only does that notification if the focus is NOT coming from the parent window, and since the window manager is a bit async, there's a race condition there with my one pixel border. But you might never see this if your system is even a little different.
But regardless of the message coming or not, the behavior still happens: the explicit focus is on the parent, but the pointer focus goes to the bottom most child that is interested in key events. This part is very important: if you do not subscribe to the KeyPressMask, the event will propagate up to the parent!
So for your case, just don't add KeyPressMask to the XSelectInput on the child window. Then the events go up to the parent. Relatively simple - just manage it all from the parent by not asking for it from the child.
If you did manage it from the child, you'd want to respond to WM_TAKE_FOCUS by calling XSetInputFocus on the relevant child - this way the window manager delegates to you who passes it to your child - but then XSelectInput it and process them all in the top. So you get a KeyPressEvent, see the child, then you can dispatch it however you want in your code. Once you set an explicit focus to the child, that child will be issued the events, even of the mouse moves (unless the mouse moves over one of its own children again!)
I don't know which approach GTK takes. My suspicion is they simply don't XSelectInput key press on the child since that's the easiest thing to do.
Now, why did this NOT answer my question that led me to this search in the first place? In my case, the child window is run my another X connection (specifically, I'm wrapping a chromium embedded framework window in my own window, so CEF has its own X display connection and creates its own window, but there's other cases too, like taskbar notification area icons. In this case, the other connection has already claimed KeyPress events, so they will go to the child if the pointer is over it. But there is a solution: remember how those pointer events only happen in lateral changes? And how events will be sent through a parent? The XEmbed spec uses this to get a clever answer:
https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
You use XSetInputFocus on that WM_TAKE_FOCUS... but you hide the window. Let me quote from that link:
In detail, the topmost embedder creates a not-visible X Window to hold the focus, the focus proxy. (It might be a 1x1 child window of toplevel located at -1,-1.) Since the focus proxy isn't an ancestor of the client window, the X focus can never move into the client window because of the mouse pointer location. In other words, whenever the outer window is activated (receives the X input focus), it has to put the X focus on the FocusProxy by calling XSetInputFocus().
How do you forward keypress events? The link covers that too!
Key events are forwarded by changing the event's window field to the window handle of the client and sending the modified message via XSendEvent() to the embedder, with no event mask and propagation turned off.
As of this writing, I haven't actually attempted this with my CEF use case, it is possible they filter synthetic events like this, but the approach sounds pretty promising.
Those freedesktop specs are not always easy to read, but they are a good source of valuable clues - as well as actual interop standards - for how these things actually work.
Sorry for rambling a bit, but I hope this is helpful to you. This is a week after I first found this link which is already a ways after you asked... looking back OF COURSE i should have checked the XEmbed spec, but it just didn't come to mind until after a lot of frustration and fruitless searches.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Adam D. Ruppe |

