wineboot: Terminate processes immediately after WM_ENDSESSION.
[wine] / programs / wineboot / shutdown.c
1 /*
2  * Copyright (C) 2006 Alexandre Julliard
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include <stdarg.h>
20 #include <stdlib.h>
21
22 #include "windef.h"
23 #include "winbase.h"
24 #include "winuser.h"
25 #include "tlhelp32.h"
26
27 #include "wine/debug.h"
28
29 #include "resource.h"
30
31 WINE_DEFAULT_DEBUG_CHANNEL(wineboot);
32
33 #define MESSAGE_TIMEOUT     5000
34
35 struct window_info
36 {
37     HWND  hwnd;
38     DWORD pid;
39     DWORD tid;
40 };
41
42 static UINT win_count;
43 static UINT win_max;
44 static struct window_info *windows;
45 static DWORD desktop_pid;
46
47 /* store a new window; callback for EnumWindows */
48 static BOOL CALLBACK enum_proc( HWND hwnd, LPARAM lp )
49 {
50     if (win_count >= win_max)
51     {
52         UINT new_count = win_max * 2;
53         struct window_info *new_win = HeapReAlloc( GetProcessHeap(), 0, windows,
54                                                    new_count * sizeof(windows[0]) );
55         if (!new_win) return FALSE;
56         windows = new_win;
57         win_max = new_count;
58     }
59     windows[win_count].hwnd = hwnd;
60     windows[win_count].tid = GetWindowThreadProcessId( hwnd, &windows[win_count].pid );
61     win_count++;
62     return TRUE;
63 }
64
65 /* compare two window info structures; callback for qsort */
66 static int cmp_window( const void *ptr1, const void *ptr2 )
67 {
68     const struct window_info *info1 = ptr1;
69     const struct window_info *info2 = ptr2;
70     int ret = info1->pid - info2->pid;
71     if (!ret) ret = info1->tid - info2->tid;
72     return ret;
73 }
74
75 /* build the list of all windows (FIXME: handle multiple desktops) */
76 static BOOL get_all_windows(void)
77 {
78     win_count = 0;
79     win_max = 16;
80     windows = HeapAlloc( GetProcessHeap(), 0, win_max * sizeof(windows[0]) );
81     if (!windows) return FALSE;
82     if (!EnumWindows( enum_proc, 0 )) return FALSE;
83     /* sort windows by processes */
84     qsort( windows, win_count, sizeof(windows[0]), cmp_window );
85     return TRUE;
86 }
87
88 struct callback_data
89 {
90     UINT window_count;
91     BOOL timed_out;
92     LRESULT result;
93 };
94
95 static void CALLBACK end_session_message_callback( HWND hwnd, UINT msg, ULONG_PTR data, LRESULT lresult )
96 {
97     struct callback_data *cb_data = (struct callback_data *)data;
98
99     WINE_TRACE( "received response %s hwnd %p lresult %ld\n",
100                 msg == WM_QUERYENDSESSION ? "WM_QUERYENDSESSION" : (msg == WM_ENDSESSION ? "WM_ENDSESSION" : "Unknown"),
101                 hwnd, lresult );
102
103     /* we only care if a WM_QUERYENDSESSION response is FALSE */
104     cb_data->result = cb_data->result && lresult;
105
106     /* cheap way of ref-counting callback_data whilst freeing memory at correct
107      * time */
108     if (!(cb_data->window_count--) && cb_data->timed_out)
109         HeapFree( GetProcessHeap(), 0, cb_data );
110 }
111
112 struct endtask_dlg_data
113 {
114     struct window_info *win;
115     BOOL cancelled;
116 };
117
118 static INT_PTR CALLBACK endtask_dlg_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
119 {
120     struct endtask_dlg_data *data;
121     HANDLE handle;
122
123     switch (msg)
124     {
125     case WM_INITDIALOG:
126         SetWindowLongPtrW( hwnd, DWLP_USER, lparam );
127         data = (struct endtask_dlg_data *)lparam;
128         ShowWindow( hwnd, SW_SHOWNORMAL );
129         return TRUE;
130     case WM_COMMAND:
131         data = (struct endtask_dlg_data *)GetWindowLongPtrW( hwnd, DWLP_USER );
132         switch (wparam)
133         {
134         case MAKEWPARAM(IDOK, BN_CLICKED):
135             handle = OpenProcess( PROCESS_TERMINATE, FALSE, data->win[0].pid );
136             if (handle)
137             {
138                 WINE_TRACE( "terminating process %04x\n", data->win[0].pid );
139                 TerminateProcess( handle, 0 );
140                 CloseHandle( handle );
141             }
142             return TRUE;
143         case MAKEWPARAM(IDCANCEL, BN_CLICKED):
144             data->cancelled = TRUE;
145             return TRUE;
146         }
147         break;
148     }
149     return FALSE;
150 }
151
152 /* Sends a message to a set of windows, displaying a dialog if the window
153  * doesn't respond to the message within a set amount of time.
154  * If the process has already been terminated, the function returns -1.
155  * If the user or application cancels the process, the function returns 0.
156  * Otherwise the function returns 0. */
157 static LRESULT send_messages_with_timeout_dialog(
158     struct window_info *win, UINT count, HANDLE process_handle,
159     UINT msg, WPARAM wparam, LPARAM lparam )
160 {
161     unsigned int i;
162     DWORD ret;
163     DWORD start_time;
164     struct callback_data *cb_data;
165     HWND hwnd_endtask = NULL;
166     struct endtask_dlg_data dlg_data;
167     LRESULT result;
168
169     cb_data = HeapAlloc( GetProcessHeap(), 0, sizeof(*cb_data) );
170     if (!cb_data)
171         return 1;
172
173     cb_data->result = TRUE; /* we only care if a WM_QUERYENDSESSION response is FALSE */
174     cb_data->timed_out = FALSE;
175     cb_data->window_count = count;
176
177     dlg_data.win = win;
178     dlg_data.cancelled = FALSE;
179
180     for (i = 0; i < count; i++)
181     {
182         if (!SendMessageCallbackW( win[i].hwnd, msg, wparam, lparam,
183                                    end_session_message_callback, (ULONG_PTR)cb_data ))
184             cb_data->window_count --;
185     }
186
187     start_time = GetTickCount();
188     while (TRUE)
189     {
190         DWORD current_time = GetTickCount();
191
192         ret = MsgWaitForMultipleObjects( 1, &process_handle, FALSE,
193                                          MESSAGE_TIMEOUT - (current_time - start_time),
194                                          QS_ALLINPUT );
195         if (ret == WAIT_OBJECT_0) /* process exited */
196         {
197             HeapFree( GetProcessHeap(), 0, cb_data );
198             result = 1;
199             goto cleanup;
200         }
201         else if (ret == WAIT_OBJECT_0 + 1) /* window message */
202         {
203             MSG msg;
204             while(PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE ))
205             {
206                 if (!hwnd_endtask || !IsDialogMessageW( hwnd_endtask, &msg ))
207                 {
208                     TranslateMessage( &msg );
209                     DispatchMessageW( &msg );
210                 }
211             }
212             if (!cb_data->window_count)
213             {
214                 result = cb_data->result;
215                 HeapFree( GetProcessHeap(), 0, cb_data );
216                 if (!result)
217                     goto cleanup;
218                 break;
219             }
220             if (dlg_data.cancelled)
221             {
222                 cb_data->timed_out = TRUE;
223                 result = 0;
224                 goto cleanup;
225             }
226         }
227         else if ((ret == WAIT_TIMEOUT) && !hwnd_endtask)
228         {
229             hwnd_endtask = CreateDialogParamW( GetModuleHandleW(NULL),
230                                                MAKEINTRESOURCEW(IDD_ENDTASK),
231                                                NULL, endtask_dlg_proc,
232                                                (LPARAM)&dlg_data );
233         }
234         else break;
235     }
236
237     result = 1;
238
239 cleanup:
240     if (hwnd_endtask) DestroyWindow( hwnd_endtask );
241     return result;
242 }
243
244 /* send WM_QUERYENDSESSION and WM_ENDSESSION to all windows of a given process */
245 static DWORD_PTR send_end_session_messages( struct window_info *win, UINT count, UINT flags )
246 {
247     LRESULT result, end_session;
248     HANDLE process_handle;
249     DWORD ret;
250
251     /* FIXME: Use flags to implement EWX_FORCEIFHUNG! */
252     /* don't kill the desktop process */
253     if (win[0].pid == desktop_pid) return 1;
254
255     process_handle = OpenProcess( SYNCHRONIZE, FALSE, win[0].pid );
256     if (!process_handle)
257         return 1;
258
259     end_session = send_messages_with_timeout_dialog( win, count, process_handle,
260                                                      WM_QUERYENDSESSION, 0, 0 );
261     if (end_session == -1)
262     {
263         CloseHandle( process_handle );
264         return 1;
265     }
266
267     result = send_messages_with_timeout_dialog( win, count, process_handle,
268                                                 WM_ENDSESSION, end_session, 0 );
269     if (end_session == 0)
270     {
271         CloseHandle( process_handle );
272         return 0;
273     }
274     if (result == -1)
275     {
276         CloseHandle( process_handle );
277         return 1;
278     }
279
280     /* Check whether the app quit on its own */
281     ret = WaitForSingleObject( process_handle, 0 );
282     CloseHandle( process_handle );
283     if (ret == WAIT_TIMEOUT)
284     {
285         /* If not, it returned from all WM_ENDSESSION and is finished cleaning
286          * up, so we can safely kill the process. */
287         HANDLE handle = OpenProcess( PROCESS_TERMINATE, FALSE, win[0].pid );
288         if (handle)
289         {
290             WINE_TRACE( "terminating process %04x\n", win[0].pid );
291             TerminateProcess( handle, 0 );
292             CloseHandle( handle );
293         }
294     }
295     return 1;
296 }
297
298 /* close all top-level windows and terminate processes cleanly */
299 BOOL shutdown_close_windows( BOOL force )
300 {
301     UINT send_flags = force ? SMTO_ABORTIFHUNG : SMTO_NORMAL;
302     DWORD_PTR result = 1;
303     UINT i, n;
304
305     if (!get_all_windows()) return FALSE;
306
307     GetWindowThreadProcessId( GetDesktopWindow(), &desktop_pid );
308
309     for (i = n = 0; result && i < win_count; i++, n++)
310     {
311         if (n && windows[i-1].pid != windows[i].pid)
312         {
313             result = send_end_session_messages( windows + i - n, n, send_flags );
314             n = 0;
315         }
316     }
317     if (n && result)
318         result = send_end_session_messages( windows + win_count - n, n, send_flags );
319
320     HeapFree( GetProcessHeap(), 0, windows );
321
322     return (result != 0);
323 }
324
325 /* forcibly kill all processes without any cleanup */
326 void kill_processes( BOOL kill_desktop )
327 {
328     BOOL res;
329     UINT killed;
330     HANDLE handle, snapshot;
331     PROCESSENTRY32W process;
332
333     GetWindowThreadProcessId( GetDesktopWindow(), &desktop_pid );
334
335     do
336     {
337         if (!(snapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ))) break;
338
339         killed = 0;
340         process.dwSize = sizeof(process);
341         for (res = Process32FirstW( snapshot, &process ); res; res = Process32NextW( snapshot, &process ))
342         {
343             if (process.th32ProcessID == GetCurrentProcessId()) continue;
344             if (process.th32ProcessID == desktop_pid) continue;
345             WINE_TRACE("killing process %04x %s\n",
346                        process.th32ProcessID, wine_dbgstr_w(process.szExeFile) );
347             if (!(handle = OpenProcess( PROCESS_TERMINATE, FALSE, process.th32ProcessID )))
348                 continue;
349             if (TerminateProcess( handle, 0 )) killed++;
350             CloseHandle( handle );
351         }
352         CloseHandle( snapshot );
353     } while (killed > 0);
354
355     if (desktop_pid && kill_desktop)  /* do this last */
356     {
357         if ((handle = OpenProcess( PROCESS_TERMINATE, FALSE, desktop_pid )))
358         {
359             TerminateProcess( handle, 0 );
360             CloseHandle( handle );
361         }
362     }
363 }