548 lines
18 KiB
C++
548 lines
18 KiB
C++
//
|
|
// machinfo.cpp - SHGetMachineInfo and related functions
|
|
//
|
|
//
|
|
|
|
#include "priv.h"
|
|
#include <dbt.h>
|
|
#include <cfgmgr32.h>
|
|
#include <apithk.h>
|
|
|
|
#ifndef UNIX
|
|
#include <batclass.h>
|
|
|
|
const GUID GUID_DEVICE_BATTERY = { 0x72631e54L, 0x78A4, 0x11d0,
|
|
{ 0xbc, 0xf7, 0x00, 0xaa, 0x00, 0xb7, 0xb3, 0x2a } };
|
|
|
|
#include <winsta.h>
|
|
#endif
|
|
|
|
//
|
|
// Win95 does not decorate BroadcastSystemMessage, so we can't either.
|
|
//
|
|
#undef BroadcastSystemMessage
|
|
|
|
extern "C" {
|
|
WINUSERAPI long WINAPI
|
|
BroadcastSystemMessage(DWORD, LPDWORD, UINT, WPARAM, LPARAM);
|
|
};
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* DOCK STATE - Win95, Win98, and WinNT all do this differently (yuck)
|
|
*
|
|
*****************************************************************************/
|
|
|
|
C_ASSERT(GMID_NOTDOCKABLE == CM_HWPI_NOT_DOCKABLE);
|
|
C_ASSERT(GMID_UNDOCKED == CM_HWPI_UNDOCKED);
|
|
C_ASSERT(GMID_DOCKED == CM_HWPI_DOCKED);
|
|
|
|
#if defined(_X86_) && !defined(UNIX)
|
|
|
|
typedef struct CMHDR {
|
|
LPVOID pArgs;
|
|
DWORD dwService;
|
|
DWORD dwRet;
|
|
} CMHDR, *PCMHDR;
|
|
|
|
#define CONFIGMG_Get_Hardware_Profile_Info 0x00330052
|
|
|
|
//
|
|
// GetDockedState95
|
|
//
|
|
DWORD GetDockedState95()
|
|
{
|
|
struct GHWPI95 { // Get_Hardware_Profile_Info parameter blk
|
|
CMHDR cmhdr;
|
|
ULONG ulIndex;
|
|
PHWPROFILEINFO_A pHWProfileInfo;
|
|
ULONG ulFlags;
|
|
HWPROFILEINFO_A HWProfileInfo;
|
|
} *pghwpi;
|
|
|
|
HANDLE hheap;
|
|
DWORD Result = GMID_NOTDOCKABLE; // assume the worst
|
|
|
|
#define HEAP_SHARED 0x04000000 /* put heap in shared memory--undoc'd */
|
|
|
|
//
|
|
// Win95 Configmg requires the parameter block to reside in the shared
|
|
// heap since we're going to do a cross-process SendMessage.
|
|
//
|
|
|
|
hheap = HeapCreate(HEAP_SHARED, 1, 4096);
|
|
if (hheap) {
|
|
// Allocate parameter block in shared memory
|
|
pghwpi = (struct GHWPI95 *)HeapAlloc(hheap, HEAP_ZERO_MEMORY,
|
|
sizeof(*pghwpi));
|
|
if (pghwpi) {
|
|
DWORD dwRecipients = BSM_VXDS;
|
|
|
|
pghwpi->cmhdr.dwRet = 0;
|
|
pghwpi->cmhdr.dwService = CONFIGMG_Get_Hardware_Profile_Info;
|
|
pghwpi->cmhdr.pArgs = &pghwpi->ulIndex;
|
|
pghwpi->ulIndex = 0xFFFFFFFF;
|
|
pghwpi->pHWProfileInfo = &pghwpi->HWProfileInfo;
|
|
pghwpi->ulFlags = 0;
|
|
|
|
// "Call" the service
|
|
|
|
BroadcastSystemMessage(0, &dwRecipients, WM_DEVICECHANGE,
|
|
DBT_CONFIGMGAPI32, (LPARAM)pghwpi);
|
|
|
|
if (pghwpi->cmhdr.dwRet == CR_SUCCESS) {
|
|
|
|
Result = pghwpi->HWProfileInfo.HWPI_dwFlags;
|
|
} else {
|
|
TraceMsg(DM_WARNING, "GetDockedState95: CONFIGMG did not respond");
|
|
}
|
|
}
|
|
|
|
HeapDestroy(hheap);
|
|
} else {
|
|
TraceMsg(DM_WARNING, "GetDockedState95: Unable to create shared heap");
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
|
|
//
|
|
// On Win98, use the 32-bit interface to configmg.
|
|
//
|
|
|
|
CONFIGRET __cdecl
|
|
CallConfigmg98(DWORD dwServiceNumber, ...)
|
|
{
|
|
CONFIGRET cr;
|
|
HANDLE hCM;
|
|
|
|
hCM = CreateFileA("\\\\.\\CONFIGMG",
|
|
GENERIC_READ|GENERIC_WRITE,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
|
NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hCM != INVALID_HANDLE_VALUE) {
|
|
DWORD dwRet;
|
|
|
|
// Evil hack that works only on x86. Fortunately, this code is
|
|
// inside an #ifdef _X86_ block, so we're safe.
|
|
LPVOID pvArg = 1 + &dwServiceNumber;
|
|
|
|
if (DeviceIoControl(hCM, dwServiceNumber, &pvArg, sizeof(pvArg),
|
|
&cr, sizeof(cr), &dwRet, 0) &&
|
|
dwRet == sizeof(cr)) {
|
|
} else {
|
|
TraceMsg(DM_WARNING, "CallConfigmg98: CONFIGMG did not respond");
|
|
cr = CR_FAILURE;
|
|
}
|
|
|
|
CloseHandle(hCM);
|
|
} else {
|
|
TraceMsg(DM_WARNING, "CallConfigmg98: Couldn't connect to CONFIGMG");
|
|
cr = CR_FAILURE;
|
|
}
|
|
|
|
return cr;
|
|
}
|
|
|
|
DWORD GetDockedState98()
|
|
{
|
|
CONFIGRET cr;
|
|
DWORD Result = GMID_NOTDOCKABLE; // assume the worst
|
|
HWPROFILEINFO_A HWProfileInfo;
|
|
|
|
cr = CallConfigmg98(
|
|
0x80000000 + LOWORD(CONFIGMG_Get_Hardware_Profile_Info),
|
|
-1, // ulIndex, -1 means "current profile"
|
|
&HWProfileInfo, // PHWPROFILEINFO
|
|
0); // ulFlags
|
|
|
|
if (cr == CR_SUCCESS) {
|
|
Result = HWProfileInfo.HWPI_dwFlags;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
#endif
|
|
|
|
typedef BOOL (WINAPI *GETCURRENTHWPROFILEA)(LPHW_PROFILE_INFOA);
|
|
DWORD GetDockedStateNT()
|
|
{
|
|
HW_PROFILE_INFOA hpi;
|
|
GETCURRENTHWPROFILEA GetCurrentHwProfileA;
|
|
DWORD Result = GMID_NOTDOCKABLE; // assume the worst
|
|
|
|
GetCurrentHwProfileA = (GETCURRENTHWPROFILEA)
|
|
GetProcAddress(GetModuleHandle("ADVAPI32"),
|
|
"GetCurrentHwProfileA");
|
|
|
|
if (GetCurrentHwProfileA && GetCurrentHwProfileA(&hpi)) {
|
|
Result = hpi.dwDockInfo & (DOCKINFO_UNDOCKED | DOCKINFO_DOCKED);
|
|
|
|
// Wackiness: If the machine does not support docking, then
|
|
// NT returns >both< flags set. Go figure.
|
|
if (Result == (DOCKINFO_UNDOCKED | DOCKINFO_DOCKED)) {
|
|
Result = GMID_NOTDOCKABLE;
|
|
}
|
|
} else {
|
|
TraceMsg(DM_WARNING, "GetDockedStateNT: GetCurrentHwProfile failed");
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
#if defined(_X86_) && !defined(UNIX)
|
|
|
|
//
|
|
// Platforms that support Win95/Win98 need to do version switching
|
|
//
|
|
DWORD GetDockedState()
|
|
{
|
|
if (g_bRunningOnNT) {
|
|
return GetDockedStateNT();
|
|
} else if (g_bRunningOnMemphis) {
|
|
return GetDockedState98();
|
|
} else {
|
|
return GetDockedState95();
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
//
|
|
// Platforms that do not support Win95/Win98 can just call the NT version.
|
|
//
|
|
#define GetDockedState() GetDockedStateNT()
|
|
|
|
#endif
|
|
|
|
|
|
#ifndef UNIX
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* BATTERY STATE - Once again, Win95 and Win98 and NT all do it differently
|
|
*
|
|
*****************************************************************************/
|
|
|
|
//
|
|
// Values for SYSTEM_POWER_STATUS.ACLineStatus
|
|
//
|
|
#define SPSAC_OFFLINE 0
|
|
#define SPSAC_ONLINE 1
|
|
|
|
//
|
|
// Values for SYSTEM_POWER_STATUS.BatteryFlag
|
|
//
|
|
#define SPSBF_NOBATTERY 128
|
|
|
|
//
|
|
// So many ways to detect batteries, so little time...
|
|
//
|
|
DWORD GetBatteryState()
|
|
{
|
|
//
|
|
// Since GMIB_HASBATTERY is cumulative (any battery turns it on)
|
|
// and GMIB_ONBATTERY is subtractive (any AC turns it off), the
|
|
// state you have to start in before you find a battery is
|
|
// GMIB_HASBATTERY off and GMIB_ONBATTERY on.
|
|
//
|
|
// dwResult & GMIB_ONBATTERY means we have yet to find AC power.
|
|
// dwResult & GMIB_HASBATTERY means we have found a non-UPS battery.
|
|
//
|
|
DWORD dwResult = GMIB_ONBATTERY;
|
|
|
|
//------------------------------------------------------------------
|
|
//
|
|
// First try - IOCTL_BATTERY_QUERY_INFORMATION
|
|
//
|
|
//------------------------------------------------------------------
|
|
//
|
|
// Windows 98 and Windows 2000 support IOCTL_BATTERY_QUERY_INFORMATION,
|
|
// which lets us enumerate the batteries and ask each one for information.
|
|
// Except that on Windows 98, we can enumerate only ACPI batteries.
|
|
// We still have to use VPOWERD to enumerate APM batteries.
|
|
// FEATURE -- deal with Win98 APM batteries
|
|
|
|
HDEVINFO hdev = SetupDiGetClassDevs(&GUID_DEVICE_BATTERY, 0, 0,
|
|
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
if (hdev != INVALID_HANDLE_VALUE) {
|
|
SP_DEVICE_INTERFACE_DATA did;
|
|
did.cbSize = sizeof(did);
|
|
// Stop at 100 batteries so we don't go haywire
|
|
for (int idev = 0; idev < 100; idev++) {
|
|
// Pre-set the error code because our DLLLOAD wrapper doesn't
|
|
// and Windows NT 4 supports SetupDiGetClassDevs but not
|
|
// SetupDiEnumDeviceInterfaces (go figure).
|
|
SetLastError(ERROR_NO_MORE_ITEMS);
|
|
if (SetupDiEnumDeviceInterfaces(hdev, 0, &GUID_DEVICE_BATTERY, idev, &did)) {
|
|
DWORD cbRequired = 0;
|
|
|
|
/*
|
|
* Ask for the required size then allocate it then fill it.
|
|
*
|
|
* Sigh. Windows NT and Windows 98 implement
|
|
* SetupDiGetDeviceInterfaceDetail differently if you are
|
|
* querying for the buffer size.
|
|
*
|
|
* Windows 98 returns FALSE, and GetLastError() returns
|
|
* ERROR_INSUFFICIENT_BUFFER.
|
|
*
|
|
* Windows NT returns TRUE.
|
|
*
|
|
* So we allow the cases either where the call succeeds or
|
|
* the call fails with ERROR_INSUFFICIENT_BUFFER.
|
|
*/
|
|
|
|
if (SetupDiGetDeviceInterfaceDetail(hdev, &did, 0, 0, &cbRequired, 0) ||
|
|
GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd;
|
|
pdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, cbRequired);
|
|
if (pdidd) {
|
|
pdidd->cbSize = sizeof(*pdidd);
|
|
if (SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, cbRequired, &cbRequired, 0)) {
|
|
/*
|
|
* Finally enumerated a battery. Ask it for information.
|
|
*/
|
|
HANDLE hBattery = CreateFile(pdidd->DevicePath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL, OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hBattery != INVALID_HANDLE_VALUE) {
|
|
/*
|
|
* Now you have to ask the battery for its tag.
|
|
*/
|
|
BATTERY_QUERY_INFORMATION bqi;
|
|
|
|
DWORD dwWait = 0;
|
|
DWORD dwOut;
|
|
bqi.BatteryTag = 0;
|
|
|
|
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG,
|
|
&dwWait, sizeof(dwWait),
|
|
&bqi.BatteryTag, sizeof(bqi.BatteryTag),
|
|
&dwOut, NULL) && bqi.BatteryTag) {
|
|
/*
|
|
* With the tag, you can query the battery info.
|
|
*/
|
|
BATTERY_INFORMATION bi;
|
|
bqi.InformationLevel = BatteryInformation;
|
|
bqi.AtRate = 0;
|
|
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION,
|
|
&bqi, sizeof(bqi),
|
|
&bi, sizeof(bi),
|
|
&dwOut, NULL)) {
|
|
// Only system batteries count
|
|
if (bi.Capabilities & BATTERY_SYSTEM_BATTERY) {
|
|
if (!(bi.Capabilities & BATTERY_IS_SHORT_TERM)) {
|
|
dwResult |= GMIB_HASBATTERY;
|
|
}
|
|
|
|
/*
|
|
* And then query the battery status.
|
|
*/
|
|
BATTERY_WAIT_STATUS bws;
|
|
BATTERY_STATUS bs;
|
|
ZeroMemory(&bws, sizeof(bws));
|
|
bws.BatteryTag = bqi.BatteryTag;
|
|
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS,
|
|
&bws, sizeof(bws),
|
|
&bs, sizeof(bs),
|
|
&dwOut, NULL)) {
|
|
if (bs.PowerState & BATTERY_POWER_ON_LINE) {
|
|
dwResult &= ~GMIB_ONBATTERY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CloseHandle(hBattery);
|
|
}
|
|
}
|
|
LocalFree(pdidd);
|
|
}
|
|
}
|
|
} else {
|
|
// Enumeration failed - perhaps we're out of items
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
}
|
|
}
|
|
SetupDiDestroyDeviceInfoList(hdev);
|
|
|
|
}
|
|
|
|
//
|
|
// On Windows NT, SetupDi tells us everything there is to know.
|
|
// So once you get this far, you're finished.
|
|
//
|
|
if (g_bRunningOnNT) {
|
|
goto finish;
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
//
|
|
// Second try - GetSystemPowerStatus
|
|
//
|
|
//------------------------------------------------------------------
|
|
//
|
|
// On Windows 9x, GetSystemPowerStatus enumerates a disjoint set of
|
|
// batteries from SetupDi, so it's worth calling to find out.
|
|
//
|
|
|
|
SYSTEM_POWER_STATUS status;
|
|
|
|
if (GetSystemPowerStatus(&status)) {
|
|
//
|
|
// HACKHACK: Some APM BIOS implementations set BatteryFlag = 0
|
|
// instead of 128 or 255 when they don't have a
|
|
// battery, so we have to check both.
|
|
//
|
|
if (status.BatteryFlag != 0 &&
|
|
!(status.BatteryFlag & SPSBF_NOBATTERY)) {
|
|
//
|
|
// Found an APM battery.
|
|
//
|
|
dwResult |= GMIB_HASBATTERY;
|
|
}
|
|
|
|
if (status.ACLineStatus == SPSAC_ONLINE) {
|
|
dwResult &= ~GMIB_ONBATTERY;
|
|
}
|
|
}
|
|
|
|
#ifdef TRY_NtPowerInformation // Hopefully the Third Try won't be necessary
|
|
SYSTEM_POWER_CAPABILITIES caps;
|
|
|
|
//------------------------------------------------------------------
|
|
//
|
|
// Third try - NtPowerInformation
|
|
//
|
|
//------------------------------------------------------------------
|
|
//
|
|
// NtPowerInformation is supported on Win98 and Windows 2000, but not
|
|
// Windows 95 or NT4.
|
|
//
|
|
if (SUCCEEDED(NtPowerInformation(SystemPowerCapabilities, NULL, 0,
|
|
&caps, sizeof(caps)))) {
|
|
|
|
if (caps.BatteriesAreShortTerm) {
|
|
#error futz futz
|
|
}
|
|
|
|
if (caps.SystemBatteriesPresent && !fFoundUPS) {
|
|
#error futz futz
|
|
}
|
|
|
|
}
|
|
|
|
#endif // TRY_NtPowerInformation
|
|
|
|
finish:
|
|
//
|
|
// Final cleanup: If we didn't find a battery, then presume that we
|
|
// are on AC power.
|
|
//
|
|
if (!(dwResult & GMIB_HASBATTERY))
|
|
dwResult &= ~GMIB_ONBATTERY;
|
|
|
|
return dwResult;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* TERMINAL SERVER CLIENT
|
|
*
|
|
* This is particularly gruesome because Terminal Server for NT4 SP3 goes
|
|
* to extraordinary lengths to prevent you from detecting it. Even the
|
|
* semi-documented NtCurrentPeb()->SessionId trick doesn't work on NT4 SP3.
|
|
* So we have to go to the totally undocumented winsta.dll to find out.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL g_fTSClient = -1; // Tri-state, 0 = no, 1 = yes, -1 = don't know
|
|
|
|
BOOL IsTSClientNT4(void)
|
|
{
|
|
BOOL fTS = FALSE; // Assume not
|
|
|
|
HINSTANCE hinstWinSta = LoadLibrary("winsta.dll");
|
|
if (hinstWinSta) {
|
|
PWINSTATIONQUERYINFORMATIONW WinStationQueryInformationW;
|
|
WINSTATIONINFORMATIONW wi;
|
|
WinStationQueryInformationW = (PWINSTATIONQUERYINFORMATIONW)
|
|
GetProcAddress(hinstWinSta, "WinStationQueryInformationW");
|
|
if (WinStationQueryInformationW &&
|
|
WinStationQueryInformationW(SERVERNAME_CURRENT, LOGONID_CURRENT, WinStationInformation, &wi, sizeof(wi), NULL) &&
|
|
wi.LogonId != 0) {
|
|
fTS = TRUE;
|
|
}
|
|
FreeLibrary(hinstWinSta);
|
|
}
|
|
|
|
return fTS;
|
|
}
|
|
|
|
BOOL IsTSClient(void)
|
|
{
|
|
if (!g_bRunningOnNT) {
|
|
// Windows 9x doesn't support Terminal Server
|
|
return FALSE;
|
|
} else if (g_bRunningOnNT5OrHigher) {
|
|
// NT5 has a new system metric to detect this
|
|
return GetSystemMetrics(SM_REMOTESESSION);
|
|
} else {
|
|
// NT4 is gross and evil. This is slow, so cache the result.
|
|
if (g_fTSClient < 0)
|
|
g_fTSClient = IsTSClientNT4();
|
|
return g_fTSClient;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* SHGetMachineInfo
|
|
*
|
|
*****************************************************************************/
|
|
|
|
//
|
|
// SHGetMachineInfo
|
|
//
|
|
// Given an index, returns some info about that index. See shlwapi.w
|
|
// for documentation on the flags available.
|
|
//
|
|
STDAPI_(DWORD_PTR) SHGetMachineInfo(UINT gmi)
|
|
{
|
|
switch (gmi) {
|
|
case GMI_DOCKSTATE:
|
|
return GetDockedState();
|
|
|
|
case GMI_BATTERYSTATE:
|
|
return GetBatteryState();
|
|
|
|
//
|
|
// It smell like a laptop if it has a battery or if it can be docked.
|
|
//
|
|
case GMI_LAPTOP:
|
|
return (GetBatteryState() & GMIB_HASBATTERY) ||
|
|
(GetDockedState() != GMID_NOTDOCKABLE);
|
|
|
|
case GMI_TSCLIENT:
|
|
return IsTSClient();
|
|
}
|
|
|
|
TraceMsg(DM_WARNING, "SHGetMachineInfo: Unknown info query %d", gmi);
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
STDAPI_(DWORD_PTR) SHGetMachineInfo(UINT gmi)
|
|
{
|
|
// IEUNIX : Stubbed out this api to resolve undefind symbol in linking.
|
|
TraceMsg(DM_WARNING, "SHGetMachineInfo: Unknown info query %d", gmi);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|