Jukka Heinonen <jhei@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@winehq.com