No focus on map hint II.



 Hello,

 here's my revised proposal for the "don't focus this window on map" hint. It 
took some time, but now I have KWin version that is quite good at preventing 
unwanted focus stealing from active application. The single hint from the 
original proposal of course wasn't enough for this, I had to add few more 
things. I'm not sure which of those should be added to the spec, so I'll list 
them all, and I'll see. I have no problem with keeping the WM/pager specific 
ones as KDE-only.

 Just in case somebody is interested, the comment from sources describing the 
mechanism is attached as kwin.txt (I had to write it down first before 
implementing it, otherwise I'd go crazy because of it ;)  ).

 First, the hint that started this all. The only thing I added when compared 
to the last proposal is that clients should also update the user time in the 
hint in the visible window, so that the WM is able to detect last user 
activity in the window if key events don't propagate up to the frame. I also 
think the name MAP_TIMESTAMP no longer fits, so I suggest _NET_WM_USER_TIME 
as the name of the property. 

The patch is attached as user_time.txt . I'd like this one to get in the spec. 
(I added the note about CARDINAL[] because I use it in code, and it would 
break code that would strictly request the property to contain only one 
CARDINAL value - any complaints about that?).

 Now, if WM refuses to activate a window, it should mark it somehow, so that 
the user knows some window tried to get activated, but the WM refused it 
(and, also, I'm considering using the same mechanism for cases when e.g. 
Konqueror finishes loading a HTML page). I used a 
_NET_WM_STATE_DEMANDS_ATTENTION property. I first thought about using the 
Urgency hint from ICCCM, but I eventually decided not to. I wasn't sure if it 
would be ok to set this hint from the WM, and moreover the meaning of this 
state is actually somewhat opposite to being urgent.

 The patch is in demands_attention.txt . As this is only KWin->Kicker thingy, 
I don't care that much if this gets in the spec, I can prefix it with _KDE, 
but if somebody else would use the same feature, we should use the same name.

 The patch for startup notification spec is in startup-notification.txt . The 
only change from the last proposal is saying that the property should be used 
even if the newly mapped window has _NET_WM_USER_TIME, but it's older 
(happens when the newly started app doesn't create the window itself, but 
instead tells already running instance to do so and then exits, e.g. 
Mozilla).

 That should be it. Now some problems I'm not quite sure what to do with them.

- Mozilla startup, if it's already running - in this case, newly launched 
mozilla tells the running instance to open a new window, and exits. The 
problem is, KWin detects this as inactive application opening another window, 
and refuses activation of the window. That's logical in most cases, but I 
failed to find a way how to detect this Mozilla case. For KDE applications 
that work the same way, I ended up with reading WM_WINDOW_ROLE on the 
windows, and if both of them contain # (hash), they are considered not 
belonging to the same application, even if they really are. KMainWindow class 
by default set WM_WINDOW_ROLE for toplevel windows as <something>-#<number>, 
and since KWord's , KWrite's, Konqueror's etc. mainwindows from the user's 
point of view are different applications, even if it's the same process.

 Mozilla doesn't set WM_WINDOW_ROLE at all, BTW. GEdit has some strange long 
'bonobo-mdi-window ...' string as WM_WINDOW_ROLE, so I think the extra # 
wouldn't offend anybody ;). Could something like this find its way in the 
spec somehow?

- I'm considering extending some request from clients to say whether they come 
from a pager-like tool or from an application. For example, say that you 
click on the taskbar and want to activate the matching window. The taskbar 
sends the WM _NET_ACTIVE_WINDOW for the window, and the WM can't do anything 
better than to activate the window (but it would be stupid if it didn't). 
Now, however, some applicattions use _NET_ACTIVE_WINDOW on themselves 
(because it's simpler than making sure the window is shown, and doing 
XSetInputFocus()). The same if the app uses _NET_WM_DESKTOP on its window.

 Should these calls be forbidden to do from normal applications, or is it ok 
to extend the request? One of the the data.l[] fields in the messages could 
be used to mark from pager/from app, and for the _NET_ACTIVE_WINDOW message 
from app, another field could be used for the app's currently active window, 
so that the WM would obey the request only if the app's other window is the 
window with focus. As said in my other mail, those messages currently leave 
unused fields as undefined, but in these cases getting garbage in those new 
fields wouldn't really break anything.

-- 
Lubos Lunak
KDE developer
---------------------------------------------------------------------
SuSE CR, s.r.o.  e-mail: l lunak suse cz , l lunak kde org
Drahobejlova 27  tel: +420 2 9654 2373
190 00 Praha 9   fax: +420 2 9654 2374
Czech Republic   http://www.suse.cz/
/*
 Prevention of focus stealing:
 
 KWin tries to prevent unwanted changes of focus, that would result
 from mapping a new window. Also, some nasty applications may try
 to force focus change even in cases when ICCCM 4.2.7 doesn't allow it
 (e.g. they may try to activate their main window because the user
 definitely "needs" to see something happened - misusing
 of QWidget::setActiveWindow() may be such case).
 
 There are 4 ways how a window may become active:
 - the user changes the active window (e.g. focus follows mouse, clicking
   on some window's titlebar) - the change of focus will
   be done by KWin, so there's nothing to solve in this case
 - the change of active window will be requested using the _NET_ACTIVE_WINDOW
   message (handled in RootInfo::changeActiveWindow()) - such requests
   will be obeyed, because this request is meant mainly for e.g. taskbar
   asking the WM to change the active window as a result of some user action.
   Normal applications should use this request only rarely in special cases.
   See also below the discussion of _NET_ACTIVE_WINDOW_TRANSFER.
 - the change of active window will be done by performing XSetInputFocus()
   on a window that's not currently active. ICCCM 4.2.7 describes when
   the application may perform change of input focus. In order to handle
   misbehaving applications, KWin will try to detect focus changes to
   windows that don't belong to currently active application, and restore
   focus back to the currently active window, instead of activating the window
   that got focus (unfortunately there's no way to FocusChangeRedirect similar
   to e.g. SubstructureRedirect, so there will be short time when the focus
   will be changed). The check itself that's done is
   Workspace::allowClientActivation() (see below).
 - a new window will be mapped - this is the most complicated case. If
   the new window belongs to the currently active application, it may be safely
   mapped on top and activated. The same if there's no active window,
   or the active window is the desktop. These checks are done by
   Workspace::allowClientActivation().
    Following checks need to compare times. One time is the timestamp
   of last user action in the currently active window, the other time is
   the timestamp of the action that originally caused mapping of the new window
   (e.g. when the application was started). If the first time is newer than
   the second one, the window will not be activated, as that indicates
   futher user actions took place after the action leading to this new
   mapped window. This check is done by Workspace::allowClientActivationTimestamp().
    There are several ways how to get the timestamp of action that caused
   the new mapped window (done in Client::readUserTimeMapTimestamp()) :
     - the window may have the _NET_WM_USER_TIME property. This way
       the application may either explicitly request that the window is not
       activated (by using 0 timestamp), or the property contains the time
       of last user action in the application.
     - KWin itself tries to detect time of last user action in every window,
       by watching KeyPress and ButtonPress events on windows. This way some
       events may be missed (if they don't propagate to the toplevel window),
       but it's good as a fallback for applications that don't provide
       _NET_WM_USER_TIME, and missing some events may at most lead
       to unwanted focus stealing.
     - the timestamp may come from application startup notification.
       Application startup notification, if it exists for the new mapped window,
       should include time of the user action that caused it.
     - if there's no timestamp available, it's checked whether the new window
       belongs to some already running application - if yes, the timestamp
       will be 0 (i.e. refuse activation)
     - if the window is from session restored window, the timestamp will
       be 0 too, unless this application was the active one at the time
       when the session was saved, in which case the window will be
       activated if there wasn't any user interaction since the time
       KWin was started.
     - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp
       is used. For every toplevel window that is created (see CreateNotify
       handling), this property is set to the at that time current time.
       Since at this time it's known that the new window doesn't belong
       to any existing application (better said, the application doesn't
       have any other window mapped), it is either the very first window
       of the application, or its the only window of the application
       that was hidden before. The latter case is handled by removing
       the property from windows before withdrawing them, making
       the timestamp empty for next mapping of the window. In the sooner
       case, the timestamp will be used. This helps in case when
       an application is launched without application startup notification,
       it creates its mainwindow, and starts its initialization (that
       may possibly take long time). The timestamp used will be older
       than any user action done after launching this application.
     - if no timestamp is found at all, the window is activated.
    The check whether two windows belong to the same application (same
   process) is done in Client::belongToSameApplication(). Not 100% reliable,
   but hopefully 99,99% reliable.
   
 As a somewhat special case, window activation is always enabled when
 session saving is in progress. When session saving, the session
 manager allows only one application to interact with the user.
 Not allowing window activation in such case would result in e.g. dialogs
 not becoming active, so focus stealing prevention would cause here
 more harm than good.

 Windows that attempted to become active but KWin prevented this will
 be marked as demanding user attention. They'll get
 the _NET_WM_STATE_DEMANDS_ATTENTION state, and the taskbar should mark
 them specially (blink, etc.). The state will be reset when the window
 eventually really becomes active.
 
 There are one more ways how a window can become obstrusive, window stealing
 focus: By showing above the active window, by either raising itself,
 or by moving itself on the active desktop.
     - KWin will refuse raising non-active window above the active one,
         unless they belong to the same application. Applications shouldn't
         raise their windows anyway (unless the app wants to raise one
         of its windows above another of its windows).
     - KWin activates windows moved to the current desktop (as that seems
         logical from the user's point of view, after sending the window
         there directly from KWin, or e.g. using pager). This means
         applications shouldn't send their windows to another desktop
         (SELI TODO - but what if they do?)
 
 Special cases I can think of:
    - konqueror reusing, i.e. kfmclient tells running Konqueror instance
        to open new window
        - without focus stealing prevention - no problem
        - with ASN (application startup notification) - ASN is forwarded,
            and because it's newer than the instance's user timestamp,
            it takes precedence
        - without ASN - user timestamp needs to be reset, otherwise it would
            be used, and it's old; moreover this new window mustn't be detected
            as window belonging to already running application, or it wouldn't
            be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly)
            hack
    - konqueror preloading, i.e. window is created in advance, and kfmclient
        tells this Konqueror instance to show it later
        - without focus stealing prevention - no problem
        - with ASN - ASN is forwarded, and because it's newer than the instance's
        user timestamp, it takes precedence
        - without ASN - user timestamp needs to be reset, otherwise it would
            be used, and it's old; also, creation timestamp is changed to
            the time the instance starts (re-)initializing the window,
            this ensures creation timestamp will still work somewhat even in this case
    - KUniqueApplication - when the window is already visible, and the new instance
        wants it to activate
        - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem
!!!!        - with ASN - should forward ASN, and running instance should use this ASN's timestamp - how?
!!!!        - without ASN - _NET_ACTIVE_WINDOW is used, this results in focus stealing,
            but there's no other way, as creation timestamp will have roughly the same time
            as the _NET_ACTIVE_WINDOW request, and there's no other way how to activate
            the other window when the application is started from Konsole without ASN
    - when one application wants to activate another application's window (e.g. KMail
        activating already running KAddressBook window ?)
        - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem
        - with ASN - can't be here, it's the KUniqueApp case then
!!!!        - without ASN - _NET_ACTIVE_WINDOW - focus stealing :(
                - see _KDE_NET_WM_TRANSFER_ACTIVE_WINDOW


*/
--- wm-spec.sgml.sav	2003-01-03 21:31:14.000000000 +0100
+++ wm-spec.sgml	2003-05-15 14:37:39.000000000 +0200
@@ -930,6 +930,7 @@ _NET_WM_STATE_HIDDEN, ATOM
 _NET_WM_STATE_FULLSCREEN, ATOM
 _NET_WM_STATE_ABOVE, ATOM
 _NET_WM_STATE_BELOW, ATOM
+_NET_WM_STATE_DEMANDS_ATTENTION, ATOM
 ]]></programlisting>
       <para>
 An implementation MAY add new atoms to this list. Implementations
@@ -1009,6 +1010,12 @@ windows (see <xref linkend="STACKINGORDE
        </para>
 
 	<para>
+_NET_WM_STATE_DEMANDS_ATTENTION indicates that some action in or with the window
+happened. For example, it may be set by the WM if the window requested activation
+but the WM refused it, or the application may set it if it finished some work.
+	</para>
+
+	<para>
 To change the state of a mapped window, a Client MUST send a _NET_WM_STATE
 client message to the root window  (window is the respective window, type
 _NET_WM_STATE, format 32, l[0]=&lt;the action, as listed below&gt;,
--- wm-spec.sgml.sav	2003-01-03 21:31:14.000000000 +0100
+++ wm-spec.sgml	2003-05-15 14:42:30.000000000 +0200
@@ -1205,6 +1205,41 @@ _NET_WM_HANDLED_ICONS
 	for iconified windows. 
 	</para>
     </sect2>
+       <sect2><title>_NET_WM_USER_TIME</title>
+       <programlisting><![CDATA[
+_NET_WM_USER_TIME CARDINAL[]/32
+]]></programlisting>
+        <para>
+This property contains the XServer time at which last user activity in this
+window took place.
+        </para>
+        <para>
+Clients should keep the last timestamp from events KeyPress, 
+ButtonPress and ButtonRelease (not KeyRelease, as the client may possibly
+get events for modifier key releases for some global actions), and set
+this timestamp in this property on every new toplevel window before mapping it.
+They should start setting the property only after first receiving a KeyPress,
+ButtonPress or ButtonRelease event, they shouldn't set it before receiving first input 
+event. If the client has the active window, it should also update this
+property on the window whenever there's user activity. The special value of zero
+on a newly mapped window means that the window shouldn't initially get focus
+after being mapped.
+        </para>
+        <para>
+Rationale: This property allows a Window Manager to alter the focus,
+stacking, and/or placement behavior of windows when they are
+mapped depending on whether the new window was created by a user
+action or is a "pop-up" window activated by a timer or some other
+event.
+        </para>
+        <para>
+Note: The property is specified as CARDINAL[] in order to simplify implementation
+in toolkits - always only the first element should be read. The toolkit can always
+use PropModeAppend to set the property, which will set the property if it's not
+set yet, but won't overwrite any previous value set before in the application.
+In such case, the toolkit should remove the property after the window is withdrawn.
+        </para>
+    </sect2>
 </sect1>
 <sect1>
 	<title>Window Manager Protocols</title>
--- startup-notification.txt.sav	2002-10-23 20:05:14.000000000 +0200
+++ startup-notification.txt	2003-05-15 14:50:11.000000000 +0200
@@ -234,6 +234,17 @@ The following keys may be provided optio
           This desktop is relative to the screen provided by 
           the SCREEN key.
 
+  TIMESTAMP
+
+          X server timestamp of the user action that caused this
+          launch. For example window manager that doesn't allow
+          stealing of focus by newly mapped windows while the user
+          works in an application can use this timestamp for windows
+          that have matching _NET_STARTUP_ID property if they don't
+          have any _NET_WM_USER_TIME property set or if it's older.
+          See the description of _NET_WM_USER_TIME in the WM spec
+          for details.
+
   DESCRIPTION   
 
           a short description suitable for display in a dialog that


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