Proposal for a smarter behavior for raising windows on mouse click (#2)



 Hello,

 ok, this took same time :-/ (releases, releases). But now I have a fully 
working (I hope) implementation. The kdelibs+KWin patches are already in KDE 
CVS HEAD, attached is a Qt patch that might be useful to you, and a patch for 
the spec.

 I changed a bit how it works, so now it is:
- apps specifies that it supports the protocol
- user presses mouse button on the window (inside it)
- instead of raising/activating/whatever the WM would normally do, it sends 
the client a _NET_WM_TAKE_ACTIVITY [1] message
- the client saves the message and waits for next ButtonRelease
- if the mouse hasn't moved, the client sends the message back (the same way 
_NET_WM_PING works), otherwise it was DND, moving a scrollbar, selecting text 
etc.[2] so it doesn't do anything, and therefore there will be no 
raising/activating
- the WM receives the reply, and does what it would otherwise do right when 
getting the ButtonPress (unless there was another raising/activation 
meanwhile as a result of the mouse events)

 Looks simple, and seems to be working.

 Any comments, flames, questions, is anybody going to try to implement it as 
well (the Qt patch may help you, it's for Qt-3.3.x)?

[1] _NET_WM_TAKE_ACTIVITY is probably a bad name right now, it originally 
worked much like WM_TAKE_FOCUS. Any better name?
[2] Just checking if the mouse hasn't moved (further than minimal drag 
distance) actually quite nicely solves the to-timeout-or-not-to-timeout 
problem.

-- 
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/
--- wm-spec.xml.sav	2004-01-29 14:17:38.000000000 +0100
+++ wm-spec.xml	2004-04-22 14:27:41.183510664 +0200
@@ -1439,6 +1439,46 @@ respond to this protocol within a reason
 See also the implementation notes on <link linkend="KILLINGWINDOWS">killing hung processes</link>.
 		</para>
 	</sect2>
+	<sect2>
+		<title>_NET_WM_TAKE_ACTIVITY</title>
+		<para>
+This protocol allows preventing raising of windows during DND operations.
+When the Window Manager detects a mouse press in a Client window, it can
+use this protocol to find out if the window should be raised and/or activated
+as a result of this event, or if the event leads to an operation where altering
+the window state is not desired. Instead of immediatelly altering the window
+state, the Window Manager can send a client message as follows: </para>
+		<programlisting><![CDATA[
+type = ClientMessage
+window = the respective client window
+message_type = WM_PROTOCOLS
+format = 32
+data.l[0] = _NET_WM_TAKE_ACTIVITY
+data.l[1] = timestamp
+data.l[2] = the respective client window
+other data.l[] elements = <the WM is free to use these fields for any data it wants>
+]]></programlisting>
+		<para>
+A participating Client after receiving this message MUST wait for next mouse button release
+event and MUST either ignore the message if the mouse events lead to a DND-like operation,
+or it MUST send it back to the root
+window immediately, by setting window = root, and calling XSendEvent with
+the same event mask like all other root window messages in this specification use,
+The Window Manager can uniquely
+identify the message by the timestamp and the data.l[2] field if necessary.
+The Client MUST NOT alter any field in the event other than the window.
+Operations that allow the Client to ignore the message are any mouse operations that
+involve pressing mouse button, keeping it down and moving the mouse (DND, moving a scrollbar,
+selecting text,etc.).
+		</para>
+		<para>
+It's entirely up to the Window Manager how to react on a reply to the message. Usually,
+it should perform the same set of operations it would do immediatelly after a user clicks
+on the Client window (raising,activating,etc. depending on its configuration). Note however
+that that the Window Manager should not do anything if such operations have meanwhile taken
+place for another window (e.g. if a new Client window has been mapped as a result of the click).
+		</para>
+	</sect2>
 </sect1>
 <sect1>
 	<title>Implementation notes</title>
--- src/kernel/qapplication_x11.cpp.sav	2004-04-14 13:52:48.000000000 +0200
+++ src/kernel/qapplication_x11.cpp	2004-04-22 10:09:23.945448952 +0200
@@ -216,6 +216,12 @@ Q_EXPORT Atom	qt_wm_protocols		= 0;	// w
 Q_EXPORT Atom	qt_wm_delete_window	= 0;	// delete window protocol
 Q_EXPORT Atom	qt_wm_take_focus	= 0;	// take focus window protocol
 
+Atom            qt_net_wm_take_activity = 0;    // _NET_WM_TAKE_ACTIVITY protocol
+XEvent*         qt_take_activity        = 0;    // event from _NET_WM_TAKE_ACTIVITY
+static QGuardedPtr<QWidget> qt_take_activity_window = 0;
+QPoint          qt_take_activity_pos;
+Time            qt_take_activity_time   = 0;
+
 Atom		qt_qt_scrolldone	= 0;	// scroll synchronization
 Atom		qt_net_wm_context_help	= 0;	// context help
 Atom		qt_net_wm_ping		= 0;	// _NET_WM_PING protocol
@@ -403,6 +409,7 @@ extern bool	qt_check_selection_sentinel(
 
 static void	qt_save_rootinfo();
 bool	qt_try_modal( QWidget *, XEvent * );
+static void qt_process_take_activity( XEvent* event );
 
 int		qt_ncols_option  = 216;		// used in qcolor_x11.cpp
 int		qt_visual_option = -1;
@@ -1841,6 +1848,7 @@ void qt_init_internal( int *argcptr, cha
 	qt_x11_intern_atom( "WM_STATE", &qt_wm_state );
 	qt_x11_intern_atom( "WM_CHANGE_STATE", &qt_wm_change_state );
 	qt_x11_intern_atom( "WM_TAKE_FOCUS", &qt_wm_take_focus );
+	qt_x11_intern_atom( "_NET_WM_TAKE_ACTIVITY", &qt_net_wm_take_activity );
 	qt_x11_intern_atom( "WM_CLIENT_LEADER", &qt_wm_client_leader);
 	qt_x11_intern_atom( "WM_WINDOW_ROLE", &qt_window_role);
 	qt_x11_intern_atom( "SM_CLIENT_ID", &qt_sm_client_id);
@@ -3088,6 +3096,15 @@ int QApplication::x11ClientMessage(QWidg
 				False, SubstructureNotifyMask|SubstructureRedirectMask, event );
 		}
 	    }
+	    else if ( a == qt_net_wm_take_activity ) {
+		if ( qt_take_activity_time > qt_x_time )
+		    qt_x_time = qt_take_activity_time;
+                if( qt_take_activity == 0 )
+                    qt_take_activity = new XEvent;
+                *qt_take_activity = *event;
+                qt_take_activity_window = w;
+                qt_take_activity_time = event->xclient.data.l[1];
+            }
 	} else if ( event->xclient.message_type == qt_qt_scrolldone ) {
 	    widget->translateScrollDoneEvent(event);
 	} else if ( event->xclient.message_type == qt_xdnd_position ) {
@@ -3126,6 +3143,7 @@ int QApplication::x11ClientMessage(QWidg
 */
 int QApplication::x11ProcessEvent( XEvent* event )
 {
+    qt_process_take_activity( event );
     switch ( event->type ) {
     case ButtonPress:
 	ignoreNextMouseReleaseEvent = FALSE;
@@ -5922,6 +5940,39 @@ bool QApplication::isEffectEnabled( Qt::
     }
 }
 
+void qt_process_take_activity( XEvent* event )
+{
+    switch ( event->type ) {
+    case ButtonPress:
+        // save position of the button press - if the mouse is moved far away before the release,
+        // consider it dragging/selecting and don't do any activation
+        qt_take_activity_pos = QPoint( event->xbutton.x_root, event->xbutton.y_root );
+        break;
+    case ButtonRelease:
+        if( qt_take_activity && qt_take_activity_window
+            && ( qt_take_activity_pos - QPoint( event->xbutton.x_root, event->xbutton.y_root )).manhattanLength()
+                <= QApplication::startDragDistance() ) {
+	    Window root = QPaintDevice::x11AppRootWindow( qt_take_activity_window->x11Screen() );
+            XEvent* event = qt_take_activity;
+	    if (event->xclient.window != root) {
+	        event->xclient.window = root;
+	        XSendEvent( event->xclient.display, event->xclient.window,
+			    False, SubstructureNotifyMask|SubstructureRedirectMask, event );
+	    }
+        }
+        delete qt_take_activity;
+        qt_take_activity = 0;
+	break;
+    case MotionNotify:
+        if(( qt_take_activity_pos - QPoint( event->xbutton.x_root, event->xbutton.y_root )).manhattanLength() >
+                   QApplication::startDragDistance() ) {
+            delete qt_take_activity;
+            qt_take_activity = 0;
+        }
+	break;
+    }
+}
+
 /*****************************************************************************
   Session management support
  *****************************************************************************/
--- src/kernel/qwidget_x11.cpp.sav	2004-03-02 15:10:21.000000000 +0100
+++ src/kernel/qwidget_x11.cpp	2004-04-22 10:02:45.350044664 +0200
@@ -100,6 +100,7 @@ extern Atom qt_wm_state;
 extern Atom qt_wm_change_state;
 extern Atom qt_wm_delete_window;
 extern Atom qt_wm_take_focus;
+extern Atom qt_net_wm_take_activity;
 extern Atom qt_wm_client_leader;
 extern Atom qt_window_role;
 extern Atom qt_sm_client_id;
@@ -615,11 +616,12 @@ void QWidget::create( WId window, bool i
 
 	XResizeWindow( dpy, id, crect.width(), crect.height() );
 	XStoreName( dpy, id, qAppName() );
-	Atom protocols[4];
+	Atom protocols[5];
 	int n = 0;
 	protocols[n++] = qt_wm_delete_window;	// support del window protocol
 	protocols[n++] = qt_wm_take_focus;	// support take focus window protocol
 	protocols[n++] = qt_net_wm_ping;	// support _NET_WM_PING protocol
+	protocols[n++] = qt_net_wm_take_activity; // support _NET_WM_TAKE_ACTIVITY protocol
 	if ( testWFlags( WStyle_ContextHelp ) )
 	    protocols[n++] = qt_net_wm_context_help;
 	XSetWMProtocols( dpy, id, protocols, n );


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