2 * Copyright (C) 2006 Alexandre Julliard
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.
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.
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
27 #include "wine/debug.h"
31 WINE_DEFAULT_DEBUG_CHANNEL(wineboot
);
33 #define MESSAGE_TIMEOUT 5000
42 static UINT win_count
;
44 static struct window_info
*windows
;
45 static DWORD desktop_pid
;
47 /* store a new window; callback for EnumWindows */
48 static BOOL CALLBACK
enum_proc( HWND hwnd
, LPARAM lp
)
50 if (win_count
>= win_max
)
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
;
59 windows
[win_count
].hwnd
= hwnd
;
60 windows
[win_count
].tid
= GetWindowThreadProcessId( hwnd
, &windows
[win_count
].pid
);
65 /* compare two window info structures; callback for qsort */
66 static int __cdecl
cmp_window( const void *ptr1
, const void *ptr2
)
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
;
75 /* build the list of all windows (FIXME: handle multiple desktops) */
76 static BOOL
get_all_windows(void)
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
);
95 static void CALLBACK
end_session_message_callback( HWND hwnd
, UINT msg
, ULONG_PTR data
, LRESULT lresult
)
97 struct callback_data
*cb_data
= (struct callback_data
*)data
;
99 WINE_TRACE( "received response %s hwnd %p lresult %ld\n",
100 msg
== WM_QUERYENDSESSION
? "WM_QUERYENDSESSION" : (msg
== WM_ENDSESSION
? "WM_ENDSESSION" : "Unknown"),
103 /* If the window was destroyed while the message was in its queue, SendMessageCallback()
104 calls us with a default 0 result. Ignore it. */
105 if (!lresult
&& !IsWindow( hwnd
))
107 WINE_TRACE( "window was destroyed; ignoring FALSE lresult\n" );
111 /* we only care if a WM_QUERYENDSESSION response is FALSE */
112 cb_data
->result
= cb_data
->result
&& lresult
;
114 /* cheap way of ref-counting callback_data whilst freeing memory at correct
116 if (!(cb_data
->window_count
--) && cb_data
->timed_out
)
117 HeapFree( GetProcessHeap(), 0, cb_data
);
120 struct endtask_dlg_data
122 struct window_info
*win
;
127 static INT_PTR CALLBACK
endtask_dlg_proc( HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
129 struct endtask_dlg_data
*data
;
135 SetWindowLongPtrW( hwnd
, DWLP_USER
, lparam
);
136 ShowWindow( hwnd
, SW_SHOWNORMAL
);
139 data
= (struct endtask_dlg_data
*)GetWindowLongPtrW( hwnd
, DWLP_USER
);
142 case MAKEWPARAM(IDOK
, BN_CLICKED
):
143 handle
= OpenProcess( PROCESS_TERMINATE
, FALSE
, data
->win
[0].pid
);
146 WINE_TRACE( "terminating process %04x\n", data
->win
[0].pid
);
147 TerminateProcess( handle
, 0 );
148 CloseHandle( handle
);
149 data
->terminated
= TRUE
;
152 case MAKEWPARAM(IDCANCEL
, BN_CLICKED
):
153 data
->cancelled
= TRUE
;
161 /* Sends a message to a set of windows, displaying a dialog if the window
162 * doesn't respond to the message within a set amount of time.
163 * If the process has already been terminated, the function returns -1.
164 * If the user or application cancels the process, the function returns 0.
165 * Otherwise the function returns 0. */
166 static LRESULT
send_messages_with_timeout_dialog(
167 struct window_info
*win
, UINT count
, HANDLE process_handle
,
168 UINT msg
, WPARAM wparam
, LPARAM lparam
)
173 struct callback_data
*cb_data
;
174 HWND hwnd_endtask
= NULL
;
175 struct endtask_dlg_data dlg_data
;
178 cb_data
= HeapAlloc( GetProcessHeap(), 0, sizeof(*cb_data
) );
182 cb_data
->result
= TRUE
; /* we only care if a WM_QUERYENDSESSION response is FALSE */
183 cb_data
->timed_out
= FALSE
;
184 cb_data
->window_count
= count
;
187 dlg_data
.terminated
= FALSE
;
188 dlg_data
.cancelled
= FALSE
;
190 for (i
= 0; i
< count
; i
++)
192 if (!SendMessageCallbackW( win
[i
].hwnd
, msg
, wparam
, lparam
,
193 end_session_message_callback
, (ULONG_PTR
)cb_data
))
194 cb_data
->window_count
--;
197 start_time
= GetTickCount();
200 DWORD current_time
= GetTickCount();
202 ret
= MsgWaitForMultipleObjects( 1, &process_handle
, FALSE
,
203 MESSAGE_TIMEOUT
- (current_time
- start_time
),
205 if (ret
== WAIT_OBJECT_0
) /* process exited */
207 HeapFree( GetProcessHeap(), 0, cb_data
);
211 else if (ret
== WAIT_OBJECT_0
+ 1) /* window message */
214 while(PeekMessageW( &msg
, NULL
, 0, 0, PM_REMOVE
))
216 if (!hwnd_endtask
|| !IsDialogMessageW( hwnd_endtask
, &msg
))
218 TranslateMessage( &msg
);
219 DispatchMessageW( &msg
);
222 if (!cb_data
->window_count
)
224 result
= dlg_data
.terminated
|| cb_data
->result
;
225 HeapFree( GetProcessHeap(), 0, cb_data
);
230 if (dlg_data
.cancelled
)
232 cb_data
->timed_out
= TRUE
;
237 else if ((ret
== WAIT_TIMEOUT
) && !hwnd_endtask
)
239 hwnd_endtask
= CreateDialogParamW( GetModuleHandleW(NULL
),
240 MAKEINTRESOURCEW(IDD_ENDTASK
),
241 NULL
, endtask_dlg_proc
,
250 if (hwnd_endtask
) DestroyWindow( hwnd_endtask
);
254 /* send WM_QUERYENDSESSION and WM_ENDSESSION to all windows of a given process */
255 static DWORD_PTR
send_end_session_messages( struct window_info
*win
, UINT count
, UINT flags
)
257 LRESULT result
, end_session
;
258 HANDLE process_handle
;
261 /* FIXME: Use flags to implement EWX_FORCEIFHUNG! */
262 /* don't kill the desktop process */
263 if (win
[0].pid
== desktop_pid
) return 1;
265 process_handle
= OpenProcess( SYNCHRONIZE
, FALSE
, win
[0].pid
);
269 end_session
= send_messages_with_timeout_dialog( win
, count
, process_handle
,
270 WM_QUERYENDSESSION
, 0, 0 );
271 if (end_session
== -1)
273 CloseHandle( process_handle
);
277 result
= send_messages_with_timeout_dialog( win
, count
, process_handle
,
278 WM_ENDSESSION
, end_session
, 0 );
279 if (end_session
== 0)
281 CloseHandle( process_handle
);
286 CloseHandle( process_handle
);
290 /* Check whether the app quit on its own */
291 ret
= WaitForSingleObject( process_handle
, 0 );
292 CloseHandle( process_handle
);
293 if (ret
== WAIT_TIMEOUT
)
295 /* If not, it returned from all WM_ENDSESSION and is finished cleaning
296 * up, so we can safely kill the process. */
297 HANDLE handle
= OpenProcess( PROCESS_TERMINATE
, FALSE
, win
[0].pid
);
300 WINE_TRACE( "terminating process %04x\n", win
[0].pid
);
301 TerminateProcess( handle
, 0 );
302 CloseHandle( handle
);
308 /* close all top-level windows and terminate processes cleanly */
309 BOOL
shutdown_close_windows( BOOL force
)
311 UINT send_flags
= force
? SMTO_ABORTIFHUNG
: SMTO_NORMAL
;
312 DWORD_PTR result
= 1;
315 if (!get_all_windows()) return FALSE
;
317 GetWindowThreadProcessId( GetDesktopWindow(), &desktop_pid
);
319 for (i
= n
= 0; result
&& i
< win_count
; i
++, n
++)
321 if (n
&& windows
[i
-1].pid
!= windows
[i
].pid
)
323 result
= send_end_session_messages( windows
+ i
- n
, n
, send_flags
);
328 result
= send_end_session_messages( windows
+ win_count
- n
, n
, send_flags
);
330 HeapFree( GetProcessHeap(), 0, windows
);
332 return (result
!= 0);
335 static BOOL CALLBACK
shutdown_one_desktop( LPWSTR name
, LPARAM force
)
339 WINE_TRACE("Shutting down desktop %s\n", wine_dbgstr_w(name
));
341 hdesk
= OpenDesktopW( name
, 0, FALSE
, GENERIC_ALL
);
344 WINE_ERR("Cannot open desktop %s, err=%i\n", wine_dbgstr_w(name
), GetLastError());
348 if (!SetThreadDesktop( hdesk
))
350 CloseDesktop( hdesk
);
351 WINE_ERR("Cannot set thread desktop %s, err=%i\n", wine_dbgstr_w(name
), GetLastError());
355 CloseDesktop( hdesk
);
357 return shutdown_close_windows( force
);
360 BOOL
shutdown_all_desktops( BOOL force
)
365 prev_desktop
= GetThreadDesktop(GetCurrentThreadId());
367 ret
= EnumDesktopsW( NULL
, shutdown_one_desktop
, (LPARAM
)force
);
369 SetThreadDesktop(prev_desktop
);
374 /* forcibly kill all processes without any cleanup */
375 void kill_processes( BOOL kill_desktop
)
379 HANDLE handle
, snapshot
;
380 PROCESSENTRY32W process
;
382 GetWindowThreadProcessId( GetDesktopWindow(), &desktop_pid
);
386 if (!(snapshot
= CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS
, 0 ))) break;
389 process
.dwSize
= sizeof(process
);
390 for (res
= Process32FirstW( snapshot
, &process
); res
; res
= Process32NextW( snapshot
, &process
))
392 if (process
.th32ProcessID
== GetCurrentProcessId()) continue;
393 if (process
.th32ProcessID
== desktop_pid
) continue;
394 WINE_TRACE("killing process %04x %s\n",
395 process
.th32ProcessID
, wine_dbgstr_w(process
.szExeFile
) );
396 if (!(handle
= OpenProcess( PROCESS_TERMINATE
, FALSE
, process
.th32ProcessID
)))
398 if (TerminateProcess( handle
, 0 )) killed
++;
399 CloseHandle( handle
);
401 CloseHandle( snapshot
);
402 } while (killed
> 0);
404 if (desktop_pid
&& kill_desktop
) /* do this last */
406 if ((handle
= OpenProcess( PROCESS_TERMINATE
, FALSE
, desktop_pid
)))
408 TerminateProcess( handle
, 0 );
409 CloseHandle( handle
);