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

XEMBED: Preventing focus loops



Currently, it's possible to get into loops with focus messages and
XEMBED. The problem occurs when you have a client that doesn't have
any internal focus locations, embedded into a toplevel without
any focus sites.

If we try to focus the first widget in the toplevel (say, when
the toplevel is mapped), we get the following sequence:

 - Embedder sends XEMBED_FOCUS_IN/XEMBED_FOCUS_FIRST to the client
 - Client finds no focus sites, sends XEMBED_FOCUS_NEXT to
   the embedder
 - Embedder takes this as the user hitting Tab again, wraps
   around to the beginning, and sends another XEMBED_FOCUS_IN

If you have nested embedding, then this can occur over two
levels.

My proposed solution to this is to define the currently unused data1
of the XEMBED_FOCUS_IN, XEMBED_FOCUS_PREV and XEMBED_FOCUS next
messages as a flags field, with currently one flag defined:

#define XEMBED_FOCUS_WRAPAROUND         (1 << 0)

When sending one of these messages, the flag is set as follows:

 - If the message is not generated as the result as one of
   these three XEMBED messages, set the bit to zero

 - When generating an XEMBED_FOCUS_IN in response to a
   XEMBED_FOCUS_PREV or XEMBED_FOCUS_NEXT message and the
   focus moved from the bottom of the window to the top
   for a XEMBED_FOCUS_NEXT message or from the top of the
   window to the bottom for a XEMBED_FOCUS_PREV message:

   - If the bit was not set in the generating message, set
     the bit in the new message
   - If the bit was set in the generating message, a loop
     has be detected. DO NOT SEND THE NEW MESSAGE.

 - Otherwise, set the bit to the value found in the 
   generating message.
   
All other bits in the field are unused and must be set to zero.

Notes about the proposal:

 * The only difficult thing about implementing this proposal
   is detecting when XEMBED_FOCUS_NEXT causes a wrap-around
   at the toplevel. It required some hackiness to implement
   in GTK+. I think most toolkits will be similar - a bit
   hacky to do but possible.
 
 * The two other approach that I considered were:

   * assign a unique ID to the focus sequence so that an 
     individual embedder could check if it was repeating
     a focus sequence.

     Assigning a sufficiently unique ID is hard and requires
     using all the remaining free space in the messages
     instead of just one bit.You have to keep a history since 
     it is possible to have multiple interleaved infinite 
     focus loops.

   * Some sort of response message to XEMBED_FOCUS_IN,
     to allow tracking focus sequences without needing
     a globally unique ID for the sequence. 

     This has the same problem with multiple interleaved focus 
     sequences as the prior solution and would also require 
     a lot of complicated bookkeeping to implement.

 * I didn't up the protocol version because it is a highly
   compatible addition - there is nothing you would do
   differently if you knew that the other side didn't
   understand XEMBED_FOCUS_WRAPAROUND.

Thanks for any feedback; if I don't hear anything in the next
few days I'll go ahead and make the changes to the spec.

Regards,
						Owen

Index: xembed-spec.xml
===================================================================
RCS file: /home/freedesktop/xembed/spec/xembed-spec.xml,v
retrieving revision 1.2
diff -u -p -r1.2 xembed-spec.xml
--- xembed-spec.xml	21 Apr 2002 19:12:33 -0000	1.2
+++ xembed-spec.xml	21 Aug 2003 12:54:38 -0000
@@ -5,8 +5,8 @@
 <article id="index">
   <articleinfo>
     <title>XEmbed Protocol Specification</title>
-    <releaseinfo>Version 0.5</releaseinfo>
-    <date>15 April 2002</date>
+    <releaseinfo>Version 0.6</releaseinfo>
+    <date>20 April 2003</date>
     <authorgroup>
       <author>
 	<firstname>Mathias</firstname>
@@ -339,6 +339,57 @@
 	Backward tabbing is done exactly in the same manner, using the
 	XEMBED_FOCUS_PREV message.
       </para>
+      <para>
+	If there are no focusable widgets at all in a toplevel window,
+	then is possible for an infinite loop of focusing to be
+	generated. To prevent this, the XEMBED_FOCUS_WRAPAROUND flag
+	can be used in the flags field XEMBED_FOCUS_IN, XEMBED_FOCUS_NEXT, 
+	and XEMBED_FOCUS_PREV. it is set as follows:
+      </para>
+      <itemizedlist id="focus-wraparound">
+	<listitem>
+	  <para>
+	    If the message is not generated as the result as one of
+	    these three focus chain messages, set the bit to zero.
+	    (As examples of such generation, a XEMBED_FOCUS_NEXT message
+	    from a client to the embedder might cause XEMBED_FOCUS_IN
+	    to be sent to another client in the same toplevel, or a
+	    XEMBED_FOCUS_IN event sent to a client might cause another
+	    XEMBED_FOCUS_IN event to be sent to a client nested in
+	    another level of embedding.
+	  </para>
+	</listitem>
+	<listitem>
+	  <para>
+	    When generating an XEMBED_FOCUS_IN in response to a
+	    XEMBED_FOCUS_PREV or XEMBED_FOCUS_NEXT message and the
+	    focus moved from the bottom of the window to the top
+	    for a XEMBED_FOCUS_NEXT message or from the top of the
+	    window to the bottom for a XEMBED_FOCUS_PREV message;
+	    then:
+	  </para>
+	  <itemizedlist>
+	    <listitem>
+	      <para>
+		If the bit was not set in the generating message, set
+		the bit in the new message
+	      </para>
+	    </listitem>
+	    <listitem>
+	      <para>
+		If the bit was set in the generating message, a loop
+		has been detected. <emphasis>Do not send the new message.</emphasis>
+	      </para>
+	    </listitem>
+	  </itemizedlist>
+	</listitem>
+	<listitem>
+	  <para>
+	    Otherwise, set the bit to the value found in the 
+	    generating message.
+	  </para>
+	</listitem>
+      </itemizedlist>
     </sect2>
     <sect2>
       <title>Keyboard short cuts / accelerators</title>
@@ -596,6 +647,18 @@
 #define XEMBED_FOCUS_FIRST 		1
 #define XEMBED_FOCUS_LAST		2<!--
  --></programlisting>
+    <para>
+      The XEMBED_FOCUS_IN, XEMBED_FOCUS_NEXT, and XEMBED_FOCUS_PREV
+      messages store flags in the data1 field. The valid flags
+      currently defined are:
+    </para>
+    <programlisting><!--
+-->/* Flags for XEMBED_FOCUS_IN, XEMBED_FOCUS_NEXT, XEMBED_FOCUS_PREV */
+#define XEMBED_FOCUS_WRAPAROUND         (1 &lt;&lt; 0)<!--
+ --></programlisting>
+    <para>
+      All other bits must be zero.
+    </para>
     <sect2>
       <title>XEMBED_EMBEDDED_NOTIFY</title>
       <para>
@@ -639,13 +702,6 @@
 	within a toplevel can be moved programmatically when the
 	toplevel doesn't have input focus.
       </para>
-      <remark>
-	[ GTK+ is currently in violation of the preceding note,
-	  and sends FOCUS_IN and FOCUS_OUT only when the toplevel
-	  is active. See 
-	<ulink
-	  url="http://bugzilla.gnome.org/show_bug.cgi?id=67943";>GNOME bug #67943</ulink> ]
-      </remark>
       <para>
 	Widgets within the client should typically be displayed with
 	the focus only when the client both has focus and is active.
@@ -705,6 +761,23 @@
 	  </listitem>
 	</varlistentry>
       </variablelist>
+      <para>
+	The flags field can contain the
+	<link linkend="focus-wraparound">XEMBED_FOCUS_WRAPAROUND</link> bit.
+      </para>
+      <table>
+	<title>XEMBED_FOCUS_IN</title>
+	<tgroup cols="2">
+	  <tbody>
+	    <row>
+	      <entry>detail</entry><entry>enumeration indicating</entry>
+	    </row>
+	    <row>
+	      <entry>data1</entry><entry>flags field.</entry>
+	    </row>
+	  </tbody>
+	</tgroup>
+      </table>
     </sect2>
     <sect2>
       <title>XEMBED_FOCUS_OUT</title>
@@ -719,8 +792,20 @@
 	its logical tab chain after the user tabbed forward. If the
 	embedder has siblings that accept tab focus, it will do a virtual
 	tab forward. As a result, it will loose focus itself and
-	consequently send an XEMBED_FOCUS_OUT message to the client
+	consequently send an XEMBED_FOCUS_OUT message to the client.
+	The flags field can contain the
+	<link linkend="focus-wraparound">XEMBED_FOCUS_WRAPAROUND</link> bit.
       </para>
+      <table>
+	<title>XEMBED_FOCUS_NEXT</title>
+	<tgroup cols="2">
+	  <tbody>
+	    <row>
+	      <entry>data1</entry><entry>flags field.</entry>
+	    </row>
+	  </tbody>
+	</tgroup>
+      </table>
     </sect2>
     <sect2>
       <title>XEMBED_FOCUS_PREV</title>
@@ -730,8 +815,19 @@
 	backward. If the embedder has siblings that accept tab focus, it
 	will do a virtual tab backward. As a result, it will loose focus
 	itself and consequently send an XEMBED_FOCUS_OUT message to the
-	client
+	client. The flags field can contain the
+	<link linkend="focus-wraparound">XEMBED_FOCUS_WRAPAROUND</link> bit.
       </para>
+      <table>
+	<title>XEMBED_FOCUS_PREV</title>
+	<tgroup cols="2">
+	  <tbody>
+	    <row>
+	      <entry>data1</entry><entry>flags field.</entry>
+	    </row>
+	  </tbody>
+	</tgroup>
+      </table>
     </sect2>
     <sect2>
       <title>XEMBED_REGISTER_ACCELERATOR / XEMBED_UNREGISTER_ACCELERATOR</title>
@@ -1128,59 +1224,6 @@ void send_xembed_message(
       </para>
     </sect2>
     <sect2>
-      <title>Infinite loops in focusing</title>
-      <para>
-	There is the potential for infinite loops of focusing -
-	Consider the case:
-      </para>
-      <programlisting><!--
--->     Toplevel Window
-       Embedder
-         Client<!--
-   --></programlisting>
-      <para>
-	Where there are no focusable sites in the client or in the
-	toplevel window. Then if <keysym>Tab</keysym> is pressed, the embedder
-	will send: FOCUS_IN/FOCUS_FIRST to the client, the client will
-	send FOCUS_NEXT to the embedder, the toplevel window will
-	wrap the focus around and send FOCUS_IN/FOCUS_FIRST to the
-	client...
-      </para>
-      <para>
-	The minimum mechanism that seems necessary to prevent this
-	loop is a serial number in the FOCUS_IN/FOCUS_FIRST message
-	that is repeated in a resulting FOCUS_NEXT message.
-      </para>
-      <para>
-	A possibly better way of handling this could be to make FOCUS_IN have
-	an explicit response; that, is, add a XEMBED_FOCUS_IN_RESPONSE
-	that the client must send to the embedder after receipt
-	of a FOCUS_IN message.
-      </para>
-
-      <table>
-	<title>XEMBED_FOCUS_IN_RESPONSE</title>
-	<tgroup cols="2">
-	  <tbody>
-	    <row>
-	      <entry>detail</entry><entry>1 if the client accepted the focus, 0 otherwise</entry>
-	    </row>
-	    <row>
-	      <entry>data1</entry><entry>serial number from XEMBED_FOCUS_IN</entry>
-	    </row>
-	  </tbody>
-	</tgroup>
-      </table>
-      <para>
-	The main problem with requiring a response here is that caller
-	needs to wait for the return event, and to handle cases like
-	parent (client 1) => child (client 2) => grandchild (client 1),
-	it probably needs to process all sorts of incoming events at
-	this point. If the user hits <keysym>Tab</keysym><keysym>Tab</keysym>
-	in quick succession things could get very complicated.
-      </para>
-    </sect2>
-    <sect2>
       <title>Robustness</title>
       <para>	
 	The protocol, as currently constituted, is not robust against
@@ -1398,6 +1441,18 @@ void send_xembed_message(
 	  <listitem>
 	    <para>
 	      Converted to docbook format.
+	    </para>
+	  </listitem>
+	</itemizedlist>
+      </para>
+    </formalpara>
+    <formalpara>
+      <title>Version 0.6, 20 August 2003, Owen Taylor</title>    
+      <para>
+	<itemizedlist>
+	  <listitem>
+	    <para>
+	      Added the XEMBED_FOCUS_WRAPAROUND flag.
 	    </para>
 	  </listitem>
 	</itemizedlist>

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