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