wineboot: Add a timeout dialog when any WM_QUERYENDSESSION or WM_ENDSESSION messages take too long.
diff --git a/programs/wineboot/Makefile.in b/programs/wineboot/Makefile.in
index b11b280..1f751ce 100644
--- a/programs/wineboot/Makefile.in
+++ b/programs/wineboot/Makefile.in
@@ -11,6 +11,8 @@
 	shutdown.c \
 	wineboot.c
 
+RC_SRCS = wineboot.rc
+
 @MAKE_PROG_RULES@
 
 @DEPENDENCIES@  # everything below this line is overwritten by make depend
diff --git a/programs/wineboot/resource.h b/programs/wineboot/resource.h
new file mode 100644
index 0000000..3838922
--- /dev/null
+++ b/programs/wineboot/resource.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2007 Robert Shearman for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#define IDC_STATIC          -1
+#define IDD_ENDTASK         100
diff --git a/programs/wineboot/shutdown.c b/programs/wineboot/shutdown.c
index 5f8dadf..b3825e8 100644
--- a/programs/wineboot/shutdown.c
+++ b/programs/wineboot/shutdown.c
@@ -23,10 +23,16 @@
 #include "winbase.h"
 #include "winuser.h"
 #include "tlhelp32.h"
+
 #include "wine/debug.h"
 
+#include "resource.h"
+
 WINE_DEFAULT_DEBUG_CHANNEL(wineboot);
 
+#define MESSAGE_TIMEOUT     5000
+#define PROCQUIT_TIMEOUT    20000
+
 struct window_info
 {
     HWND  hwnd;
@@ -80,36 +86,203 @@
     return TRUE;
 }
 
-/* send WM_QUERYENDSESSION and WM_ENDSESSION to all windows of a given process */
-/* FIXME: should display a confirmation dialog if process doesn't respond to the messages */
-static DWORD_PTR send_end_session_messages( struct window_info *win, UINT count, UINT flags )
+struct callback_data
+{
+    UINT window_count;
+    BOOL timed_out;
+    LRESULT result;
+};
+
+static void CALLBACK end_session_message_callback( HWND hwnd, UINT msg, ULONG_PTR data, LRESULT lresult )
+{
+    struct callback_data *cb_data = (struct callback_data *)data;
+
+    WINE_TRACE( "received response %s hwnd %p lresult %ld\n",
+                msg == WM_QUERYENDSESSION ? "WM_QUERYENDSESSION" : (msg == WM_ENDSESSION ? "WM_ENDSESSION" : "Unknown"),
+                hwnd, lresult );
+
+    /* we only care if a WM_QUERYENDSESSION response is FALSE */
+    cb_data->result = cb_data->result && lresult;
+
+    /* cheap way of ref-counting callback_data whilst freeing memory at correct
+     * time */
+    if (!(cb_data->window_count--) && cb_data->timed_out)
+        HeapFree( GetProcessHeap(), 0, cb_data );
+}
+
+struct endtask_dlg_data
+{
+    struct window_info *win;
+    BOOL cancelled;
+};
+
+static INT_PTR CALLBACK endtask_dlg_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
+{
+    struct endtask_dlg_data *data;
+    HANDLE handle;
+
+    switch (msg)
+    {
+    case WM_INITDIALOG:
+        SetWindowLongPtrW( hwnd, DWLP_USER, lparam );
+        data = (struct endtask_dlg_data *)lparam;
+        ShowWindow( hwnd, SW_SHOWNORMAL );
+        return TRUE;
+    case WM_COMMAND:
+        data = (struct endtask_dlg_data *)GetWindowLongPtrW( hwnd, DWLP_USER );
+        switch (wparam)
+        {
+        case MAKEWPARAM(IDOK, BN_CLICKED):
+            handle = OpenProcess( PROCESS_TERMINATE, FALSE, data->win[0].pid );
+            if (handle)
+            {
+                WINE_TRACE( "terminating process %04x\n", data->win[0].pid );
+                TerminateProcess( handle, 0 );
+                CloseHandle( handle );
+            }
+            return TRUE;
+        case MAKEWPARAM(IDCANCEL, BN_CLICKED):
+            data->cancelled = TRUE;
+            return TRUE;
+        }
+        break;
+    }
+    return FALSE;
+}
+
+/* Sends a message to a set of windows, displaying a dialog if the window
+ * doesn't respond to the message within a set amount of time.
+ * If the process has already been terminated, the function returns -1.
+ * If the user or application cancels the process, the function returns 0.
+ * Otherwise the function returns 0. */
+static LRESULT send_messages_with_timeout_dialog(
+    struct window_info *win, UINT count, HANDLE process_handle,
+    UINT msg, WPARAM wparam, LPARAM lparam )
 {
     unsigned int i;
-    DWORD_PTR result, ret = 1;
+    DWORD ret;
+    DWORD start_time;
+    struct callback_data *cb_data;
+    HWND hwnd_endtask = NULL;
+    struct endtask_dlg_data dlg_data;
+    LRESULT result;
+
+    cb_data = HeapAlloc( GetProcessHeap(), 0, sizeof(*cb_data) );
+    if (!cb_data)
+        return 1;
+
+    cb_data->result = TRUE; /* we only care if a WM_QUERYENDSESSION response is FALSE */
+    cb_data->timed_out = FALSE;
+    cb_data->window_count = count;
+
+    dlg_data.win = win;
+    dlg_data.cancelled = FALSE;
+
+    for (i = 0; i < count; i++)
+    {
+        if (!SendMessageCallbackW( win[i].hwnd, msg, wparam, lparam,
+                                   end_session_message_callback, (ULONG_PTR)cb_data ))
+            cb_data->window_count --;
+    }
+
+    start_time = GetTickCount();
+    while (TRUE)
+    {
+        DWORD current_time = GetTickCount();
+
+        ret = MsgWaitForMultipleObjects( 1, &process_handle, FALSE,
+                                         MESSAGE_TIMEOUT - (current_time - start_time),
+                                         QS_ALLINPUT );
+        if (ret == WAIT_OBJECT_0) /* process exited */
+        {
+            HeapFree( GetProcessHeap(), 0, cb_data );
+            result = 1;
+            goto cleanup;
+        }
+        else if (ret == WAIT_OBJECT_0 + 1) /* window message */
+        {
+            MSG msg;
+            while(PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE ))
+            {
+                if (!hwnd_endtask || !IsDialogMessageW( hwnd_endtask, &msg ))
+                {
+                    TranslateMessage( &msg );
+                    DispatchMessageW( &msg );
+                }
+            }
+            if (!cb_data->window_count)
+            {
+                result = cb_data->result;
+                HeapFree( GetProcessHeap(), 0, cb_data );
+                if (!result)
+                    goto cleanup;
+                break;
+            }
+            if (dlg_data.cancelled)
+            {
+                cb_data->timed_out = TRUE;
+                result = 0;
+                goto cleanup;
+            }
+        }
+        else if ((ret == WAIT_TIMEOUT) && !hwnd_endtask)
+        {
+            hwnd_endtask = CreateDialogParamW( GetModuleHandle(NULL),
+                                               MAKEINTRESOURCEW(IDD_ENDTASK),
+                                               NULL, endtask_dlg_proc,
+                                               (LPARAM)&dlg_data );
+        }
+        else break;
+    }
+
+    result = 1;
+
+cleanup:
+    if (hwnd_endtask) DestroyWindow( hwnd_endtask );
+    return result;
+}
+
+/* send WM_QUERYENDSESSION and WM_ENDSESSION to all windows of a given process */
+static DWORD_PTR send_end_session_messages( struct window_info *win, UINT count, UINT flags )
+{
+    LRESULT result, end_session;
+    HANDLE process_handle;
+    DWORD ret;
 
     /* don't kill the desktop process */
     if (win[0].pid == desktop_pid) return 1;
 
-    for (i = 0; ret && i < count; i++)
+    process_handle = OpenProcess( SYNCHRONIZE, FALSE, win[0].pid );
+    if (!process_handle)
+        return 1;
+
+    end_session = send_messages_with_timeout_dialog( win, count, process_handle,
+                                                     WM_QUERYENDSESSION, 0, 0 );
+    if (end_session == -1)
     {
-        if (SendMessageTimeoutW( win[i].hwnd, WM_QUERYENDSESSION, 0, 0, flags, 0, &result ))
-        {
-            WINE_TRACE( "sent MW_QUERYENDSESSION hwnd %p pid %04x result %ld\n",
-                        win[i].hwnd, win[i].pid, result );
-            ret = result;
-        }
-        else win[i].hwnd = 0;  /* ignore this window */
+        CloseHandle( process_handle );
+        return 1;
     }
 
-    for (i = 0; i < count; i++)
+    result = send_messages_with_timeout_dialog( win, count, process_handle,
+                                                WM_ENDSESSION, end_session, 0 );
+    if (end_session == 0)
     {
-        if (!win[i].hwnd) continue;
-        WINE_TRACE( "sending WM_ENDSESSION hwnd %p pid %04x wp %ld\n", win[i].hwnd, win[i].pid, ret );
-        SendMessageTimeoutW( win[i].hwnd, WM_ENDSESSION, ret, 0, flags, 0, &result );
+        CloseHandle( process_handle );
+        return 0;
+    }
+    if (result == -1)
+    {
+        CloseHandle( process_handle );
+        return 1;
     }
 
-    if (ret)
+    /* wait for app to quit on its own for a while */
+    ret = WaitForSingleObject( process_handle, PROCQUIT_TIMEOUT );
+    CloseHandle( process_handle );
+    if (ret == WAIT_TIMEOUT)
     {
+        /* it didn't quit by itself in time, so terminate it with extreme prejudice */
         HANDLE handle = OpenProcess( PROCESS_TERMINATE, FALSE, win[0].pid );
         if (handle)
         {
@@ -118,7 +291,7 @@
             CloseHandle( handle );
         }
     }
-    return ret;
+    return 1;
 }
 
 /* close all top-level windows and terminate processes cleanly */
diff --git a/programs/wineboot/wineboot.rc b/programs/wineboot/wineboot.rc
new file mode 100644
index 0000000..4607e01
--- /dev/null
+++ b/programs/wineboot/wineboot.rc
@@ -0,0 +1,30 @@
+/*
+ * WineBoot resources
+ *
+ * Copyright (C) 2007 Robert Shearman for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ */
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winnls.h"
+#include "winuser.h"
+#include "commctrl.h"
+#include "resource.h"
+
+#include "wineboot_En.rc"
diff --git a/programs/wineboot/wineboot_En.rc b/programs/wineboot/wineboot_En.rc
new file mode 100644
index 0000000..45abe9c
--- /dev/null
+++ b/programs/wineboot/wineboot_En.rc
@@ -0,0 +1,35 @@
+/*
+ * WineBoot resources
+ *
+ * Copyright (C) 2007 Robert Shearman for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ */
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+IDD_ENDTASK DIALOG DISCARDABLE  0, 0, 186, 71
+STYLE DS_MODALFRAME | DS_NOIDLEMSG | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Waiting for Program"
+FONT 8, "MS Shell Dlg"
+BEGIN
+    DEFPUSHBUTTON   "Terminate Process",IDOK,51,49,71,15
+    PUSHBUTTON      "Cancel",IDCANCEL,129,49,50,15
+    LTEXT           "A simulated log-off or shutdown is in progress, but this program isn't responding.",
+                    IDC_STATIC,7,7,172,19
+    LTEXT           "If you terminate the process you may lose all unsaved data.",
+                    IDC_STATIC,7,28,172,15
+END