GNOME Bugzilla – Bug 488468
Mapping a transient window steals focus
Last modified: 2007-11-17 22:11:43 UTC
See the attached pygtk program. When a program pops up a transient window, it steals focus away from whatever currently has the focus. The focus should stay where it is. Otherwise, if a dialog comes up while you are typing something, you'll end up mistakenly typing in the dialog.
did you forget to attach it?
Is this a case of the application you're typing in popping up its own transient window? Or of a window that you're not typing in popping up a transient window? (Hard to tell without the program...)
Created attachment 97647 [details] transient-focus-steal.py Oops, sorry about forgetting the attachment :) Basically: 1. Run this program; it will pop up a window. 2. Immediately go back to typing in a terminal (or whatever). 3. The program will map a transient window, and you'll lose focus from your window in (2).
Ah, so you haven't typed or clicked in the main window of the new app? When you map the main window of the new app, there's no timestamp for it since the window isn't launched with startup-notification. Metacity gets no hint, so it has to assume that the window took no time at all to start up. That seems to work fine; you didn't complain that the main window took focus when it was launched. However, then you map another window--a transient to the main window. Since you didn't launch the application with startup-notification AND haven't clicked in the main window AND haven't typed in the main window, there is no timestamp associated with when the application was last used. Thus gtk+ can't guess at the user-interaction time associated with "launching" the transient window and provides no hint to metacity. metacity then falls back to assuming that the transient took no time at all to start up. Real fix: Use startup notification to launch your applications. Workaround for metacity: When a new window opens without any timestamp hint about what user-interaction time caused it to open, obtain a timestamp and stash it away. If the app launches any more transient windows later that also don't come with timestamp hints, use the stashed approximation timestamp from the parent window.
No, the problem is this. You are furiously typing somewhere. A program that was already running (gaim? firefox? your long-running software updater?) pops up a transient dialog ("software update done") and steals the focus. The example program is just an easy way to bring up a transient window. Ignore its initial window; it's just there to have something to be transient-for :)
You're suggesting this is a bug affecting all transients, always? That's not true in my testing...in fact, focus-stealing prevention works with the exact example program you provided so long as you make sure the main window has a timestamp. To duplicate the correct behavior with your program: Run the program you attached here. After the main window loads, it should have focus (unless you have strict focus on and launched from a terminal...in that case just manually give it focus). Type some characters while that window has focus. Then click on a window of some other application and start typing away. When the popup appears it won't get focus, as expected. (You may need to increase the timeout so you have time to type characters over the main window and then click on something else and start typing away). So, the explanation that fits so far is comment #4 -- you didn't launch the application with startup-notification AND haven't clicked in the main window or typed in it. I already suggested a possible workaround that we could provide for this case in comment #4 as well. The other possible explanations I can think of for your your long-running application cases you mentioned in comment 5 are (1) the last time you interacted with the app was over 25 days ago (i.e. that you're hitting the X timestamp wraparound issue), (2) the application is using sending a _NET_ACTIVE_WINDOW message without a valid timestamp, or (3) you use sloppy focus and some really old motif/lesstif apps a lot (or whatever older emacs was built with) -- you haven't actually clicked on emacs and typing in emacs doesn't update its user time since it doesn't support the spec so the window manager can't prevent other apps from stealing focus from it. I hope it's not case (1), because that isn't readily fixable; xserver timestamps sadly wraparound every 50 days or so. Case (2) is relatively likely. There was code to fix this, but the problem was some kind of backward compatibility concern, so we decided to delay pushing the fix. See the comment at lines 2800-2806 of metacity/src/window.c. I don't know the likelihood of case (3), but I hear that the more recent versions of emacs use gtk+ so it should avoid this problem, assuming there aren't also other obsolete apps you're using.
Elijah, thanks for the help. I don't understand what you are saying though :) -- or rather, I don't see how it applies to applications' developers. Please clarify at what side the issue lays. Failed attempts: * GtkWindow::set_focus_on_map (False): seems to set the WM_USER_TIME value to 0 which supposedly tells the window to not steal focus. Doesn't seem to have an effect on Metacity. It has reported to work some what on Compiz (it seems it sends the window to background in some cases though...) * Gtk::window_set_auto_startup_notification (False): the main window won't get focus, but transient windows do.
Created attachment 98319 [details] Adds an entry to the window
Elijah: * What focus mode are you using? I'm using sloppy focus, and the transient gets the focus (which is what I don't want). * How do you launch a program with startup notification? I.e. how do you make "python foo.py" start up like that? I created a launcher from Nautilus and the panel, and neither worked (the transient still gets focus). Then I added a "StartupNotify=true" to the .desktop files that got created, and it still didn't work. As a side question... IF focus-stealing prevention only works if apps cooperate, then it's pretty useless :) Could we have some heuristics like 1. The user was typing or clicking; store a timestamp. 2. A window gets MapRequest. It doesn't come with a _NET_WM_USER_TIME. Is it from the same client as (1)? If so, map it and give it the focus. Otherwise, just map it but don't give it the focus. Or if $current_time is greater than (timestamp_from_(1) + threshold), also give the focus to the new window.
Btw, it should be noted that the problem is with regard to Metacity 2.20. This worked okay on Metacity 2.18.
Created attachment 98734 [details] [review] metacity-net-wm-user-time-window.diff This fixes a bug that causes gtk_window_set_focus_on_map(w, FALSE) not to work. Metacity wasn't reading the user time from the _NET_WM_USER_TIME_WINDOW correctly. The other problem remains: in sloppy focus, the test program's transient window gets the focus even if you are typing elsewhere. I'll keep digging.
For reference, this bug started as https://bugzilla.novell.com/show_bug.cgi?id=331835
Ricardo: Thanks for the testcase. As you noted, that functionality did work before (I tested it a bajillion times when implementing the functionality originally), so you found a regression. We really need some kind of regression test suite... Federico: Thanks for the fix to Ricardo's problem. Very good catch; please commit. :-) Some answers to various questions of yours: I use mouse focus, predominantly (it's hard to use any mode exclusively when you're the maintainer of a window manager and have spent lots of that time fixing focus issues for each of the focus modes in the code). Setting StartupNotify=true in the .desktop file and launching with nautilus or gnome-panel should ensure that the application is launched with startup-notification. I'll have to agree that focus-stealing prevention is much less useful due to requiring various levels of application support, but it simply is not possible to make it work without the window manager having more information than what has traditionally been available. In other words, we need more information, and it really needs to come from the applications. We realize we can't get 100% cooperation, so heuristics already exist -- much like you suggest. (And I suggested an additional one we could use in comment #4.) The question is always what do you decide to do when you have insufficient information. Now, your suggestion in item (2) from comment #9 basically means changing the default to not ever focus new windows except in the case of apps that cooperate and provide information suggesting the window is due to a new user request. Users have screamed like mad in the past when we switched to don't focus new windows by default, and most people will treat it as a backwards incompatible break. As such, this simply isn't an option. I'm not sure what you are saying still doesn't work. I would have thought it was what I already covered above, and that the additional heuristic in comment 4 would cover the problem you're currently seeing. But you mentioned that you'd keep digging, so I'm wondering if you're now referring to a different issue. Could you provide exact steps to duplicate the problem you are still seeing?
I am a focus-follows-mouse guy. IMHO, Metacity should not steal focus in that mode.
Morten, yeah, popups from some other window probably shouldn't steal focus, ever. But I do think main windows (without parent) should steal focus (in case the user hasn't being interacting with the current window for a few seconds -- like in Metacity 2.18). If you don't like this, then you are requesting for KDE's strict-focus-follows-mouse, and like in KDE, it should be an extra mode. Because its a total pain for those that like to use the keyboard to launch and interact with applications. Give it a try.
(In reply to comment #13) > > Federico: Thanks for the fix to Ricardo's problem. Very good catch; please > commit. :-) Committed to trunk (r3379) and gnome-2-20 (r3380): 2007-11-07 Federico Mena Quintero <federico@novell.com> * src/window-props.c (reload_net_wm_user_time_window): Fix typo; the arguments to meta_window_reload_property_from_xwindow() were reversed. This is why the wm_user_time wasn't getting initialized properly from the _NET_WM_USER_TIME_WINDOW. Fixes part of http://bugzilla.gnome.org/show_bug.cgi?id=488468
Created attachment 98836 [details] Updated transient-steal-focus2.py This makes the test windows have titles of "IGNORE ME" (for the first window) and "EVIL TRANSIENT" (for the second, transient window), so that it is easier to see which is which in the Metacity logs and in gdb.
(In reply to comment #13) > always what do you decide to do when you have insufficient information. Now, > your suggestion in item (2) from comment #9 basically means changing the > default to not ever focus new windows except in the case of apps that cooperate > and provide information suggesting the window is due to a new user request. > Users have screamed like mad in the past when we switched to don't focus new > windows by default, and most people will treat it as a backwards incompatible > break. As such, this simply isn't an option. > > 4 would cover the problem you're currently seeing. But you mentioned that > you'd keep digging, so I'm wondering if you're now referring to a different > issue. Could you provide exact steps to duplicate the problem you are still > seeing? Sure: 1. Update to svn trunk so you get the fix for the reversed arguments. 2. Run the test program from comment #17 (it doesn't matter if you run it from a terminal or with startup-notification). 3. When the "IGNORE ME" window appears, quickly switch to typing in another window. 4. Keep typing for 5 seconds, then the "EVIL TRANSIENT" window will appear and will steal your focus. I traced this to intervening_user_event_occurred(): 1787 if (!(window->net_wm_user_time_set) && !(window->initial_timestamp_set)) 1788 { 1789 meta_topic (META_DEBUG_STARTUP, 1790 "no information about window %s found\n", 1791 window->desc); 1792 return FALSE; 1793 } And then I changed that to "return TRUE", under the rationale of, "this stupid window doesn't have a timestamp, so it has no right to be focused". This of course breaks things horribly, since new apps launched from a terminal don't get the focus :) I was thinking of "focus new windows if the user hasn't been typing for N seconds", but I'm getting to like your idea of "if new non-transient windows come without timestamps, generate one and stash it in there". I'll test it and tell you what happens. Hmmmm, offhand, do you know the right place in the code for a test like this?
Created attachment 98845 [details] [review] stash away the "current" time when apps are launched with no launch time; use stashed time from parents when transients are also launched without a launch time
So, yeah, the bug you are experiencing is precisely the case I outlined a workaround for in comment 4. I've implmented it and it fixes the problem with the testcase. Any other lurking problems?
(In reply to comment #20) > So, yeah, the bug you are experiencing is precisely the case I outlined a > workaround for in comment 4. I've implmented it and it fixes the problem with > the testcase. > > Any other lurking problems? W00t, you are fast! I spent all of Friday implementing something similar, and the code was much uglier. Your patch seems to fix this, and it works quite nicely for me. Let me make a patch for openSUSE so that we can get some testing for the original problem, and I'll tell you how it works out. Thanks for the patch, Elijah, this is fantastic :)
Cool, after you get it tested, let me know and I'll apply this to the gnome-2-20 branch and do a release. Thanks!
It works! You rock, Elijah :) Thanks for the fix.
Ok, applied to gnome-2-20 and 2.20.1 released. :-)