GNOME Bugzilla – Bug 64613
focus handling (OutputOnly, ICCCM compliance)
Last modified: 2011-02-04 16:12:26 UTC
There recently was some discussion of ICCCM focus handling on gtk-devel and it was found that gtk windows always employ the ICCCM "Locally Active" focus handling model. There apparently no way to create unfocusable (ie ICCCM "No Input" model) windows, short of falling back to Xlib methods. I also noticed that there are two places where gtk/gdk call XSetInputFocus with arguments which violate ICCCM conventions: In gdkwindow-x11.c (gdk_window_focus): XRaiseWindow (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XID (window)); /* There is no way of knowing reliably whether we are viewable so we need * to trap errors so we don't cause a BadMatch. */ gdk_error_trap_push (); XSetInputFocus (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XWINDOW (window), RevertToNone, timestamp); XSync (GDK_WINDOW_XDISPLAY (window), False); gdk_error_trap_pop (); This is against section 4.1.7 of the ICCCM, which states that Clients that invoke a SetInputFocus request should set the revert-to argument to Parent . Looking further, I find that gdk_window_focus() is called from gtk_window_present() in the following way: gdk_window_focus (widget->window, gtk_get_current_event_time()) and gtk_get_current_event_time() may return CurrentTime if there is no current event. But section 4.1.7 states that Clients that use a SetInputFocus request must set the time field to the timestamp of the event that caused them to make the attempt. This cannot be a FocusIn event because they do not have timestamps. Clients may also acquire the focus without a corresponding EnterNotify . Note that clients must not use CurrentTime in the time field.
This is not actually a big deal. The problem case outlined in the ICCCM is a reparenting WM which doesn't use save sets and then crashes after the client has set the focus with revert-to None. Quite theoretical, and looking at the X protocol specification of revert-to Parent, it is not all that much safer, because the revert-to value will change to None when the focus does actually revert to a anchestor window. Thus, even with revert-to Parent, we can end up with focus being None, it just takes to reverting steps instead of one.
Just committed a fix for the RevertToParent thing. Mon Nov 19 13:56:45 2001 Owen Taylor <otaylor@redhat.com> * gdk/x11/gdkwindow-x11.c (gdk_window_focus): Use RevertToParent, not RevertToNone. (#64613, Matthias Clasen) I'm not worried about the timestamp issue, really. I think the ICCCM should be read as "clients must not use CurrentTime if they have any meaningful other time". If I'm presenting the window in response to some incoming network traffic, there are no race conditions to worry about. One small problem you _can_ get is if the app itself first sets the input focus with CurrentTime and then sets the input focus to another window with a real (older timestamp). What we do in GtkClipboard is retrieve real time stamps from the server instead of using the CurrentTime and then enforce increasing timestamps for all selection claiming from within the app. This could be done here, but I have a hard time coming with real examples where it would matter, and I'd rather it keep it simple until someone has problems. The OutputOnly issue is a API issue, and thus 2.2 fodder. Until then, people will have to resort to Xlib.
Adding 'accessibility' keyword since assistive technologies like magnifiers and onscreen keyboards frequently require output-only windows.
The necessary api here would probably be some boolean property "accept_toplevel_focus" on GtkWindow plus the necessary getters/setters to propagate this down to where gdk calls XSetWMHints.
*** Bug 126727 has been marked as a duplicate of this bug. ***
changing status whiteboard to 'AP0' since this has become a complete blocker for GOK, due to changes in gtk+ internals.
Here is a proposal by Gregory Merchan to solve this by switching to the globally active model for all gtk windows. "No Input" windows can then be realized by g_signal_connect (GTK_WINDOW (window), "take-focus-event", G_CALLBACK (gtk_true), NULL); Gregory wants to use the globally active model for better handling of focus in connection with DND, see http://www.phys.lsu.edu/students/merchan/GNOME/Recommendations/DnD/
Created attachment 21671 [details] [review] globally active input
When I applied the attached patch I found that the gok main window no longer receives focus. Is this expected? I had thought from the previous comment that I would need to connect to the "take-focus-event" signal.
No, this should not be the case. Everything should work as before, unless you connect to the take_focus_event signal. Could you perhaps debug where it is failing ? Here is what should happen if you don't have a signal handler: 1. wm sends WM_TAKE_FOCUS 2. gdk wm protocol handler converts this into a GdkTakeFocus event 3. default handling in gtkmain.c for GTK_TAKE_FOCUS calls gtk_window_take_focus() 4. which calls gdk_window_take_focus() 5. which calls _gdk_x11_safe_set_input_focus() (or similar) 6. which performs the async equivalent of XSetInputFocus the steps 2 - 4 are new here, previously the gdk wm protocol handler would directly call _gdk_x11_safe_set_input_focus() Oh, you should also verify that the GOK window is actually using the globally active model now: xprop WM_PROTOCOLS should list WM_TAKE_FOCUS and xprop WM_HINTS should state that the InputHint is set to false.
gok calls gdk_window_add_filter and in the filter function checks whether a WM_TAKE_FOCUS message was sent. This seems to be an alternative to specifing gtk_true as a "take-focus-event" signal handler.
As long as you manage to ignore the WM_TAKE_FOCUS message...
yes, what we're doing is equivalent apparently to attaching gtk_true to the wm-take-focus signal, if the above patch is applied. The problem is that the current gdk model doesn't emit wm-take-focus and thus the only way for us to explicitly reject focus is via WM_TAKE_FOCUS, with a "no input" hint. gdk is currently overwriting the "no input" hint and there's no way to explicitly tell gdk/gtk+ that a window should be no-input. I think that the reason the patch makes our WM_TAKE_FOCUS code work again has to do with the changes to gdk's input hint management. Attaching to wm-take-focus as a gsignal is certainly cleaner than doing the Xlib calls and ought to be more maintainable.
Yes, exactly. Setting the input hint to false is what makes this work, since it instructs the wm to not call XSetInputFocus on its own.
* What about portability? Is this setup really going to work on Win32 / OS-X / etc? * What's the use case for doing anything other than simply not focusing the window? If that's the only use case, shouldn't we go with a simpler API?
* What about portability? Is this setup really going to work on Win32 / OS-X / etc? No idea if it is possible to intercept focus-setting in this way on Win32. But if it isn't, nothing should break. Apps have to handle being given the focus anyway. They just won't get any TakeFocus events on Win32 then. It's certainly going to work on XDarwin, and I don't think we should worry about a native OS-X port at this point, should we ? * What's the use case for doing anything other than simply not focusing the window? If that's the only use case, shouldn't we go with a simpler API? Have you looked at Gregorys ideas (see the link above) ?
But we can certainly add a simple no-input api now and consider going for globally active later. I'll try to come up with an alternative patch.
Created attachment 22113 [details] [review] alternative patch
Here is an alternative patch which adds an accept_focus property on gtkwindow plus two ways to set it on gdkwindow. Note that I still use the globally active model, but ignore WM_TAKE_FOCUS to implement no-input, since the actual no-input model seems to work less well with the wms I tried. I guess other wms will have problems with the globally active model...
cool, thanks a _lot_ for working on this Matthias. The API patch would solve the GOK problem too (as would the globally-active model patch).
The point about the take-focus not being portable is that making a window non-focusable *is* possible on windows, but you need to do it a different way. I don't think the original patch is enough to do Gregory's ideas, becuase it doesn't address the question of how the window is getting the focus ... you can't distinguish say, a user clicking on the titlebar then clicking on the content area, from a click drag on the content. You'd need some sort of protocol which said "here's a button press event that you may want to use to start the focus" - some of Gregory's later comments talk about using unused fields in WM_TAKE_FOCUS to do this. Patch comments on the simple patch: * "should receive input focus" would read better as "the input focus", and the documentation needs to explain what the input focus is. * priv->accept_focus = 1; should be '= TRUE' * gtk_window_set_accept_focus() docs shouldn't reference the window manager, since that isn't a cross-platform concept. And "toggles" isn't really an appropriate term here - I'd take toggle to mean a function that switches a setting every time you call it. * I probably wouldn't bother adding the setting to GdkWindowAttributes; setting it from the default value is rare enough that an extra X request won't hurt. BUt if you did, then gtk_window_realize() should take advantage of that. * Cross-platform handling: Needs at least stubs for win32 and linux-fb so they keep compiling Should be pretty easy to implement for linux-fb, since all window "management" is done inside GTK+ Probably best to simply file a bug to implement for win32; I think it's trivial but needs to be done by someone that can test.
I went ahead and committed a revised patch; I'll attach it below. I was a bit at a loss how to explain "input focus". I've removed the GdkWindowAttr additions and added stubs for Win32 and linux-fb. I've filed bug 129045 and bug 129043 to track the actual implementation of "no input" windows for these backends.
Created attachment 22314 [details] [review] patch as committed
I find that I still need to call XSetWMHints for WM_TAKE_FOCUS or else clicking on the window title focuses the window. Should this be dealt with in gdk_window_set_accept_focus()?
Test case?
Hi Owen: I know short unencumbered test cases are preferred. But if you cvs co GOK and build it, you can see the issue by removing the WM_TAKE_FOCUS call in callbacks.c (on_window1_realize) and "clicking around a bit".
Bill, Padraig, can you verify that the gok window is actually switched to globally active ? See some comments above for what to look for.
And maybe also check if calling gtk_window_set_accept_focus() separately works better, either before or after realizing the window ?
you've confused me: I thought the purpose of the patch was to prevent the need to make windows globally active, ie. gdk is responsible for not focussing them. Note that things 'mostly' work when setting the property using g_object_connect, so I don't see how calling set_accept_focus would have a different effect. i.e. the property is definitely getting set, but correct behavior depends on also using WM_TAKE_FOCUS in the client. So it means that ATM the client still must use Xlib calls in order to do the right thing.
I have confirmed that calling gtk_window_set_accept_focus in the 'realize' handler doesn't help. But I understand the problem we are seeing: we call XSetWMProtocols() which resets what gtk+ has already done; therefore we must add WM_TAKE_FOCUS as one of those protocols. But I think we no longer need the above Xlib call at all, so I will remove it from GOK. IOW I think this is client code error.
Yes, if you call XSetWMProtocols() you must take care not to remove WM_TAKE_FOCUS, since that would change the input model gdk has selected. Regarding globally active: My superficial tests showed that some window managers seem to have problems with not giving focus to "no input" windows, while they don't have a problem to accept that a "globally active" window doesn't take focus. Therefore I've implemented "no input" by making the window globally active and never take focus when the wm sends WM_TAKE_FOCUS.