Fix for focus loops

Alexandre Julliard julliard at winehq.com
Tue May 7 12:44:08 CDT 2002


Jukka Heinonen <jhei at iki.fi> writes:

> The patch should not change how Wine handles
> focus (even though Wine focus handling is quite
> far from ICCCM compliance). The patch adds one 
> SetFocus call, the missing of which is actually bug 
> because it means that X11 and Wine may have different 
> ideas about which window has focus.
> 
> Changelog:
>   Changed the input model of managed windows from
>   passive input into globally active input.
>   Moved code to detect focus changes from FocusIn
>   handler to WM_TAKE_FOCUS handler.

I've been working on this too, and I found that the locally active
model seems to work better with most window managers, even though it's
not 100% correct per the spec. Here's the patch I have, please give it
a try and let me know how it works for you.

Index: dlls/x11drv/event.c
===================================================================
RCS file: /opt/cvs-commit/wine/dlls/x11drv/event.c,v
retrieving revision 1.1
diff -u -r1.1 event.c
--- dlls/x11drv/event.c	30 Apr 2002 21:16:39 -0000	1.1
+++ dlls/x11drv/event.c	7 May 2002 17:38:25 -0000
@@ -73,9 +73,6 @@
 
 #define DndURL          128   /* KDE drag&drop */
 
-/* The last X window which had the focus */
-static Window glastXFocusWin = 0;
-
 static const char * const event_names[] =
 {
   "", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease",
@@ -90,7 +87,6 @@
 
 
 static void EVENT_ProcessEvent( XEvent *event );
-static BOOL X11DRV_CheckFocus(void);
 
   /* Event handlers */
 static void EVENT_FocusIn( HWND hWnd, XFocusChangeEvent *event );
@@ -364,91 +360,147 @@
 
 
 /**********************************************************************
- *              EVENT_FocusIn
+ *              set_focus
  */
-static void EVENT_FocusIn( HWND hWnd, XFocusChangeEvent *event )
+static void set_focus( HWND hwnd, Time time )
 {
-    WND *pWndLastFocus;
-    XWindowAttributes win_attr;
-    BOOL bIsDisabled;
-
-    if (!hWnd) return;
-
-    bIsDisabled = GetWindowLongA( hWnd, GWL_STYLE ) & WS_DISABLED;
-
-    /* If the window has been disabled and we are in managed mode,
-       * revert the X focus back to the last focus window. This is to disallow
-       * the window manager from switching focus away while the app is
-       * in a modal state.
-       */
-    if ( Options.managed && bIsDisabled && glastXFocusWin)
+    HWND focus = GetFocus();
+    Window win = X11DRV_get_whole_window( hwnd );
+
+    if (win)
     {
-        /* Change focus only if saved focus window is registered and viewable */
-        wine_tsx11_lock();
-        if (XFindContext( event->display, glastXFocusWin, winContext,
-                           (char **)&pWndLastFocus ) == 0 )
-        {
-            if (XGetWindowAttributes( event->display, glastXFocusWin, &win_attr ) &&
-                (win_attr.map_state == IsViewable) )
-            {
-                XSetInputFocus( event->display, glastXFocusWin, RevertToParent, CurrentTime );
-                wine_tsx11_unlock();
-                return;
-            }
-        }
-        wine_tsx11_unlock();
+        TRACE( "setting focus to %x (%lx) time=%ld\n", hwnd, win, time );
+        TSXSetInputFocus( thread_display(), win, RevertToParent, time );
     }
 
-    if (event->detail != NotifyPointer && hWnd != GetForegroundWindow())
-        SetForegroundWindow( hWnd );
+    if (hwnd != focus && !IsChild( hwnd, focus ))
+    {
+        TRACE( "changing window focus to %x\n", hwnd );
+        SetFocus( hwnd );
+    }
+    else TRACE( "focus already OK (%x/%x)\n", hwnd, focus );
 }
 
 
 /**********************************************************************
- *              EVENT_FocusOut
- *
- * Note: only top-level override-redirect windows get FocusOut events.
+ *              handle_wm_protocols_message
  */
-static void EVENT_FocusOut( HWND hWnd, XFocusChangeEvent *event )
+static void handle_wm_protocols_message( HWND hwnd, XClientMessageEvent *event )
 {
-    /* Save the last window which had the focus */
-    glastXFocusWin = event->window;
-    if (!hWnd) return;
-    if (GetWindowLongA( hWnd, GWL_STYLE ) & WS_DISABLED) glastXFocusWin = 0;
-
-    if (event->detail != NotifyPointer && hWnd == GetForegroundWindow())
-    {
-        /* don't reset the foreground window, if the window which is
-               getting the focus is a Wine window */
-        if (!X11DRV_CheckFocus())
-        {
-            SendMessageA( hWnd, WM_CANCELMODE, 0, 0 );
-            /* Abey : 6-Oct-99. Check again if the focus out window is the
-               Foreground window, because in most cases the messages sent
-               above must have already changed the foreground window, in which
-               case we don't have to change the foreground window to 0 */
+    Atom protocol = (Atom)event->data.l[0];
+
+    if (!protocol) return;
+
+    if (protocol == wmDeleteWindow)
+    {
+        /* Ignore the delete window request if the window has been disabled
+         * and we are in managed mode. This is to disallow applications from
+         * being closed by the window manager while in a modal state.
+         */
+        if (IsWindowEnabled(hwnd)) PostMessageW( hwnd, WM_SYSCOMMAND, SC_CLOSE, 0 );
+    }
+    else if (protocol == wmTakeFocus)
+    {
+        Time event_time = (Time)event->data.l[1];
+        HWND last_focus = x11drv_thread_data()->last_focus;
 
-            if (hWnd == GetForegroundWindow())
-                SetForegroundWindow( 0 );
+        TRACE( "got take focus msg for %x, enabled=%d, focus=%x, active=%x, fg=%x, last=%x\n",
+               hwnd, IsWindowEnabled(hwnd), GetFocus(), GetActiveWindow(),
+               GetForegroundWindow(), last_focus );
+
+        if (IsWindowEnabled(hwnd))
+        {
+            /* simulate a mouse click on the caption to find out
+             * whether the window wants to be activated */
+            LRESULT ma = SendMessageW( hwnd, WM_MOUSEACTIVATE,
+                                       GetAncestor( hwnd, GA_ROOT ),
+                                       MAKELONG(HTCAPTION,WM_LBUTTONDOWN) );
+            if (ma != MA_NOACTIVATEANDEAT && ma != MA_NOACTIVATE) set_focus( hwnd, event_time );
+            else TRACE( "not setting focus to %x (%lx), ma=%ld\n", hwnd, event->window, ma );
+        }
+        else
+        {
+            hwnd = GetFocus();
+            if (!hwnd) hwnd = GetActiveWindow();
+            if (!hwnd) hwnd = last_focus;
+            if (hwnd && IsWindowEnabled(hwnd)) set_focus( hwnd, event_time );
         }
     }
 }
 
+
+static const char * const focus_details[] =
+{
+    "NotifyAncestor",
+    "NotifyVirtual",
+    "NotifyInferior",
+    "NotifyNonlinear",
+    "NotifyNonlinearVirtual",
+    "NotifyPointer",
+    "NotifyPointerRoot",
+    "NotifyDetailNone"
+};
+
+/**********************************************************************
+ *              EVENT_FocusIn
+ */
+static void EVENT_FocusIn( HWND hwnd, XFocusChangeEvent *event )
+{
+    if (!hwnd) return;
+
+    TRACE( "win %x xwin %lx detail=%s\n", hwnd, event->window, focus_details[event->detail] );
+
+    if (wmTakeFocus) return;  /* ignore FocusIn if we are using take focus */
+    if (event->detail == NotifyPointer) return;
+
+    if (!IsWindowEnabled(hwnd))
+    {
+        HWND hwnd = GetFocus();
+        if (!hwnd) hwnd = GetActiveWindow();
+        if (!hwnd) hwnd = x11drv_thread_data()->last_focus;
+        if (hwnd && IsWindowEnabled(hwnd)) set_focus( hwnd, CurrentTime );
+    }
+    else if (hwnd != GetForegroundWindow())
+    {
+        SetForegroundWindow( hwnd );
+    }
+}
+
+
 /**********************************************************************
- *		CheckFocus (X11DRV.@)
+ *              EVENT_FocusOut
+ *
+ * Note: only top-level windows get FocusOut events.
  */
-static BOOL X11DRV_CheckFocus(void)
+static void EVENT_FocusOut( HWND hwnd, XFocusChangeEvent *event )
 {
-    Display *display = thread_display();
-  HWND   hWnd;
-  Window xW;
-  int	   state;
-  
-  TSXGetInputFocus(display, &xW, &state);
-    if( xW == None ||
-        TSXFindContext(display, xW, winContext, (char **)&hWnd) ) 
-      return FALSE;
-    return TRUE;
+    HWND hwnd_tmp;
+    Window focus_win;
+    int revert;
+
+    TRACE( "win %x xwin %lx detail=%s\n", hwnd, event->window, focus_details[event->detail] );
+
+    if (event->detail == NotifyPointer) return;
+    if (hwnd != GetForegroundWindow()) return;
+    SendMessageA( hwnd, WM_CANCELMODE, 0, 0 );
+
+    /* don't reset the foreground window, if the window which is
+       getting the focus is a Wine window */
+
+    TSXGetInputFocus( thread_display(), &focus_win, &revert );
+    if (!focus_win || TSXFindContext( thread_display(), focus_win, winContext, (char **)&hwnd_tmp ))
+    {
+        /* Abey : 6-Oct-99. Check again if the focus out window is the
+           Foreground window, because in most cases the messages sent
+           above must have already changed the foreground window, in which
+           case we don't have to change the foreground window to 0 */
+        if (hwnd == GetForegroundWindow())
+        {
+            TRACE( "lost focus, setting fg to 0\n" );
+            x11drv_thread_data()->last_focus = hwnd;
+            SetForegroundWindow( 0 );
+        }
+    }
 }
 
 
@@ -1240,19 +1292,16 @@
   }
 }
 
+
 /**********************************************************************
  *           EVENT_ClientMessage
  */
 static void EVENT_ClientMessage( HWND hWnd, XClientMessageEvent *event )
 {
-  if (event->message_type != None && event->format == 32) {
-    if ((event->message_type == wmProtocols) && 
-	(((Atom) event->data.l[0]) == wmDeleteWindow))
-    {
-        /* Ignore the delete window request if the window has been disabled */
-        if (!(GetWindowLongA( hWnd, GWL_STYLE ) & WS_DISABLED))
-            PostMessageA( hWnd, WM_SYSCOMMAND, SC_CLOSE, 0 );
-    }
+  if (event->message_type != None && event->format == 32)
+  {
+    if (event->message_type == wmProtocols)
+        handle_wm_protocols_message( hWnd, event );
     else if (event->message_type == dndProtocol)
     {
         /* query window (drag&drop event contains only drag window) */
Index: dlls/x11drv/window.c
===================================================================
RCS file: /opt/cvs-commit/wine/dlls/x11drv/window.c,v
retrieving revision 1.32
diff -u -r1.32 window.c
--- dlls/x11drv/window.c	7 May 2002 01:52:15 -0000	1.32
+++ dlls/x11drv/window.c	7 May 2002 17:38:28 -0000
@@ -420,9 +420,7 @@
     if ((wm_hints = TSXAllocWMHints()))
     {
         wm_hints->flags = InputHint | StateHint | WindowGroupHint;
-        /* use globally active model if take focus is supported,
-         * passive model otherwise (cf. ICCCM) */
-        wm_hints->input = !wmTakeFocus;
+        wm_hints->input = !(win->dwStyle & WS_DISABLED);
 
         set_icon_hints( display, win, wm_hints );
 
@@ -637,8 +635,7 @@
     winContext     = XUniqueContext();
     wmProtocols    = XInternAtom( display, "WM_PROTOCOLS", False );
     wmDeleteWindow = XInternAtom( display, "WM_DELETE_WINDOW", False );
-/*    wmTakeFocus    = XInternAtom( display, "WM_TAKE_FOCUS", False );*/
-    wmTakeFocus = 0;  /* not yet */
+    if (use_take_focus) wmTakeFocus = XInternAtom( display, "WM_TAKE_FOCUS", False );
     dndProtocol = XInternAtom( display, "DndProtocol" , False );
     dndSelection = XInternAtom( display, "DndSelection" , False );
     wmChangeState = XInternAtom( display, "WM_CHANGE_STATE", False );
@@ -831,7 +828,8 @@
  */
 BOOL X11DRV_DestroyWindow( HWND hwnd )
 {
-    Display *display = thread_display();
+    struct x11drv_thread_data *thread_data = x11drv_thread_data();
+    Display *display = thread_data->display;
     WND *wndPtr = WIN_GetPtr( hwnd );
     X11DRV_WND_DATA *data = wndPtr->pDriverData;
 
@@ -840,6 +838,7 @@
     if (data->whole_window)
     {
         TRACE( "win %x xwin %lx/%lx\n", hwnd, data->whole_window, data->client_window );
+        if (thread_data->last_focus == hwnd) thread_data->last_focus = 0;
         wine_tsx11_lock();
         XSync( gdi_display, False );  /* flush any reference to this drawable in GDI queue */
         XDeleteContext( display, data->whole_window, winContext );
Index: dlls/x11drv/x11drv_main.c
===================================================================
RCS file: /opt/cvs-commit/wine/dlls/x11drv/x11drv_main.c,v
retrieving revision 1.49
diff -u -r1.49 x11drv_main.c
--- dlls/x11drv/x11drv_main.c	24 Apr 2002 21:32:11 -0000	1.49
+++ dlls/x11drv/x11drv_main.c	7 May 2002 17:38:29 -0000
@@ -61,6 +61,7 @@
 unsigned int screen_depth;
 Window root_window;
 int dxgrab, usedga, usexvidmode;
+BOOL use_take_focus = TRUE;
 
 unsigned int X11DRV_server_startticks;
 
@@ -249,6 +250,9 @@
     if (!get_config_key( hkey, appkey, "UseXVidMode", buffer, sizeof(buffer) ))
         usexvidmode = IS_OPTION_TRUE( buffer[0] );
 
+    if (!get_config_key( hkey, appkey, "UseTakeFocus", buffer, sizeof(buffer) ))
+        use_take_focus = IS_OPTION_TRUE( buffer[0] );
+
     screen_depth = 0;
     if (!get_config_key( hkey, appkey, "ScreenDepth", buffer, sizeof(buffer) ))
         screen_depth = atoi(buffer);
@@ -460,6 +464,7 @@
     data->display_fd = FILE_DupUnixHandle( ConnectionNumber(data->display),
                                            GENERIC_READ | SYNCHRONIZE, FALSE );
     data->process_event_count = 0;
+    data->last_focus = 0;
     NtCurrentTeb()->driver_data = data;
     return data;
 }
Index: include/x11drv.h
===================================================================
RCS file: /opt/cvs-commit/wine/include/x11drv.h,v
retrieving revision 1.104
diff -u -r1.104 x11drv.h
--- include/x11drv.h	2 May 2002 01:39:48 -0000	1.104
+++ include/x11drv.h	7 May 2002 17:38:31 -0000
@@ -332,6 +332,7 @@
     Display *display;
     HANDLE   display_fd;
     int      process_event_count;  /* recursion count for event processing */
+    HWND     last_focus;           /* last window that had focus */
 };
 
 extern struct x11drv_thread_data *x11drv_init_thread_data(void);
@@ -351,6 +352,7 @@
 extern unsigned int screen_height;
 extern unsigned int screen_depth;
 extern unsigned int text_caps;
+extern BOOL use_take_focus;
 
 extern Atom wmProtocols;
 extern Atom wmDeleteWindow;

-- 
Alexandre Julliard
julliard at winehq.com



More information about the wine-patches mailing list