In order to make Wine ICCCM compliant and to fix focus livelock problems, it is necessary for Wine to use WM_TAKE_FOCUS protocol. This makes it necessary to stop using override-redirect for non-transient windows. I have included below a patch that does both of these things. The patch seems to work fine even with full-screen games and it does make Wine focus handling to cooperate a lot better with window managers. However, I feel reluctant to submit this patch to wine-patches, at least before I do get some feedback about the patch.
Here are some notes about the patch: - Done against the latest Wine cvs tree, file wine/dlls/ntdll/debugtools.c has some bugs that might need to be fixed first, though. - There have recently been lots of changes in directory wine/windows/x11drv and I'm not sure whether the changes are over yet. - Alexandre did mention that this fix is in his todo list, perhaps he has already planned a better way to do this fix. - Menus do work but activating them makes parent window lose focus. This does not look particularly nice. - It is highly likely that every single case of Wine using XSetInputFocus breaks ICCCM compliance. These need fixing. - It might be a good idea to make X11DRV_SetFocus call EVENT_TakeFocus, since there seems to be much shared code. - Mouse clicks over non-active windows should not automatically result into focus transfer. In these cases, Windows sends a special message that makes certain that the window really wants to gain focus. However, I don't think this is very important thing to do.
Index: wine/windows/x11drv/event.c =================================================================== RCS file: /home/wine/wine/windows/x11drv/event.c,v retrieving revision 1.112 diff -u -r1.112 event.c --- wine/windows/x11drv/event.c 2001/10/18 21:38:59 1.112 +++ wine/windows/x11drv/event.c 2001/10/21 12:26:07 @@ -42,6 +42,7 @@
extern Atom wmProtocols; extern Atom wmDeleteWindow; +extern Atom wmTakeFocus; extern Atom dndProtocol; extern Atom dndSelection;
@@ -116,7 +117,59 @@ static INPUT_TYPE current_input_type = X11DRV_INPUT_ABSOLUTE; static BOOL in_transition = FALSE; /* This is not used as for today */
+/*********************************************************************** + * EVENT_TakeFocus + */ +static void EVENT_TakeFocus( HWND hWnd, XAnyEvent *event, Time time ) +{ + XWindowAttributes win_attr; + BOOL bIsDisabled; + Window focusWindow; + HWND focusHandle; + + TRACE("called, X11 window is %08lx, window handle is %04x.\n", + event->window, hWnd); + + if (!hWnd) return; + + bIsDisabled = GetWindowLongA( hWnd, GWL_STYLE ) & WS_DISABLED; + + /* If the window has been disabled, + * 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 (bIsDisabled) + focusWindow = glastXFocusWin; + else + focusWindow = event->window; + + wine_tsx11_lock(); + + /* Change focus only if focus window is registered and viewable. */ + if (XFindContext( event->display, focusWindow, winContext, (char **)&focusHandle ) || + !XGetWindowAttributes( event->display, focusWindow, &win_attr ) || + (win_attr.map_state != IsViewable)) + { + wine_tsx11_unlock(); + return; + } + + TRACE("Set X11 focus to: %08lx\n", focusWindow); + XSetInputFocus( event->display, focusWindow, RevertToParent, time ); + + wine_tsx11_unlock(); + + glastXFocusWin = focusWindow;
+ if (focusHandle != GetForegroundWindow()) + { + TRACE("Set foreground window to: %04x\n", focusHandle); + SetForegroundWindow( focusHandle ); + } +} + + /*********************************************************************** * process_events */ @@ -274,6 +327,7 @@ break;
case ButtonPress: + EVENT_TakeFocus(hWnd, (XAnyEvent*)event, ((XButtonEvent*)event)->time); X11DRV_ButtonPress( hWnd, (XButtonEvent*)event ); break;
@@ -354,39 +408,6 @@ */ static void EVENT_FocusIn( HWND hWnd, XFocusChangeEvent *event ) { - 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) - { - /* 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(); - } - - if (event->detail != NotifyPointer && hWnd != GetForegroundWindow()) - SetForegroundWindow( hWnd ); }
@@ -397,10 +418,7 @@ */ static void EVENT_FocusOut( HWND hWnd, XFocusChangeEvent *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()) { @@ -1252,6 +1270,10 @@ EVENT_DropFromOffiX(hWnd, event); else if (event->data.l[0] == DndURL) EVENT_DropURLs(hWnd, event); + } + else if (event->message_type == wmProtocols && ((Atom)event->data.l[0]) == wmTakeFocus) + { + EVENT_TakeFocus(hWnd, (XAnyEvent*)event, (Time)event->data.l[1]); } else { #if 0
Index: wine/dlls/x11drv/window.c =================================================================== RCS file: /home/wine/wine/dlls/x11drv/window.c,v retrieving revision 1.27 diff -u -r1.27 window.c --- wine/dlls/x11drv/window.c 2001/10/18 21:38:59 1.27 +++ wine/dlls/x11drv/window.c 2001/10/21 12:27:57 @@ -44,6 +44,7 @@ Atom wmChangeState = None; Atom kwmDockWindow = None; Atom _kde_net_wm_system_tray_window_for = None; /* KDE 2 Final */ +Atom wmMotifHints = None;
static LPCSTR whole_window_atom; static LPCSTR client_window_atom; @@ -109,7 +110,7 @@ if (managed) win->dwExStyle |= WS_EX_MANAGED; else win->dwExStyle &= ~WS_EX_MANAGED;
- attr->override_redirect = !managed; + attr->override_redirect = 0; // FIXME: true for transient windows (menus) attr->colormap = X11DRV_PALETTE_PaletteXColormap; attr->save_under = ((win->clsStyle & CS_SAVEBITS) != 0); attr->cursor = None; @@ -332,6 +333,16 @@
wine_tsx11_lock();
+ /* motif hints */ + if(!(win->dwExStyle & WS_EX_MANAGED)) + { + INT32 hints[5] = {2,0,0,0,0}; + XChangeProperty( display, data->whole_window, + wmMotifHints, wmMotifHints, + 32, PropModeReplace, + (char*)hints, 5 ); + } + /* wm protocols */ i = 0; protocols[i++] = wmDeleteWindow; @@ -594,8 +605,8 @@ 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 */ + wmTakeFocus = XInternAtom( display, "WM_TAKE_FOCUS", False ); + wmMotifHints = XInternAtom( display, "_MOTIF_WM_HINTS", False ); dndProtocol = XInternAtom( display, "DndProtocol" , False ); dndSelection = XInternAtom( display, "DndSelection" , False ); wmChangeState = XInternAtom (display, "WM_CHANGE_STATE", False);