windows-nt/Source/XPSP1/NT/shell/shell32/restart.c
2020-09-26 16:20:57 +08:00

734 lines
20 KiB
C

#include "shellprv.h"
#pragma hdrstop
#include <msginaexports.h>
#include <ntddapmt.h>
#include <lmcons.h> // Username length constant
#include <winsta.h> // Hydra functions/constants
#include <powrprof.h>
#include "SwitchUserDialog.h"
#include "filetbl.h"
#define DOCKSTATE_DOCKED 0
#define DOCKSTATE_UNDOCKED 1
#define DOCKSTATE_UNKNOWN 2
void FlushRunDlgMRU(void);
// Disconnect API fn-ptr
typedef BOOLEAN (WINAPI *PWINSTATION_DISCONNECT) (HANDLE hServer, ULONG SessionId, BOOL bWait);
// Process all of the strange ExitWindowsEx codes and privileges.
STDAPI_(BOOL) CommonRestart(DWORD dwExitWinCode, DWORD dwReasonCode)
{
BOOL fOk;
DWORD dwExtraExitCode = 0;
DWORD OldState;
DWORD dwError;
DebugMsg(DM_TRACE, TEXT("CommonRestart(0x%x, 0x%x)"), dwExitWinCode, dwReasonCode);
IconCacheSave();
if ((dwExitWinCode == EWX_SHUTDOWN) && IsPwrShutdownAllowed())
{
dwExtraExitCode = EWX_POWEROFF;
}
dwError = SetPrivilegeAttribute(SE_SHUTDOWN_NAME, SE_PRIVILEGE_ENABLED, &OldState);
switch (dwExitWinCode)
{
case EWX_SHUTDOWN:
case EWX_REBOOT:
case EWX_LOGOFF:
if (GetKeyState(VK_CONTROL) < 0)
{
dwExtraExitCode |= EWX_FORCE;
}
break;
}
fOk = ExitWindowsEx(dwExitWinCode | dwExtraExitCode, dwReasonCode);
// If we were able to set the privilege, then reset it.
if (dwError == ERROR_SUCCESS)
{
SetPrivilegeAttribute(SE_SHUTDOWN_NAME, OldState, NULL);
}
else
{
// Otherwise, if we failed, then it must have been some
// security stuff.
if (!fOk)
{
ShellMessageBox(HINST_THISDLL, NULL,
dwExitWinCode == EWX_SHUTDOWN ?
MAKEINTRESOURCE(IDS_NO_PERMISSION_SHUTDOWN) :
MAKEINTRESOURCE(IDS_NO_PERMISSION_RESTART),
dwExitWinCode == EWX_SHUTDOWN ?
MAKEINTRESOURCE(IDS_SHUTDOWN) :
MAKEINTRESOURCE(IDS_RESTART),
MB_OK | MB_ICONSTOP);
}
}
DebugMsg(DM_TRACE, TEXT("CommonRestart done"));
return fOk;
}
void EarlySaveSomeShellState()
{
// We flush two MRU's here (RecentMRU and RunDlgMRU).
// Note that they won't flush if there is any reference count.
FlushRunDlgMRU();
}
/*
* Display a dialog asking the user to restart Windows, with a button that
* will do it for them if possible.
*/
STDAPI_(int) RestartDialog(HWND hParent, LPCTSTR lpPrompt, DWORD dwReturn)
{
return RestartDialogEx(hParent, lpPrompt, dwReturn, 0);
}
STDAPI_(int) RestartDialogEx(HWND hParent, LPCTSTR lpPrompt, DWORD dwReturn, DWORD dwReasonCode)
{
UINT id;
LPCTSTR pszMsg;
EarlySaveSomeShellState();
if (lpPrompt && *lpPrompt == TEXT('#'))
{
pszMsg = lpPrompt + 1;
}
else if (dwReturn == EWX_SHUTDOWN)
{
pszMsg = MAKEINTRESOURCE(IDS_RSDLG_SHUTDOWN);
}
else
{
pszMsg = MAKEINTRESOURCE(IDS_RSDLG_RESTART);
}
id = ShellMessageBox(HINST_THISDLL, hParent, pszMsg, MAKEINTRESOURCE(IDS_RSDLG_TITLE),
MB_YESNO | MB_ICONQUESTION, lpPrompt ? lpPrompt : c_szNULL);
if (id == IDYES)
{
CommonRestart(dwReturn, dwReasonCode);
}
return id;
}
const TCHAR c_szREGSTR_ROOT_APM[] = REGSTR_KEY_ENUM TEXT("\\") REGSTR_KEY_ROOTENUM TEXT("\\") REGSTR_KEY_APM TEXT("\\") REGSTR_DEFAULT_INSTANCE;
const TCHAR c_szREGSTR_BIOS_APM[] = REGSTR_KEY_ENUM TEXT("\\") REGSTR_KEY_BIOSENUM TEXT("\\") REGSTR_KEY_APM;
const TCHAR c_szREGSTR_VAL_APMMENUSUSPEND[] = REGSTR_VAL_APMMENUSUSPEND;
/* Open the registry APM device key
*/
BOOL OpenAPMKey(HKEY *phKey)
{
HKEY hBiosSys;
BOOL rc = FALSE;
TCHAR szInst[MAX_PATH+1];
DWORD cchInst = ARRAYSIZE(szInst);
// Open HKLM\Enum\Root\*PNP0C05\0000 - This is the APM key for
// non-PnP BIOS machines.
if (RegOpenKey(HKEY_LOCAL_MACHINE, c_szREGSTR_ROOT_APM, phKey) == ERROR_SUCCESS)
return TRUE;
// Open HKLM\Enum\BIOS\*PNP0C05, Enum the 1st subkey, open that. Example:
// HKLM\Enum\BIOS\*PNP0C05\03.
if (RegOpenKey(HKEY_LOCAL_MACHINE,c_szREGSTR_BIOS_APM,&hBiosSys) == ERROR_SUCCESS)
{
if (RegEnumKey(hBiosSys, 0, szInst, cchInst) == ERROR_SUCCESS &&
RegOpenKey(hBiosSys, szInst, phKey) == ERROR_SUCCESS)
rc = TRUE;
RegCloseKey(hBiosSys);
}
return rc;
}
BOOL CheckBIOS(void)
{
HKEY hkey;
BOOL fRet = TRUE;
BOOL fSuspendUndocked = TRUE;
/* Check the Registry APM key for an APMMenuSuspend value.
* APMMenuSuspend may have the following values: APMMENUSUSPEND_DISABLED,
* APMMENUSUSPEND_ENABLED, or APMMENUSUSPEND_UNDOCKED.
*
* An APMMenuSuspend value of APMMENUSUSPEND_DISABLED means the
* tray should never show the Suspend menu item on its menu.
*
* APMMENUSUSPEND_ENABLED means the Suspend menu item should be shown
* if the machine has APM support enabled (VPOWERD is loaded). This is
* the default.
*
* APMMENUSUSPEND_UNDOCKED means the Suspend menu item should be shown,
* but only enabled when the machine is not in a docking station.
*
*/
if (OpenAPMKey(&hkey))
{
BYTE bMenuSuspend = APMMENUSUSPEND_ENABLED;
DWORD dwType, cbSize = sizeof(bMenuSuspend);
if (SHQueryValueEx(hkey, c_szREGSTR_VAL_APMMENUSUSPEND, 0,
&dwType, &bMenuSuspend, &cbSize) == ERROR_SUCCESS)
{
bMenuSuspend &= ~(APMMENUSUSPEND_NOCHANGE); // don't care about nochange flag
if (bMenuSuspend == APMMENUSUSPEND_UNDOCKED)
fSuspendUndocked = TRUE;
else
{
fSuspendUndocked = FALSE;
if (bMenuSuspend == APMMENUSUSPEND_DISABLED)
fRet = FALSE;
}
}
RegCloseKey(hkey);
}
if (fRet)
{
// Disable Suspend menu item if 1) only wanted when undocked and
// system is currently docked, 2) power mgnt level < advanced
if (fSuspendUndocked && SHGetMachineInfo(GMI_DOCKSTATE) != GMID_UNDOCKED)
fRet = FALSE;
else
{
DWORD dwPmLevel, cbOut;
BOOL fIoSuccess;
HANDLE hVPowerD = CreateFile(TEXT("\\\\.\\APMTEST"),
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (hVPowerD != INVALID_HANDLE_VALUE)
{
fIoSuccess = DeviceIoControl(hVPowerD, APM_IOCTL_GET_PM_LEVEL, NULL, 0, &dwPmLevel, sizeof(dwPmLevel), &cbOut, NULL);
fRet = (fIoSuccess && (dwPmLevel == PMLEVEL_ADVANCED));
CloseHandle (hVPowerD);
}
else
{
fRet = FALSE;
}
}
}
return fRet;
}
BOOL IsShutdownAllowed(void)
{
return SHTestTokenPrivilege(NULL, SE_SHUTDOWN_NAME);
}
// Determine if "Suspend" should appear in the shutdown dialog.
// Returns: TRUE if Suspend should appear, FALSE if not.
STDAPI_(BOOL) IsSuspendAllowed(void)
{
//
// Suspend requires SE_SHUTDOWN_PRIVILEGE
// Call IsShutdownAllowed() to test for this
//
return IsShutdownAllowed() && IsPwrSuspendAllowed();
}
BOOL _LogoffAvailable()
{
// If dwStartMenuLogoff is zero, then we remove it.
BOOL fUpgradeFromIE4 = FALSE;
BOOL fUserWantsLogoff = FALSE;
DWORD dwStartMenuLogoff = 0;
TCHAR sz[MAX_PATH];
DWORD dwRestriction = SHRestricted(REST_STARTMENULOGOFF);
DWORD cbData = sizeof(dwStartMenuLogoff);
if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, REGSTR_EXPLORER_ADVANCED,
TEXT("StartMenuLogoff"), NULL, &dwStartMenuLogoff, &cbData))
{
fUserWantsLogoff = (dwStartMenuLogoff != 0);
}
cbData = ARRAYSIZE(sz);
if (SUCCEEDED(SKGetValue(SHELLKEY_HKLM_EXPLORER, TEXT("WindowsUpdate"),
TEXT("UpdateURL"), NULL, sz, &cbData)))
{
fUpgradeFromIE4 = (sz[0] != TEXT('\0'));
}
// Admin is forcing the logoff to be on the menu
if (dwRestriction == 2)
return FALSE;
// The user does wants logoff on the start menu.
// Or it's an upgrade from IE4
if ((fUpgradeFromIE4 || fUserWantsLogoff) && dwRestriction != 1)
return FALSE;
return TRUE;
}
DWORD GetShutdownOptions()
{
LONG lResult = ERROR_SUCCESS + 1;
DWORD dwOptions = SHTDN_SHUTDOWN;
// No shutdown on terminal server
if (!GetSystemMetrics(SM_REMOTESESSION))
{
dwOptions |= SHTDN_RESTART;
}
// Add logoff if supported
if (_LogoffAvailable())
{
dwOptions |= SHTDN_LOGOFF;
}
// Add the hibernate option if it's supported.
if (IsPwrHibernateAllowed())
{
dwOptions |= SHTDN_HIBERNATE;
}
if (IsSuspendAllowed())
{
HKEY hKey;
DWORD dwAdvSuspend = 0;
DWORD dwType, dwSize;
// At least basic sleep is supported
dwOptions |= SHTDN_SLEEP;
//
// Check if we should offer advanced suspend options
//
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
TEXT("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Power"),
0, KEY_READ, &hKey) == ERROR_SUCCESS)
{
dwSize = sizeof(dwAdvSuspend);
SHQueryValueEx(hKey, TEXT("Shutdown"), NULL, &dwType,
(LPBYTE) &dwAdvSuspend, &dwSize);
RegCloseKey(hKey);
}
if (dwAdvSuspend != 0)
{
dwOptions |= SHTDN_SLEEP2;
}
}
return dwOptions;
}
BOOL_PTR CALLBACK LogoffDlgProc(HWND hdlg, UINT msg, WPARAM wparam, LPARAM lparam)
{
static BOOL s_fLogoffDialog = FALSE;
HICON hIcon;
switch (msg)
{
case WM_INITMENUPOPUP:
EnableMenuItem((HMENU)wparam, SC_MOVE, MF_BYCOMMAND|MF_GRAYED);
break;
case WM_INITDIALOG:
// We could call them when the user actually selects the shutdown,
// but I put them here to leave the shutdown process faster.
//
EarlySaveSomeShellState();
s_fLogoffDialog = FALSE;
hIcon = LoadImage (HINST_THISDLL, MAKEINTRESOURCE(IDI_STLOGOFF),
IMAGE_ICON, 48, 48, LR_DEFAULTCOLOR);
if (hIcon)
{
SendDlgItemMessage (hdlg, IDD_LOGOFFICON, STM_SETICON, (WPARAM) hIcon, 0);
}
return TRUE;
// Blow off moves (only really needed for 32bit land).
case WM_SYSCOMMAND:
if ((wparam & ~0x0F) == SC_MOVE)
return TRUE;
break;
case WM_COMMAND:
switch (LOWORD(wparam))
{
case IDOK:
s_fLogoffDialog = TRUE;
EndDialog(hdlg, SHTDN_LOGOFF);
break;
case IDCANCEL:
s_fLogoffDialog = TRUE;
EndDialog(hdlg, SHTDN_NONE);
break;
case IDHELP:
WinHelp(hdlg, TEXT("windows.hlp>proc4"), HELP_CONTEXT, (DWORD) IDH_TRAY_SHUTDOWN_HELP);
break;
}
break;
case WM_ACTIVATE:
// If we're loosing the activation for some other reason than
// the user click OK/CANCEL then bail.
if (LOWORD(wparam) == WA_INACTIVE && !s_fLogoffDialog)
{
s_fLogoffDialog = TRUE;
EndDialog(hdlg, SHTDN_NONE);
}
break;
}
return FALSE;
}
// These dialog procedures more or less mirror the behavior of LogoffDlgProc.
INT_PTR CALLBACK DisconnectDlgProc(HWND hwndDialog, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static BOOL s_fIgnoreActivate = FALSE;
INT_PTR ipResult = FALSE;
switch (uMsg)
{
case WM_INITMENUPOPUP:
EnableMenuItem((HMENU)wParam, SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
break;
case WM_INITDIALOG:
{
HICON hIcon;
EarlySaveSomeShellState();
s_fIgnoreActivate = FALSE;
hIcon = LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_MU_DISCONN), IMAGE_ICON, 48, 48, LR_DEFAULTCOLOR);
if (hIcon != NULL)
{
SendDlgItemMessage(hwndDialog, IDD_DISCONNECTICON, STM_SETICON, (WPARAM)hIcon, 0);
}
ipResult = TRUE;
break;
}
case WM_SYSCOMMAND:
ipResult = ((wParam & ~0x0F) == SC_MOVE);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
s_fIgnoreActivate = TRUE;
TBOOL(EndDialog(hwndDialog, SHTDN_DISCONNECT));
break;
case IDCANCEL:
s_fIgnoreActivate = TRUE;
TBOOL(EndDialog(hwndDialog, SHTDN_NONE));
break;
}
break;
case WM_ACTIVATE:
if ((WA_INACTIVE == LOWORD(wParam)) && !s_fIgnoreActivate)
{
s_fIgnoreActivate = TRUE;
TBOOL(EndDialog(hwndDialog, SHTDN_NONE));
}
break;
}
return ipResult;
}
BOOL CanDoFastRestart()
{
return GetAsyncKeyState(VK_SHIFT) < 0;
}
// ---------------------------------------------------------------------------
// Shutdown thread
typedef struct
{
DWORD_PTR nCmd;
HWND hwndParent;
} SDTP_PARAMS;
// Hydra-specific
void Disconnect(void)
{
TW32(ShellSwitchUser(FALSE));
}
DWORD CALLBACK ShutdownThreadProc(void *pv)
{
SDTP_PARAMS *psdtp = (SDTP_PARAMS *)pv;
BOOL fShutdownWorked = FALSE;
// tell USER that anybody can steal foreground from us
// This allows apps to put up UI during shutdown/suspend/etc.
// AllowSetForegroundWindow(ASFW_ANY);
switch (psdtp->nCmd)
{
case SHTDN_SHUTDOWN:
fShutdownWorked = CommonRestart(EWX_SHUTDOWN, 0);
break;
case SHTDN_RESTART:
fShutdownWorked = CommonRestart(CanDoFastRestart() ? EW_RESTARTWINDOWS : EWX_REBOOT, 0);
break;
case SHTDN_LOGOFF:
fShutdownWorked = CommonRestart(EWX_LOGOFF, 0);
break;
case SHTDN_RESTART_DOS: // Special hack to mean exit to dos
case SHTDN_SLEEP:
case SHTDN_SLEEP2:
case SHTDN_HIBERNATE:
SetSuspendState((psdtp->nCmd == SHTDN_HIBERNATE) ? TRUE : FALSE,
(GetKeyState(VK_CONTROL) < 0) ? TRUE : FALSE,
(psdtp->nCmd == SHTDN_SLEEP2) ? TRUE : FALSE);
break;
}
LocalFree(psdtp);
return fShutdownWorked;
}
#define DIALOG_LOGOFF 1
#define DIALOG_EXIT 2
#define DIALOG_DISCONNECT 3
void CloseWindowsDialog(HWND hwndParent, int iDialogType)
{
INT_PTR nCmd = SHTDN_NONE;
IUnknown* pIUnknown;
HWND hwndBackground;
if (FAILED(ShellDimScreen(&pIUnknown, &hwndBackground)))
{
pIUnknown = NULL;
hwndBackground = NULL;
}
switch (iDialogType)
{
LPCTSTR pszDialogID;
DLGPROC pfnDialogProc;
case DIALOG_LOGOFF:
case DIALOG_DISCONNECT:
{
if (!GetSystemMetrics(SM_REMOTESESSION) && IsOS(OS_FRIENDLYLOGONUI) && IsOS(OS_FASTUSERSWITCHING))
{
// If not remote with friendly UI and FUS show the licky button dialog.
nCmd = SwitchUserDialog_Show(hwndBackground);
pszDialogID = 0;
pfnDialogProc = NULL;
}
else if (iDialogType == DIALOG_LOGOFF)
{
// Otherwise show the Win32 log off dialog if log off.
pszDialogID = MAKEINTRESOURCE(DLG_LOGOFFWINDOWS);
pfnDialogProc = LogoffDlgProc;
}
else if (iDialogType == DIALOG_DISCONNECT)
{
// Or the Win32 disconnect dialog if disconnect.
pszDialogID = MAKEINTRESOURCE(DLG_DISCONNECTWINDOWS);
pfnDialogProc = DisconnectDlgProc;
}
else
{
ASSERTMSG(FALSE, "Unexpected case hit in CloseWindowsDialog");
}
if ((pszDialogID != 0) && (pfnDialogProc != NULL))
{
nCmd = DialogBoxParam(HINST_THISDLL, pszDialogID, hwndBackground, pfnDialogProc, 0);
}
if (nCmd == SHTDN_DISCONNECT)
{
Disconnect();
nCmd = SHTDN_NONE;
}
break;
}
case DIALOG_EXIT:
{
BOOL fGinaShutdownCalled = FALSE;
HINSTANCE hGina;
TCHAR szUsername[UNLEN];
DWORD cchUsernameLength = UNLEN;
DWORD dwOptions;
if (WNetGetUser(NULL, szUsername, &cchUsernameLength) != NO_ERROR)
{
szUsername[0] = TEXT('\0');
}
EarlySaveSomeShellState();
// Load MSGINA.DLL and get appropriate shutdown function
hGina = LoadLibrary(TEXT("msgina.dll"));
if (hGina != NULL)
{
if (IsOS(OS_FRIENDLYLOGONUI))
{
nCmd = ShellTurnOffDialog(hwndBackground);
fGinaShutdownCalled = TRUE;
}
else
{
PFNSHELLSHUTDOWNDIALOG pfnShellShutdownDialog = (PFNSHELLSHUTDOWNDIALOG)
GetProcAddress(hGina, "ShellShutdownDialog");
if (pfnShellShutdownDialog != NULL)
{
nCmd = pfnShellShutdownDialog(hwndBackground,
szUsername, 0);
// Handle disconnect right now
if (nCmd == SHTDN_DISCONNECT)
{
Disconnect();
// No other action
nCmd = SHTDN_NONE;
}
fGinaShutdownCalled = TRUE;
}
}
FreeLibrary(hGina);
}
if (!fGinaShutdownCalled)
{
dwOptions = GetShutdownOptions();
// Gina call failed; use our cheesy private version
nCmd = DownlevelShellShutdownDialog(hwndBackground,
dwOptions, szUsername);
}
break;
}
}
if (hwndBackground)
SetForegroundWindow(hwndBackground);
if (nCmd == SHTDN_NONE)
{
if (hwndBackground)
{
ShowWindow(hwndBackground, SW_HIDE);
PostMessage(hwndBackground, WM_CLOSE, 0, 0);
}
}
else
{
SDTP_PARAMS *psdtp = LocalAlloc(LPTR, sizeof(*psdtp));
if (psdtp)
{
DWORD dw;
HANDLE h;
psdtp->nCmd = nCmd;
psdtp->hwndParent = hwndParent;
// have another thread call ExitWindows() so our
// main pump keeps running durring shutdown.
//
h = CreateThread(NULL, 0, ShutdownThreadProc, psdtp, 0, &dw);
if (h)
{
CloseHandle(h);
}
else
{
if (hwndBackground)
ShowWindow(hwndBackground, SW_HIDE);
ShutdownThreadProc(psdtp);
}
}
}
if (pIUnknown != NULL)
{
pIUnknown->lpVtbl->Release(pIUnknown);
}
}
// API functions
STDAPI_(void) ExitWindowsDialog(HWND hwndParent)
{
if (!IsOS(OS_FRIENDLYLOGONUI) || IsShutdownAllowed())
{
CloseWindowsDialog (hwndParent, DIALOG_EXIT);
}
else
{
LogoffWindowsDialog(hwndParent);
}
}
STDAPI_(void) LogoffWindowsDialog(HWND hwndParent)
{
CloseWindowsDialog (hwndParent, DIALOG_LOGOFF);
}
STDAPI_(void) DisconnectWindowsDialog(HWND hwndParent)
{
CloseWindowsDialog(hwndParent, DIALOG_DISCONNECT);
}