Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

547 lines
18 KiB

  1. //
  2. // machinfo.cpp - SHGetMachineInfo and related functions
  3. //
  4. //
  5. #include "priv.h"
  6. #include <dbt.h>
  7. #include <cfgmgr32.h>
  8. #include <apithk.h>
  9. #ifndef UNIX
  10. #include <batclass.h>
  11. const GUID GUID_DEVICE_BATTERY = { 0x72631e54L, 0x78A4, 0x11d0,
  12. { 0xbc, 0xf7, 0x00, 0xaa, 0x00, 0xb7, 0xb3, 0x2a } };
  13. #include <winsta.h>
  14. #endif
  15. //
  16. // Win95 does not decorate BroadcastSystemMessage, so we can't either.
  17. //
  18. #undef BroadcastSystemMessage
  19. extern "C" {
  20. WINUSERAPI long WINAPI
  21. BroadcastSystemMessage(DWORD, LPDWORD, UINT, WPARAM, LPARAM);
  22. };
  23. /*****************************************************************************
  24. *
  25. * DOCK STATE - Win95, Win98, and WinNT all do this differently (yuck)
  26. *
  27. *****************************************************************************/
  28. C_ASSERT(GMID_NOTDOCKABLE == CM_HWPI_NOT_DOCKABLE);
  29. C_ASSERT(GMID_UNDOCKED == CM_HWPI_UNDOCKED);
  30. C_ASSERT(GMID_DOCKED == CM_HWPI_DOCKED);
  31. #if defined(_X86_) && !defined(UNIX)
  32. typedef struct CMHDR {
  33. LPVOID pArgs;
  34. DWORD dwService;
  35. DWORD dwRet;
  36. } CMHDR, *PCMHDR;
  37. #define CONFIGMG_Get_Hardware_Profile_Info 0x00330052
  38. //
  39. // GetDockedState95
  40. //
  41. DWORD GetDockedState95()
  42. {
  43. struct GHWPI95 { // Get_Hardware_Profile_Info parameter blk
  44. CMHDR cmhdr;
  45. ULONG ulIndex;
  46. PHWPROFILEINFO_A pHWProfileInfo;
  47. ULONG ulFlags;
  48. HWPROFILEINFO_A HWProfileInfo;
  49. } *pghwpi;
  50. HANDLE hheap;
  51. DWORD Result = GMID_NOTDOCKABLE; // assume the worst
  52. #define HEAP_SHARED 0x04000000 /* put heap in shared memory--undoc'd */
  53. //
  54. // Win95 Configmg requires the parameter block to reside in the shared
  55. // heap since we're going to do a cross-process SendMessage.
  56. //
  57. hheap = HeapCreate(HEAP_SHARED, 1, 4096);
  58. if (hheap) {
  59. // Allocate parameter block in shared memory
  60. pghwpi = (struct GHWPI95 *)HeapAlloc(hheap, HEAP_ZERO_MEMORY,
  61. sizeof(*pghwpi));
  62. if (pghwpi) {
  63. DWORD dwRecipients = BSM_VXDS;
  64. pghwpi->cmhdr.dwRet = 0;
  65. pghwpi->cmhdr.dwService = CONFIGMG_Get_Hardware_Profile_Info;
  66. pghwpi->cmhdr.pArgs = &pghwpi->ulIndex;
  67. pghwpi->ulIndex = 0xFFFFFFFF;
  68. pghwpi->pHWProfileInfo = &pghwpi->HWProfileInfo;
  69. pghwpi->ulFlags = 0;
  70. // "Call" the service
  71. BroadcastSystemMessage(0, &dwRecipients, WM_DEVICECHANGE,
  72. DBT_CONFIGMGAPI32, (LPARAM)pghwpi);
  73. if (pghwpi->cmhdr.dwRet == CR_SUCCESS) {
  74. Result = pghwpi->HWProfileInfo.HWPI_dwFlags;
  75. } else {
  76. TraceMsg(DM_WARNING, "GetDockedState95: CONFIGMG did not respond");
  77. }
  78. }
  79. HeapDestroy(hheap);
  80. } else {
  81. TraceMsg(DM_WARNING, "GetDockedState95: Unable to create shared heap");
  82. }
  83. return Result;
  84. }
  85. //
  86. // On Win98, use the 32-bit interface to configmg.
  87. //
  88. CONFIGRET __cdecl
  89. CallConfigmg98(DWORD dwServiceNumber, ...)
  90. {
  91. CONFIGRET cr;
  92. HANDLE hCM;
  93. hCM = CreateFileA("\\\\.\\CONFIGMG",
  94. GENERIC_READ|GENERIC_WRITE,
  95. FILE_SHARE_READ|FILE_SHARE_WRITE,
  96. NULL, OPEN_EXISTING, 0, NULL);
  97. if (hCM != INVALID_HANDLE_VALUE) {
  98. DWORD dwRet;
  99. // Evil hack that works only on x86. Fortunately, this code is
  100. // inside an #ifdef _X86_ block, so we're safe.
  101. LPVOID pvArg = 1 + &dwServiceNumber;
  102. if (DeviceIoControl(hCM, dwServiceNumber, &pvArg, sizeof(pvArg),
  103. &cr, sizeof(cr), &dwRet, 0) &&
  104. dwRet == sizeof(cr)) {
  105. } else {
  106. TraceMsg(DM_WARNING, "CallConfigmg98: CONFIGMG did not respond");
  107. cr = CR_FAILURE;
  108. }
  109. CloseHandle(hCM);
  110. } else {
  111. TraceMsg(DM_WARNING, "CallConfigmg98: Couldn't connect to CONFIGMG");
  112. cr = CR_FAILURE;
  113. }
  114. return cr;
  115. }
  116. DWORD GetDockedState98()
  117. {
  118. CONFIGRET cr;
  119. DWORD Result = GMID_NOTDOCKABLE; // assume the worst
  120. HWPROFILEINFO_A HWProfileInfo;
  121. cr = CallConfigmg98(
  122. 0x80000000 + LOWORD(CONFIGMG_Get_Hardware_Profile_Info),
  123. -1, // ulIndex, -1 means "current profile"
  124. &HWProfileInfo, // PHWPROFILEINFO
  125. 0); // ulFlags
  126. if (cr == CR_SUCCESS) {
  127. Result = HWProfileInfo.HWPI_dwFlags;
  128. }
  129. return Result;
  130. }
  131. #endif
  132. typedef BOOL (WINAPI *GETCURRENTHWPROFILEA)(LPHW_PROFILE_INFOA);
  133. DWORD GetDockedStateNT()
  134. {
  135. HW_PROFILE_INFOA hpi;
  136. GETCURRENTHWPROFILEA GetCurrentHwProfileA;
  137. DWORD Result = GMID_NOTDOCKABLE; // assume the worst
  138. GetCurrentHwProfileA = (GETCURRENTHWPROFILEA)
  139. GetProcAddress(GetModuleHandle("ADVAPI32"),
  140. "GetCurrentHwProfileA");
  141. if (GetCurrentHwProfileA && GetCurrentHwProfileA(&hpi)) {
  142. Result = hpi.dwDockInfo & (DOCKINFO_UNDOCKED | DOCKINFO_DOCKED);
  143. // Wackiness: If the machine does not support docking, then
  144. // NT returns >both< flags set. Go figure.
  145. if (Result == (DOCKINFO_UNDOCKED | DOCKINFO_DOCKED)) {
  146. Result = GMID_NOTDOCKABLE;
  147. }
  148. } else {
  149. TraceMsg(DM_WARNING, "GetDockedStateNT: GetCurrentHwProfile failed");
  150. }
  151. return Result;
  152. }
  153. #if defined(_X86_) && !defined(UNIX)
  154. //
  155. // Platforms that support Win95/Win98 need to do version switching
  156. //
  157. DWORD GetDockedState()
  158. {
  159. if (g_bRunningOnNT) {
  160. return GetDockedStateNT();
  161. } else if (g_bRunningOnMemphis) {
  162. return GetDockedState98();
  163. } else {
  164. return GetDockedState95();
  165. }
  166. }
  167. #else
  168. //
  169. // Platforms that do not support Win95/Win98 can just call the NT version.
  170. //
  171. #define GetDockedState() GetDockedStateNT()
  172. #endif
  173. #ifndef UNIX
  174. /*****************************************************************************
  175. *
  176. * BATTERY STATE - Once again, Win95 and Win98 and NT all do it differently
  177. *
  178. *****************************************************************************/
  179. //
  180. // Values for SYSTEM_POWER_STATUS.ACLineStatus
  181. //
  182. #define SPSAC_OFFLINE 0
  183. #define SPSAC_ONLINE 1
  184. //
  185. // Values for SYSTEM_POWER_STATUS.BatteryFlag
  186. //
  187. #define SPSBF_NOBATTERY 128
  188. //
  189. // So many ways to detect batteries, so little time...
  190. //
  191. DWORD GetBatteryState()
  192. {
  193. //
  194. // Since GMIB_HASBATTERY is cumulative (any battery turns it on)
  195. // and GMIB_ONBATTERY is subtractive (any AC turns it off), the
  196. // state you have to start in before you find a battery is
  197. // GMIB_HASBATTERY off and GMIB_ONBATTERY on.
  198. //
  199. // dwResult & GMIB_ONBATTERY means we have yet to find AC power.
  200. // dwResult & GMIB_HASBATTERY means we have found a non-UPS battery.
  201. //
  202. DWORD dwResult = GMIB_ONBATTERY;
  203. //------------------------------------------------------------------
  204. //
  205. // First try - IOCTL_BATTERY_QUERY_INFORMATION
  206. //
  207. //------------------------------------------------------------------
  208. //
  209. // Windows 98 and Windows 2000 support IOCTL_BATTERY_QUERY_INFORMATION,
  210. // which lets us enumerate the batteries and ask each one for information.
  211. // Except that on Windows 98, we can enumerate only ACPI batteries.
  212. // We still have to use VPOWERD to enumerate APM batteries.
  213. // FEATURE -- deal with Win98 APM batteries
  214. HDEVINFO hdev = SetupDiGetClassDevs(&GUID_DEVICE_BATTERY, 0, 0,
  215. DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
  216. if (hdev != INVALID_HANDLE_VALUE) {
  217. SP_DEVICE_INTERFACE_DATA did;
  218. did.cbSize = sizeof(did);
  219. // Stop at 100 batteries so we don't go haywire
  220. for (int idev = 0; idev < 100; idev++) {
  221. // Pre-set the error code because our DLLLOAD wrapper doesn't
  222. // and Windows NT 4 supports SetupDiGetClassDevs but not
  223. // SetupDiEnumDeviceInterfaces (go figure).
  224. SetLastError(ERROR_NO_MORE_ITEMS);
  225. if (SetupDiEnumDeviceInterfaces(hdev, 0, &GUID_DEVICE_BATTERY, idev, &did)) {
  226. DWORD cbRequired = 0;
  227. /*
  228. * Ask for the required size then allocate it then fill it.
  229. *
  230. * Sigh. Windows NT and Windows 98 implement
  231. * SetupDiGetDeviceInterfaceDetail differently if you are
  232. * querying for the buffer size.
  233. *
  234. * Windows 98 returns FALSE, and GetLastError() returns
  235. * ERROR_INSUFFICIENT_BUFFER.
  236. *
  237. * Windows NT returns TRUE.
  238. *
  239. * So we allow the cases either where the call succeeds or
  240. * the call fails with ERROR_INSUFFICIENT_BUFFER.
  241. */
  242. if (SetupDiGetDeviceInterfaceDetail(hdev, &did, 0, 0, &cbRequired, 0) ||
  243. GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
  244. PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd;
  245. pdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, cbRequired);
  246. if (pdidd) {
  247. pdidd->cbSize = sizeof(*pdidd);
  248. if (SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, cbRequired, &cbRequired, 0)) {
  249. /*
  250. * Finally enumerated a battery. Ask it for information.
  251. */
  252. HANDLE hBattery = CreateFile(pdidd->DevicePath,
  253. GENERIC_READ | GENERIC_WRITE,
  254. FILE_SHARE_READ | FILE_SHARE_WRITE,
  255. NULL, OPEN_EXISTING,
  256. FILE_ATTRIBUTE_NORMAL, NULL);
  257. if (hBattery != INVALID_HANDLE_VALUE) {
  258. /*
  259. * Now you have to ask the battery for its tag.
  260. */
  261. BATTERY_QUERY_INFORMATION bqi;
  262. DWORD dwWait = 0;
  263. DWORD dwOut;
  264. bqi.BatteryTag = 0;
  265. if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG,
  266. &dwWait, sizeof(dwWait),
  267. &bqi.BatteryTag, sizeof(bqi.BatteryTag),
  268. &dwOut, NULL) && bqi.BatteryTag) {
  269. /*
  270. * With the tag, you can query the battery info.
  271. */
  272. BATTERY_INFORMATION bi;
  273. bqi.InformationLevel = BatteryInformation;
  274. bqi.AtRate = 0;
  275. if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION,
  276. &bqi, sizeof(bqi),
  277. &bi, sizeof(bi),
  278. &dwOut, NULL)) {
  279. // Only system batteries count
  280. if (bi.Capabilities & BATTERY_SYSTEM_BATTERY) {
  281. if (!(bi.Capabilities & BATTERY_IS_SHORT_TERM)) {
  282. dwResult |= GMIB_HASBATTERY;
  283. }
  284. /*
  285. * And then query the battery status.
  286. */
  287. BATTERY_WAIT_STATUS bws;
  288. BATTERY_STATUS bs;
  289. ZeroMemory(&bws, sizeof(bws));
  290. bws.BatteryTag = bqi.BatteryTag;
  291. if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS,
  292. &bws, sizeof(bws),
  293. &bs, sizeof(bs),
  294. &dwOut, NULL)) {
  295. if (bs.PowerState & BATTERY_POWER_ON_LINE) {
  296. dwResult &= ~GMIB_ONBATTERY;
  297. }
  298. }
  299. }
  300. }
  301. }
  302. CloseHandle(hBattery);
  303. }
  304. }
  305. LocalFree(pdidd);
  306. }
  307. }
  308. } else {
  309. // Enumeration failed - perhaps we're out of items
  310. if (GetLastError() == ERROR_NO_MORE_ITEMS)
  311. break;
  312. }
  313. }
  314. SetupDiDestroyDeviceInfoList(hdev);
  315. }
  316. //
  317. // On Windows NT, SetupDi tells us everything there is to know.
  318. // So once you get this far, you're finished.
  319. //
  320. if (g_bRunningOnNT) {
  321. goto finish;
  322. }
  323. //------------------------------------------------------------------
  324. //
  325. // Second try - GetSystemPowerStatus
  326. //
  327. //------------------------------------------------------------------
  328. //
  329. // On Windows 9x, GetSystemPowerStatus enumerates a disjoint set of
  330. // batteries from SetupDi, so it's worth calling to find out.
  331. //
  332. SYSTEM_POWER_STATUS status;
  333. if (GetSystemPowerStatus(&status)) {
  334. //
  335. // HACKHACK: Some APM BIOS implementations set BatteryFlag = 0
  336. // instead of 128 or 255 when they don't have a
  337. // battery, so we have to check both.
  338. //
  339. if (status.BatteryFlag != 0 &&
  340. !(status.BatteryFlag & SPSBF_NOBATTERY)) {
  341. //
  342. // Found an APM battery.
  343. //
  344. dwResult |= GMIB_HASBATTERY;
  345. }
  346. if (status.ACLineStatus == SPSAC_ONLINE) {
  347. dwResult &= ~GMIB_ONBATTERY;
  348. }
  349. }
  350. #ifdef TRY_NtPowerInformation // Hopefully the Third Try won't be necessary
  351. SYSTEM_POWER_CAPABILITIES caps;
  352. //------------------------------------------------------------------
  353. //
  354. // Third try - NtPowerInformation
  355. //
  356. //------------------------------------------------------------------
  357. //
  358. // NtPowerInformation is supported on Win98 and Windows 2000, but not
  359. // Windows 95 or NT4.
  360. //
  361. if (SUCCEEDED(NtPowerInformation(SystemPowerCapabilities, NULL, 0,
  362. &caps, sizeof(caps)))) {
  363. if (caps.BatteriesAreShortTerm) {
  364. #error futz futz
  365. }
  366. if (caps.SystemBatteriesPresent && !fFoundUPS) {
  367. #error futz futz
  368. }
  369. }
  370. #endif // TRY_NtPowerInformation
  371. finish:
  372. //
  373. // Final cleanup: If we didn't find a battery, then presume that we
  374. // are on AC power.
  375. //
  376. if (!(dwResult & GMIB_HASBATTERY))
  377. dwResult &= ~GMIB_ONBATTERY;
  378. return dwResult;
  379. }
  380. /*****************************************************************************
  381. *
  382. * TERMINAL SERVER CLIENT
  383. *
  384. * This is particularly gruesome because Terminal Server for NT4 SP3 goes
  385. * to extraordinary lengths to prevent you from detecting it. Even the
  386. * semi-documented NtCurrentPeb()->SessionId trick doesn't work on NT4 SP3.
  387. * So we have to go to the totally undocumented winsta.dll to find out.
  388. *
  389. *****************************************************************************/
  390. BOOL g_fTSClient = -1; // Tri-state, 0 = no, 1 = yes, -1 = don't know
  391. BOOL IsTSClientNT4(void)
  392. {
  393. BOOL fTS = FALSE; // Assume not
  394. HINSTANCE hinstWinSta = LoadLibrary("winsta.dll");
  395. if (hinstWinSta) {
  396. PWINSTATIONQUERYINFORMATIONW WinStationQueryInformationW;
  397. WINSTATIONINFORMATIONW wi;
  398. WinStationQueryInformationW = (PWINSTATIONQUERYINFORMATIONW)
  399. GetProcAddress(hinstWinSta, "WinStationQueryInformationW");
  400. if (WinStationQueryInformationW &&
  401. WinStationQueryInformationW(SERVERNAME_CURRENT, LOGONID_CURRENT, WinStationInformation, &wi, sizeof(wi), NULL) &&
  402. wi.LogonId != 0) {
  403. fTS = TRUE;
  404. }
  405. FreeLibrary(hinstWinSta);
  406. }
  407. return fTS;
  408. }
  409. BOOL IsTSClient(void)
  410. {
  411. if (!g_bRunningOnNT) {
  412. // Windows 9x doesn't support Terminal Server
  413. return FALSE;
  414. } else if (g_bRunningOnNT5OrHigher) {
  415. // NT5 has a new system metric to detect this
  416. return GetSystemMetrics(SM_REMOTESESSION);
  417. } else {
  418. // NT4 is gross and evil. This is slow, so cache the result.
  419. if (g_fTSClient < 0)
  420. g_fTSClient = IsTSClientNT4();
  421. return g_fTSClient;
  422. }
  423. }
  424. /*****************************************************************************
  425. *
  426. * SHGetMachineInfo
  427. *
  428. *****************************************************************************/
  429. //
  430. // SHGetMachineInfo
  431. //
  432. // Given an index, returns some info about that index. See shlwapi.w
  433. // for documentation on the flags available.
  434. //
  435. STDAPI_(DWORD_PTR) SHGetMachineInfo(UINT gmi)
  436. {
  437. switch (gmi) {
  438. case GMI_DOCKSTATE:
  439. return GetDockedState();
  440. case GMI_BATTERYSTATE:
  441. return GetBatteryState();
  442. //
  443. // It smell like a laptop if it has a battery or if it can be docked.
  444. //
  445. case GMI_LAPTOP:
  446. return (GetBatteryState() & GMIB_HASBATTERY) ||
  447. (GetDockedState() != GMID_NOTDOCKABLE);
  448. case GMI_TSCLIENT:
  449. return IsTSClient();
  450. }
  451. TraceMsg(DM_WARNING, "SHGetMachineInfo: Unknown info query %d", gmi);
  452. return 0;
  453. }
  454. #else
  455. STDAPI_(DWORD_PTR) SHGetMachineInfo(UINT gmi)
  456. {
  457. // IEUNIX : Stubbed out this api to resolve undefind symbol in linking.
  458. TraceMsg(DM_WARNING, "SHGetMachineInfo: Unknown info query %d", gmi);
  459. return 0;
  460. }
  461. #endif