OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/win/win_util.h" | |
6 | |
7 #include <aclapi.h> | |
8 #include <cfgmgr32.h> | |
9 #include <lm.h> | |
10 #include <powrprof.h> | |
11 #include <shellapi.h> | |
12 #include <shlobj.h> | |
13 #include <shobjidl.h> // Must be before propkey. | |
14 #include <initguid.h> | |
15 #include <propkey.h> | |
16 #include <propvarutil.h> | |
17 #include <sddl.h> | |
18 #include <setupapi.h> | |
19 #include <signal.h> | |
20 #include <stdlib.h> | |
21 | |
22 #include "base/lazy_instance.h" | |
23 #include "base/logging.h" | |
24 #include "base/memory/scoped_ptr.h" | |
25 #include "base/strings/string_util.h" | |
26 #include "base/strings/stringprintf.h" | |
27 #include "base/threading/thread_restrictions.h" | |
28 #include "base/win/metro.h" | |
29 #include "base/win/registry.h" | |
30 #include "base/win/scoped_co_mem.h" | |
31 #include "base/win/scoped_handle.h" | |
32 #include "base/win/scoped_propvariant.h" | |
33 #include "base/win/windows_version.h" | |
34 | |
35 namespace { | |
36 | |
37 // Sets the value of |property_key| to |property_value| in |property_store|. | |
38 bool SetPropVariantValueForPropertyStore( | |
39 IPropertyStore* property_store, | |
40 const PROPERTYKEY& property_key, | |
41 const base::win::ScopedPropVariant& property_value) { | |
42 DCHECK(property_store); | |
43 | |
44 HRESULT result = property_store->SetValue(property_key, property_value.get()); | |
45 if (result == S_OK) | |
46 result = property_store->Commit(); | |
47 return SUCCEEDED(result); | |
48 } | |
49 | |
50 void __cdecl ForceCrashOnSigAbort(int) { | |
51 *((int*)0) = 0x1337; | |
52 } | |
53 | |
54 const wchar_t kWindows8OSKRegPath[] = | |
55 L"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}" | |
56 L"\\LocalServer32"; | |
57 | |
58 // Returns true if a physical keyboard is detected on Windows 8 and up. | |
59 // Uses the Setup APIs to enumerate the attached keyboards and returns true | |
60 // if the keyboard count is 1 or more.. While this will work in most cases | |
61 // it won't work if there are devices which expose keyboard interfaces which | |
62 // are attached to the machine. | |
63 bool IsKeyboardPresentOnSlate() { | |
64 // This function is only supported for Windows 8 and up. | |
65 DCHECK(base::win::GetVersion() >= base::win::VERSION_WIN8); | |
66 | |
67 // This function should be only invoked for machines with touch screens. | |
68 if ((GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) | |
69 != NID_INTEGRATED_TOUCH) { | |
70 return true; | |
71 } | |
72 | |
73 // If the device is docked, the user is treating the device as a PC. | |
74 if (GetSystemMetrics(SM_SYSTEMDOCKED) != 0) | |
75 return true; | |
76 | |
77 // To determine whether a keyboard is present on the device, we do the | |
78 // following:- | |
79 // 1. Check whether the device supports auto rotation. If it does then | |
80 // it possibly supports flipping from laptop to slate mode. If it | |
81 // does not support auto rotation, then we assume it is a desktop | |
82 // or a normal laptop and assume that there is a keyboard. | |
83 | |
84 // 2. If the device supports auto rotation, then we get its platform role | |
85 // and check the system metric SM_CONVERTIBLESLATEMODE to see if it is | |
86 // being used in slate mode. If yes then we return false here to ensure | |
87 // that the OSK is displayed. | |
88 | |
89 // 3. If step 1 and 2 fail then we check attached keyboards and return true | |
90 // if we find ACPI\* or HID\VID* keyboards. | |
91 | |
92 typedef BOOL (WINAPI* GetAutoRotationState)(PAR_STATE state); | |
93 | |
94 GetAutoRotationState get_rotation_state = | |
95 reinterpret_cast<GetAutoRotationState>(::GetProcAddress( | |
96 GetModuleHandle(L"user32.dll"), "GetAutoRotationState")); | |
97 | |
98 if (get_rotation_state) { | |
99 AR_STATE auto_rotation_state = AR_ENABLED; | |
100 get_rotation_state(&auto_rotation_state); | |
101 if ((auto_rotation_state & AR_NOSENSOR) || | |
102 (auto_rotation_state & AR_NOT_SUPPORTED)) { | |
103 // If there is no auto rotation sensor or rotation is not supported in | |
104 // the current configuration, then we can assume that this is a desktop | |
105 // or a traditional laptop. | |
106 return true; | |
107 } | |
108 } | |
109 | |
110 // Check if the device is being used as a laptop or a tablet. This can be | |
111 // checked by first checking the role of the device and then the | |
112 // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being used | |
113 // as a tablet then we want the OSK to show up. | |
114 POWER_PLATFORM_ROLE role = PowerDeterminePlatformRole(); | |
115 | |
116 if (((role == PlatformRoleMobile) || (role == PlatformRoleSlate)) && | |
117 (GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0)) | |
118 return false; | |
119 | |
120 const GUID KEYBOARD_CLASS_GUID = | |
121 { 0x4D36E96B, 0xE325, 0x11CE, | |
122 { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } }; | |
123 | |
124 // Query for all the keyboard devices. | |
125 HDEVINFO device_info = | |
126 SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, NULL, NULL, DIGCF_PRESENT); | |
127 if (device_info == INVALID_HANDLE_VALUE) | |
128 return false; | |
129 | |
130 // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If | |
131 // the count is more than 1 we assume that a keyboard is present. This is | |
132 // under the assumption that there will always be one keyboard device. | |
133 int keyboard_count = 0; | |
134 for (DWORD i = 0;; ++i) { | |
135 SP_DEVINFO_DATA device_info_data = { 0 }; | |
136 device_info_data.cbSize = sizeof(device_info_data); | |
137 if (!SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) | |
138 break; | |
139 | |
140 // Get the device ID. | |
141 wchar_t device_id[MAX_DEVICE_ID_LEN]; | |
142 CONFIGRET status = CM_Get_Device_ID(device_info_data.DevInst, | |
143 device_id, | |
144 MAX_DEVICE_ID_LEN, | |
145 0); | |
146 if (status == CR_SUCCESS) { | |
147 // To reduce the scope of the hack we only look for ACPI and HID\\VID | |
148 // prefixes in the keyboard device ids. | |
149 if (StartsWith(device_id, L"ACPI", false) || | |
150 StartsWith(device_id, L"HID\\VID", false)) { | |
151 keyboard_count++; | |
152 } | |
153 } | |
154 } | |
155 // The heuristic we are using is to check the count of keyboards and return | |
156 // true if the API's report one or more keyboards. Please note that this | |
157 // will break for non keyboard devices which expose a keyboard PDO. | |
158 return keyboard_count >= 1; | |
159 } | |
160 | |
161 } // namespace | |
162 | |
163 namespace base { | |
164 namespace win { | |
165 | |
166 static bool g_crash_on_process_detach = false; | |
167 | |
168 void GetNonClientMetrics(NONCLIENTMETRICS_XP* metrics) { | |
169 DCHECK(metrics); | |
170 metrics->cbSize = sizeof(*metrics); | |
171 const bool success = !!SystemParametersInfo( | |
172 SPI_GETNONCLIENTMETRICS, | |
173 metrics->cbSize, | |
174 reinterpret_cast<NONCLIENTMETRICS*>(metrics), | |
175 0); | |
176 DCHECK(success); | |
177 } | |
178 | |
179 bool GetUserSidString(std::wstring* user_sid) { | |
180 // Get the current token. | |
181 HANDLE token = NULL; | |
182 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) | |
183 return false; | |
184 base::win::ScopedHandle token_scoped(token); | |
185 | |
186 DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; | |
187 scoped_ptr<BYTE[]> user_bytes(new BYTE[size]); | |
188 TOKEN_USER* user = reinterpret_cast<TOKEN_USER*>(user_bytes.get()); | |
189 | |
190 if (!::GetTokenInformation(token, TokenUser, user, size, &size)) | |
191 return false; | |
192 | |
193 if (!user->User.Sid) | |
194 return false; | |
195 | |
196 // Convert the data to a string. | |
197 wchar_t* sid_string; | |
198 if (!::ConvertSidToStringSid(user->User.Sid, &sid_string)) | |
199 return false; | |
200 | |
201 *user_sid = sid_string; | |
202 | |
203 ::LocalFree(sid_string); | |
204 | |
205 return true; | |
206 } | |
207 | |
208 bool IsShiftPressed() { | |
209 return (::GetKeyState(VK_SHIFT) & 0x8000) == 0x8000; | |
210 } | |
211 | |
212 bool IsCtrlPressed() { | |
213 return (::GetKeyState(VK_CONTROL) & 0x8000) == 0x8000; | |
214 } | |
215 | |
216 bool IsAltPressed() { | |
217 return (::GetKeyState(VK_MENU) & 0x8000) == 0x8000; | |
218 } | |
219 | |
220 bool IsAltGrPressed() { | |
221 return (::GetKeyState(VK_MENU) & 0x8000) == 0x8000 && | |
222 (::GetKeyState(VK_CONTROL) & 0x8000) == 0x8000; | |
223 } | |
224 | |
225 bool UserAccountControlIsEnabled() { | |
226 // This can be slow if Windows ends up going to disk. Should watch this key | |
227 // for changes and only read it once, preferably on the file thread. | |
228 // http://code.google.com/p/chromium/issues/detail?id=61644 | |
229 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
230 | |
231 base::win::RegKey key(HKEY_LOCAL_MACHINE, | |
232 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", | |
233 KEY_READ); | |
234 DWORD uac_enabled; | |
235 if (key.ReadValueDW(L"EnableLUA", &uac_enabled) != ERROR_SUCCESS) | |
236 return true; | |
237 // Users can set the EnableLUA value to something arbitrary, like 2, which | |
238 // Vista will treat as UAC enabled, so we make sure it is not set to 0. | |
239 return (uac_enabled != 0); | |
240 } | |
241 | |
242 bool SetBooleanValueForPropertyStore(IPropertyStore* property_store, | |
243 const PROPERTYKEY& property_key, | |
244 bool property_bool_value) { | |
245 ScopedPropVariant property_value; | |
246 if (FAILED(InitPropVariantFromBoolean(property_bool_value, | |
247 property_value.Receive()))) { | |
248 return false; | |
249 } | |
250 | |
251 return SetPropVariantValueForPropertyStore(property_store, | |
252 property_key, | |
253 property_value); | |
254 } | |
255 | |
256 bool SetStringValueForPropertyStore(IPropertyStore* property_store, | |
257 const PROPERTYKEY& property_key, | |
258 const wchar_t* property_string_value) { | |
259 ScopedPropVariant property_value; | |
260 if (FAILED(InitPropVariantFromString(property_string_value, | |
261 property_value.Receive()))) { | |
262 return false; | |
263 } | |
264 | |
265 return SetPropVariantValueForPropertyStore(property_store, | |
266 property_key, | |
267 property_value); | |
268 } | |
269 | |
270 bool SetAppIdForPropertyStore(IPropertyStore* property_store, | |
271 const wchar_t* app_id) { | |
272 // App id should be less than 64 chars and contain no space. And recommended | |
273 // format is CompanyName.ProductName[.SubProduct.ProductNumber]. | |
274 // See http://msdn.microsoft.com/en-us/library/dd378459%28VS.85%29.aspx | |
275 DCHECK(lstrlen(app_id) < 64 && wcschr(app_id, L' ') == NULL); | |
276 | |
277 return SetStringValueForPropertyStore(property_store, | |
278 PKEY_AppUserModel_ID, | |
279 app_id); | |
280 } | |
281 | |
282 static const char16 kAutoRunKeyPath[] = | |
283 L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; | |
284 | |
285 bool AddCommandToAutoRun(HKEY root_key, const string16& name, | |
286 const string16& command) { | |
287 base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); | |
288 return (autorun_key.WriteValue(name.c_str(), command.c_str()) == | |
289 ERROR_SUCCESS); | |
290 } | |
291 | |
292 bool RemoveCommandFromAutoRun(HKEY root_key, const string16& name) { | |
293 base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); | |
294 return (autorun_key.DeleteValue(name.c_str()) == ERROR_SUCCESS); | |
295 } | |
296 | |
297 bool ReadCommandFromAutoRun(HKEY root_key, | |
298 const string16& name, | |
299 string16* command) { | |
300 base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_QUERY_VALUE); | |
301 return (autorun_key.ReadValue(name.c_str(), command) == ERROR_SUCCESS); | |
302 } | |
303 | |
304 void SetShouldCrashOnProcessDetach(bool crash) { | |
305 g_crash_on_process_detach = crash; | |
306 } | |
307 | |
308 bool ShouldCrashOnProcessDetach() { | |
309 return g_crash_on_process_detach; | |
310 } | |
311 | |
312 void SetAbortBehaviorForCrashReporting() { | |
313 // Prevent CRT's abort code from prompting a dialog or trying to "report" it. | |
314 // Disabling the _CALL_REPORTFAULT behavior is important since otherwise it | |
315 // has the sideffect of clearing our exception filter, which means we | |
316 // don't get any crash. | |
317 _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); | |
318 | |
319 // Set a SIGABRT handler for good measure. We will crash even if the default | |
320 // is left in place, however this allows us to crash earlier. And it also | |
321 // lets us crash in response to code which might directly call raise(SIGABRT) | |
322 signal(SIGABRT, ForceCrashOnSigAbort); | |
323 } | |
324 | |
325 bool IsTabletDevice() { | |
326 if (GetSystemMetrics(SM_MAXIMUMTOUCHES) == 0) | |
327 return false; | |
328 | |
329 base::win::Version version = base::win::GetVersion(); | |
330 if (version == base::win::VERSION_XP) | |
331 return (GetSystemMetrics(SM_TABLETPC) != 0); | |
332 | |
333 // If the device is docked, the user is treating the device as a PC. | |
334 if (GetSystemMetrics(SM_SYSTEMDOCKED) != 0) | |
335 return false; | |
336 | |
337 // PlatformRoleSlate was only added in Windows 8, but prior to Win8 it is | |
338 // still possible to check for a mobile power profile. | |
339 POWER_PLATFORM_ROLE role = PowerDeterminePlatformRole(); | |
340 bool mobile_power_profile = (role == PlatformRoleMobile); | |
341 bool slate_power_profile = false; | |
342 if (version >= base::win::VERSION_WIN8) | |
343 slate_power_profile = (role == PlatformRoleSlate); | |
344 | |
345 if (mobile_power_profile || slate_power_profile) | |
346 return (GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0); | |
347 | |
348 return false; | |
349 } | |
350 | |
351 bool DisplayVirtualKeyboard() { | |
352 if (base::win::GetVersion() < base::win::VERSION_WIN8) | |
353 return false; | |
354 | |
355 if (IsKeyboardPresentOnSlate()) | |
356 return false; | |
357 | |
358 static base::LazyInstance<string16>::Leaky osk_path = | |
359 LAZY_INSTANCE_INITIALIZER; | |
360 | |
361 if (osk_path.Get().empty()) { | |
362 // We need to launch TabTip.exe from the location specified under the | |
363 // LocalServer32 key for the {{054AAE20-4BEA-4347-8A35-64A533254A9D}} | |
364 // CLSID. | |
365 // TabTip.exe is typically found at | |
366 // c:\program files\common files\microsoft shared\ink on English Windows. | |
367 // We don't want to launch TabTip.exe from | |
368 // c:\program files (x86)\common files\microsoft shared\ink. This path is | |
369 // normally found on 64 bit Windows. | |
370 base::win::RegKey key(HKEY_LOCAL_MACHINE, | |
371 kWindows8OSKRegPath, | |
372 KEY_READ | KEY_WOW64_64KEY); | |
373 DWORD osk_path_length = 1024; | |
374 if (key.ReadValue(NULL, | |
375 WriteInto(&osk_path.Get(), osk_path_length), | |
376 &osk_path_length, | |
377 NULL) != ERROR_SUCCESS) { | |
378 DLOG(WARNING) << "Failed to read on screen keyboard path from registry"; | |
379 return false; | |
380 } | |
381 size_t common_program_files_offset = | |
382 osk_path.Get().find(L"%CommonProgramFiles%"); | |
383 // Typically the path to TabTip.exe read from the registry will start with | |
384 // %CommonProgramFiles% which needs to be replaced with the corrsponding | |
385 // expanded string. | |
386 // If the path does not begin with %CommonProgramFiles% we use it as is. | |
387 if (common_program_files_offset != string16::npos) { | |
388 // Preserve the beginning quote in the path. | |
389 osk_path.Get().erase(common_program_files_offset, | |
390 wcslen(L"%CommonProgramFiles%")); | |
391 // The path read from the registry contains the %CommonProgramFiles% | |
392 // environment variable prefix. On 64 bit Windows the SHGetKnownFolderPath | |
393 // function returns the common program files path with the X86 suffix for | |
394 // the FOLDERID_ProgramFilesCommon value. | |
395 // To get the correct path to TabTip.exe we first read the environment | |
396 // variable CommonProgramW6432 which points to the desired common | |
397 // files path. Failing that we fallback to the SHGetKnownFolderPath API. | |
398 | |
399 // We then replace the %CommonProgramFiles% value with the actual common | |
400 // files path found in the process. | |
401 string16 common_program_files_path; | |
402 scoped_ptr<wchar_t[]> common_program_files_wow6432; | |
403 DWORD buffer_size = | |
404 GetEnvironmentVariable(L"CommonProgramW6432", NULL, 0); | |
405 if (buffer_size) { | |
406 common_program_files_wow6432.reset(new wchar_t[buffer_size]); | |
407 GetEnvironmentVariable(L"CommonProgramW6432", | |
408 common_program_files_wow6432.get(), | |
409 buffer_size); | |
410 common_program_files_path = common_program_files_wow6432.get(); | |
411 DCHECK(!common_program_files_path.empty()); | |
412 } else { | |
413 base::win::ScopedCoMem<wchar_t> common_program_files; | |
414 if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, NULL, | |
415 &common_program_files))) { | |
416 return false; | |
417 } | |
418 common_program_files_path = common_program_files; | |
419 } | |
420 | |
421 osk_path.Get().insert(1, common_program_files_path); | |
422 } | |
423 } | |
424 | |
425 HINSTANCE ret = ::ShellExecuteW(NULL, | |
426 L"", | |
427 osk_path.Get().c_str(), | |
428 NULL, | |
429 NULL, | |
430 SW_SHOW); | |
431 return reinterpret_cast<intptr_t>(ret) > 32; | |
432 } | |
433 | |
434 bool DismissVirtualKeyboard() { | |
435 if (base::win::GetVersion() < base::win::VERSION_WIN8) | |
436 return false; | |
437 | |
438 // We dismiss the virtual keyboard by generating the ESC keystroke | |
439 // programmatically. | |
440 const wchar_t kOSKClassName[] = L"IPTip_Main_Window"; | |
441 HWND osk = ::FindWindow(kOSKClassName, NULL); | |
442 if (::IsWindow(osk) && ::IsWindowEnabled(osk)) { | |
443 PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0); | |
444 return true; | |
445 } | |
446 return false; | |
447 } | |
448 | |
449 typedef HWND (*MetroRootWindow) (); | |
450 | |
451 enum DomainEnrollementState {UNKNOWN = -1, NOT_ENROLLED, ENROLLED}; | |
452 static volatile long int g_domain_state = UNKNOWN; | |
453 | |
454 bool IsEnrolledToDomain() { | |
455 // Doesn't make any sense to retry inside a user session because joining a | |
456 // domain will only kick in on a restart. | |
457 if (g_domain_state == UNKNOWN) { | |
458 LPWSTR domain; | |
459 NETSETUP_JOIN_STATUS join_status; | |
460 if(::NetGetJoinInformation(NULL, &domain, &join_status) != NERR_Success) | |
461 return false; | |
462 ::NetApiBufferFree(domain); | |
463 ::InterlockedCompareExchange(&g_domain_state, | |
464 join_status == ::NetSetupDomainName ? | |
465 ENROLLED : NOT_ENROLLED, | |
466 UNKNOWN); | |
467 } | |
468 | |
469 return g_domain_state == ENROLLED; | |
470 } | |
471 | |
472 void SetDomainStateForTesting(bool state) { | |
473 g_domain_state = state ? ENROLLED : NOT_ENROLLED; | |
474 } | |
475 | |
476 bool MaybeHasSHA256Support() { | |
477 const base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); | |
478 | |
479 if (os_info->version() == base::win::VERSION_PRE_XP) | |
480 return false; // Too old to have it and this OS is not supported anyway. | |
481 | |
482 if (os_info->version() == base::win::VERSION_XP) | |
483 return os_info->service_pack().major >= 3; // Windows XP SP3 has it. | |
484 | |
485 // Assume it is missing in this case, although it may not be. This category | |
486 // includes Windows XP x64, and Windows Server, where a hotfix could be | |
487 // deployed. | |
488 if (os_info->version() == base::win::VERSION_SERVER_2003) | |
489 return false; | |
490 | |
491 DCHECK(os_info->version() >= base::win::VERSION_VISTA); | |
492 return true; // New enough to have SHA-256 support. | |
493 } | |
494 | |
495 } // namespace win | |
496 } // namespace base | |
OLD | NEW |