Focus stealing prevention



Focus stealing prevention is active in Metacity.  Roughly, it means
that if a window was launched at time A, and the user interacted with
a different window at time B, and B>A, then the launched window will
not be focused when it appears.  This allows a user to work with an
application while impatiently waiting for that really slow app
(*cough* OpenOffice *cough*) to show up without being interrupted
mid-thought when it finally does.  It also prevents those annoying
Instant Message windows from taking your keystrokes and sending them
to someone when you meant to be typing them into the application
window asking for your root password.

There's a couple small bugs with it (the dependencies of #149028 for
the curious), but it mostly works right now.  The main thing that we
need is for applications to do more to support it.  gtk+ tries to
handle things in a sane manner so that things just work, but it
doesn't always have sufficient information.  Therefore, we need to (A)
make sure applications are launched with startup notification, and (B)
get applications to set the timestamp causing a new window to be shown
in the cases where gtk+'s default is not correct.  The exact
requirements are listed below.

Summary of what applications need to do:
----------------------------------------------------------------------

 - If you are launching applications:
   1) Use startup notification (i.e. use gnome_desktop_item_launch()
      or a related function)

 - If you open a window "out of the blue" (not in response to user
   input):
   1) Call gtk_window_set_focus_on_map (window, FALSE) before showing
      the window

 - If you open a window a delayed amount of time after a user action
   (and the user could feasibly interact further with the application
   before the window appears):
   1) Obtain the timestamp of the user action that results in the
      later window opening
   2) Call gdk_x11_window_set_user_time (gtk_toplevel_window->window, 
                                         timestamp_of_launch_event)
      after realizing (i.e. gtk_widget_realize()) the window but
      before showing it

 - If you have a new instance of your app tell a previously running
   instance to open a new window and then you quit the new instance:
   1) Get the launch timestamp from the DESKTOP_STARTUP_ID environment
      variable (format is "<unique>_TIME<timestamp>") at program start
   2) Forward the timestamp to the previous instance
   3) Have the previous instance call
        gdk_x11_window_set_user_time (gtk_toplevel_window->window, 
                                      timestamp_of_launch_event)
      after realizing the new window but before showing it.

 - If you have one application request a different already-running
   application to open a new window:
   1) Obtain the timestamp of the user action that results in the
      request to the other app to open a window
   2) Send the timestamp along with the request
   3) Have other other app call
        gdk_x11_window_set_user_time (gtk_toplevel_window->window, 
                                      timestamp_of_launch_event)
      after realizing the window but before showing it.


All the nasty details of why applications need to do this:
----------------------------------------------------------------------

Making sure applications are launched with startup-notification:

If applications aren't launched with startup-notification, then there
will be no timestamp to compare with for the first window that opens.
This means that focus stealing prevention can't work.  For now,
metacity focuses new windows under this circumstance, but we may
change that (mainly to make it easier to find broken application
launchers that don't use startup-notification).  Even if we don't
change it, there's more than just the inconvenience of having a window
steal focus: for those applications that need to forward the launch
timestamp to a previous instance such as gnome-terminal, they won't be
able to obtain any such timestamp if they aren't launched with startup
notification--meaning that the new window that gets open will likely
not be focused as it should be.  (An example of where this problem
currently occurs is in the right-click desktop menu to open a
gnome-terminal).  So, we really do need startup notification to be
used when launching applications.  gnome_desktop_item_launch() and its
friends are meant to handle this.

Getting applications to set the timestamp of launch when needed:

When a new window is opened, gtk+ sets the timestamp specifying when
it was launched to the user's last interaction with the application.
(If it is the first window of an app, then startup-notification is
used since the user hasn't yet interacted with the app).  This fails
under four circumstances: (1) if a window appears "out of nowhere"
(i.e. not in response to user input--for example, think of a
word-processor trying to do auto-save, failing, and then displaying an
error dialog), (2) if a window appears after a delay from the user
interaction and the user has meanwhile interacted with that same
application (think of trying to load a mis-typed URL in one tab while
typing in another tab or window), (3) if an application, when
launched, forwards a "please open a new window" request to a
previously running instance instead of opening a window itself (this
is what applications like nautilus, gnome-terminal, epiphany, galeon,
mozilla, etc. do), and (4) if a user interaction in one application
causes a different application to open a window (think of a user
clicking on a URL in an email client and the email client sending a
message to an already running web browser, for example)

Note that in cases (1) and (2) the problem is that the window can be
focused when it shouldn't be.  That's no worse than the behavior we've
always had, but it would be nice to improve things.  In cases (3) and
(4) we get the problem that the window may not be focused when it
should be, i.e. a regression from previous behavior.

In case (1), if a window appears out of nowhere, the window shouldn't
take focus.  To ensure this, the application needs to call
    gtk_window_set_focus_on_map (window, FALSE)
before showing the window.

In case (2), the timestamp gtk+ will be using will be the wrong one.
To fix this, the application will need to get the timestamp of the
event that caused the window to be launched, and then after realizing
the window but before showing it, the application will need to call
    gdk_x11_window_set_user_time (gtk_toplevel_window->window,
                                  timestamp_of_launch_event)

In case (3), the application needs to obtain the timestamp that caused
it to be launched from startup-notification, forward that to the
previously running instance, and then have the previously running
instance set the timestamp manually.  The timestamp can be obtained
from startup-notification by reading the DESKTOP_STARTUP_ID
environment variable before calling gtk_init (gtk_init unsets that
environment variable); it has the format
    <unique identifier>_TIME<timestamp of launch event>
After forwarding the timestamp, the previous instance sets the
timestamp for the new window it opens using
gdk_x11_window_set_user_time (just as is done for case (2) above).
Note that for case (3), things are simplified considerably if you are
already forwarding the DESKTOP_STARTUP_ID from the new instance to the
old (which is a good idea anyway so that you can tell the desktop when
to quit the hourglass cursor thingy for startup notification); bug
156413 has a patch that shows how simple this case is for
gnome-terminal under this condition.

Case (4) is somewhat of a mix of cases 2 and 3.  The application needs
to get the timestamp from the relevant ButtonPress or KeyPress event
(or call gtk_get_current_event_time()), forward it to the other
application that will open a window, and then the application that
opens a window will need to call gdk_x11_window_set_user_time() as
described above.



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]