|  | /* | 
|  | * Copyright (C) 2006 Alexandre Julliard | 
|  | * | 
|  | * 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 <stdarg.h> | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include "windef.h" | 
|  | #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; | 
|  | DWORD pid; | 
|  | DWORD tid; | 
|  | }; | 
|  |  | 
|  | static UINT win_count; | 
|  | static UINT win_max; | 
|  | static struct window_info *windows; | 
|  | static DWORD desktop_pid; | 
|  |  | 
|  | /* store a new window; callback for EnumWindows */ | 
|  | static BOOL CALLBACK enum_proc( HWND hwnd, LPARAM lp ) | 
|  | { | 
|  | if (win_count >= win_max) | 
|  | { | 
|  | UINT new_count = win_max * 2; | 
|  | struct window_info *new_win = HeapReAlloc( GetProcessHeap(), 0, windows, | 
|  | new_count * sizeof(windows[0]) ); | 
|  | if (!new_win) return FALSE; | 
|  | windows = new_win; | 
|  | win_max = new_count; | 
|  | } | 
|  | windows[win_count].hwnd = hwnd; | 
|  | windows[win_count].tid = GetWindowThreadProcessId( hwnd, &windows[win_count].pid ); | 
|  | win_count++; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* compare two window info structures; callback for qsort */ | 
|  | static int cmp_window( const void *ptr1, const void *ptr2 ) | 
|  | { | 
|  | const struct window_info *info1 = ptr1; | 
|  | const struct window_info *info2 = ptr2; | 
|  | int ret = info1->pid - info2->pid; | 
|  | if (!ret) ret = info1->tid - info2->tid; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* build the list of all windows (FIXME: handle multiple desktops) */ | 
|  | static BOOL get_all_windows(void) | 
|  | { | 
|  | win_count = 0; | 
|  | win_max = 16; | 
|  | windows = HeapAlloc( GetProcessHeap(), 0, win_max * sizeof(windows[0]) ); | 
|  | if (!windows) return FALSE; | 
|  | if (!EnumWindows( enum_proc, 0 )) return FALSE; | 
|  | /* sort windows by processes */ | 
|  | qsort( windows, win_count, sizeof(windows[0]), cmp_window ); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | 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 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; | 
|  |  | 
|  | /* FIXME: Use flags to implement EWX_FORCEIFHUNG! */ | 
|  | /* don't kill the desktop process */ | 
|  | if (win[0].pid == desktop_pid) return 1; | 
|  |  | 
|  | 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) | 
|  | { | 
|  | CloseHandle( process_handle ); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | result = send_messages_with_timeout_dialog( win, count, process_handle, | 
|  | WM_ENDSESSION, end_session, 0 ); | 
|  | if (end_session == 0) | 
|  | { | 
|  | CloseHandle( process_handle ); | 
|  | return 0; | 
|  | } | 
|  | if (result == -1) | 
|  | { | 
|  | CloseHandle( process_handle ); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* 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) | 
|  | { | 
|  | WINE_TRACE( "terminating process %04x\n", win[0].pid ); | 
|  | TerminateProcess( handle, 0 ); | 
|  | CloseHandle( handle ); | 
|  | } | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* close all top-level windows and terminate processes cleanly */ | 
|  | BOOL shutdown_close_windows( BOOL force ) | 
|  | { | 
|  | UINT send_flags = force ? SMTO_ABORTIFHUNG : SMTO_NORMAL; | 
|  | DWORD_PTR result = 1; | 
|  | UINT i, n; | 
|  |  | 
|  | if (!get_all_windows()) return FALSE; | 
|  |  | 
|  | GetWindowThreadProcessId( GetDesktopWindow(), &desktop_pid ); | 
|  |  | 
|  | for (i = n = 0; result && i < win_count; i++, n++) | 
|  | { | 
|  | if (n && windows[i-1].pid != windows[i].pid) | 
|  | { | 
|  | result = send_end_session_messages( windows + i - n, n, send_flags ); | 
|  | n = 0; | 
|  | } | 
|  | } | 
|  | if (n && result) | 
|  | result = send_end_session_messages( windows + win_count - n, n, send_flags ); | 
|  |  | 
|  | HeapFree( GetProcessHeap(), 0, windows ); | 
|  |  | 
|  | return (result != 0); | 
|  | } | 
|  |  | 
|  | /* forcibly kill all processes without any cleanup */ | 
|  | void kill_processes( BOOL kill_desktop ) | 
|  | { | 
|  | BOOL res; | 
|  | UINT killed; | 
|  | HANDLE handle, snapshot; | 
|  | PROCESSENTRY32W process; | 
|  |  | 
|  | GetWindowThreadProcessId( GetDesktopWindow(), &desktop_pid ); | 
|  |  | 
|  | do | 
|  | { | 
|  | if (!(snapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ))) break; | 
|  |  | 
|  | killed = 0; | 
|  | process.dwSize = sizeof(process); | 
|  | for (res = Process32FirstW( snapshot, &process ); res; res = Process32NextW( snapshot, &process )) | 
|  | { | 
|  | if (process.th32ProcessID == GetCurrentProcessId()) continue; | 
|  | if (process.th32ProcessID == desktop_pid) continue; | 
|  | WINE_TRACE("killing process %04x %s\n", | 
|  | process.th32ProcessID, wine_dbgstr_w(process.szExeFile) ); | 
|  | if (!(handle = OpenProcess( PROCESS_TERMINATE, FALSE, process.th32ProcessID ))) | 
|  | continue; | 
|  | if (TerminateProcess( handle, 0 )) killed++; | 
|  | CloseHandle( handle ); | 
|  | } | 
|  | CloseHandle( snapshot ); | 
|  | } while (killed > 0); | 
|  |  | 
|  | if (desktop_pid && kill_desktop)  /* do this last */ | 
|  | { | 
|  | if ((handle = OpenProcess( PROCESS_TERMINATE, FALSE, desktop_pid ))) | 
|  | { | 
|  | TerminateProcess( handle, 0 ); | 
|  | CloseHandle( handle ); | 
|  | } | 
|  | } | 
|  | } |