Revert "Fix broken channel icon in chrome://help on CrOS" and try again
[chromium-blink-merge.git] / base / win / shortcut.cc
blob2dd01a83d9e91871d614c86e0e2e351f288c9422
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.
5 #include "base/win/shortcut.h"
7 #include <shellapi.h>
8 #include <shldisp.h>
9 #include <shlobj.h>
10 #include <propkey.h>
12 #include "base/files/file_util.h"
13 #include "base/strings/string_util.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "base/win/scoped_bstr.h"
16 #include "base/win/scoped_comptr.h"
17 #include "base/win/scoped_handle.h"
18 #include "base/win/scoped_propvariant.h"
19 #include "base/win/scoped_variant.h"
20 #include "base/win/win_util.h"
21 #include "base/win/windows_version.h"
23 namespace base {
24 namespace win {
26 namespace {
28 // String resource IDs in shell32.dll.
29 const uint32_t kPinToTaskbarID = 5386; // Win7+
30 const uint32_t kUnpinFromTaskbarID = 5387; // Win7+
31 const uint32_t kPinToStartID = 51201; // Win8+
32 const uint32_t kUnpinFromStartID = 51394; // Win10+
34 // Traits for a GenericScopedHandle that will free a module on closure.
35 struct ModuleTraits {
36 typedef HMODULE Handle;
37 static Handle NullHandle() { return nullptr; }
38 static bool IsHandleValid(Handle module) { return !!module; }
39 static bool CloseHandle(Handle module) { return !!::FreeLibrary(module); }
41 private:
42 DISALLOW_IMPLICIT_CONSTRUCTORS(ModuleTraits);
45 // An object that will free a module when it goes out of scope.
46 using ScopedLibrary = GenericScopedHandle<ModuleTraits, DummyVerifierTraits>;
48 // Returns the shell resource string identified by |resource_id|, or an empty
49 // string on error.
50 string16 LoadShellResourceString(uint32_t resource_id) {
51 ScopedLibrary shell32(::LoadLibrary(L"shell32.dll"));
52 if (!shell32.IsValid())
53 return string16();
55 const wchar_t* resource_ptr = nullptr;
56 int length = ::LoadStringW(shell32.Get(), resource_id,
57 reinterpret_cast<wchar_t*>(&resource_ptr), 0);
58 if (!length || !resource_ptr)
59 return string16();
60 return string16(resource_ptr, length);
63 // Uses the shell to perform the verb identified by |resource_id| on |path|.
64 bool DoVerbOnFile(uint32_t resource_id, const FilePath& path) {
65 string16 verb_name(LoadShellResourceString(resource_id));
66 if (verb_name.empty())
67 return false;
69 ScopedComPtr<IShellDispatch> shell_dispatch;
70 HRESULT hresult =
71 shell_dispatch.CreateInstance(CLSID_Shell, nullptr, CLSCTX_INPROC_SERVER);
72 if (FAILED(hresult) || !shell_dispatch.get())
73 return false;
75 ScopedComPtr<Folder> folder;
76 hresult = shell_dispatch->NameSpace(
77 ScopedVariant(path.DirName().value().c_str()), folder.Receive());
78 if (FAILED(hresult) || !folder.get())
79 return false;
81 ScopedComPtr<FolderItem> item;
82 hresult = folder->ParseName(ScopedBstr(path.BaseName().value().c_str()),
83 item.Receive());
84 if (FAILED(hresult) || !item.get())
85 return false;
87 ScopedComPtr<FolderItemVerbs> verbs;
88 hresult = item->Verbs(verbs.Receive());
89 if (FAILED(hresult) || !verbs.get())
90 return false;
92 long verb_count = 0;
93 hresult = verbs->get_Count(&verb_count);
94 if (FAILED(hresult))
95 return false;
97 for (long i = 0; i < verb_count; ++i) {
98 ScopedComPtr<FolderItemVerb> verb;
99 hresult = verbs->Item(ScopedVariant(i, VT_I4), verb.Receive());
100 if (FAILED(hresult) || !verb.get())
101 continue;
102 ScopedBstr name;
103 hresult = verb->get_Name(name.Receive());
104 if (FAILED(hresult))
105 continue;
106 if (StringPiece16(name, name.Length()) == verb_name) {
107 hresult = verb->DoIt();
108 return SUCCEEDED(hresult);
111 return false;
114 // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they
115 // are already initialized).
116 // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|.
117 // If any of the above steps fail, both |i_shell_link| and |i_persist_file| will
118 // be released.
119 void InitializeShortcutInterfaces(
120 const wchar_t* shortcut,
121 ScopedComPtr<IShellLink>* i_shell_link,
122 ScopedComPtr<IPersistFile>* i_persist_file) {
123 i_shell_link->Release();
124 i_persist_file->Release();
125 if (FAILED(i_shell_link->CreateInstance(CLSID_ShellLink, NULL,
126 CLSCTX_INPROC_SERVER)) ||
127 FAILED(i_persist_file->QueryFrom(i_shell_link->get())) ||
128 (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) {
129 i_shell_link->Release();
130 i_persist_file->Release();
134 } // namespace
136 ShortcutProperties::ShortcutProperties()
137 : icon_index(-1), dual_mode(false), options(0U) {
140 ShortcutProperties::~ShortcutProperties() {
143 bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path,
144 const ShortcutProperties& properties,
145 ShortcutOperation operation) {
146 base::ThreadRestrictions::AssertIOAllowed();
148 // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING.
149 if (operation != SHORTCUT_UPDATE_EXISTING &&
150 !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) {
151 NOTREACHED();
152 return false;
155 bool shortcut_existed = PathExists(shortcut_path);
157 // Interfaces to the old shortcut when replacing an existing shortcut.
158 ScopedComPtr<IShellLink> old_i_shell_link;
159 ScopedComPtr<IPersistFile> old_i_persist_file;
161 // Interfaces to the shortcut being created/updated.
162 ScopedComPtr<IShellLink> i_shell_link;
163 ScopedComPtr<IPersistFile> i_persist_file;
164 switch (operation) {
165 case SHORTCUT_CREATE_ALWAYS:
166 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file);
167 break;
168 case SHORTCUT_UPDATE_EXISTING:
169 InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link,
170 &i_persist_file);
171 break;
172 case SHORTCUT_REPLACE_EXISTING:
173 InitializeShortcutInterfaces(shortcut_path.value().c_str(),
174 &old_i_shell_link, &old_i_persist_file);
175 // Confirm |shortcut_path| exists and is a shortcut by verifying
176 // |old_i_persist_file| was successfully initialized in the call above. If
177 // so, initialize the interfaces to begin writing a new shortcut (to
178 // overwrite the current one if successful).
179 if (old_i_persist_file.get())
180 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file);
181 break;
182 default:
183 NOTREACHED();
186 // Return false immediately upon failure to initialize shortcut interfaces.
187 if (!i_persist_file.get())
188 return false;
190 if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) &&
191 FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) {
192 return false;
195 if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) &&
196 FAILED(i_shell_link->SetWorkingDirectory(
197 properties.working_dir.value().c_str()))) {
198 return false;
201 if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) {
202 if (FAILED(i_shell_link->SetArguments(properties.arguments.c_str())))
203 return false;
204 } else if (old_i_persist_file.get()) {
205 wchar_t current_arguments[MAX_PATH] = {0};
206 if (SUCCEEDED(old_i_shell_link->GetArguments(current_arguments,
207 MAX_PATH))) {
208 i_shell_link->SetArguments(current_arguments);
212 if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) &&
213 FAILED(i_shell_link->SetDescription(properties.description.c_str()))) {
214 return false;
217 if ((properties.options & ShortcutProperties::PROPERTIES_ICON) &&
218 FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(),
219 properties.icon_index))) {
220 return false;
223 bool has_app_id =
224 (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0;
225 bool has_dual_mode =
226 (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0;
227 if ((has_app_id || has_dual_mode) &&
228 GetVersion() >= VERSION_WIN7) {
229 ScopedComPtr<IPropertyStore> property_store;
230 if (FAILED(property_store.QueryFrom(i_shell_link.get())) ||
231 !property_store.get())
232 return false;
234 if (has_app_id &&
235 !SetAppIdForPropertyStore(property_store.get(),
236 properties.app_id.c_str())) {
237 return false;
239 if (has_dual_mode &&
240 !SetBooleanValueForPropertyStore(property_store.get(),
241 PKEY_AppUserModel_IsDualMode,
242 properties.dual_mode)) {
243 return false;
247 // Release the interfaces to the old shortcut to make sure it doesn't prevent
248 // overwriting it if needed.
249 old_i_persist_file.Release();
250 old_i_shell_link.Release();
252 HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE);
254 // Release the interfaces in case the SHChangeNotify call below depends on
255 // the operations above being fully completed.
256 i_persist_file.Release();
257 i_shell_link.Release();
259 // If we successfully created/updated the icon, notify the shell that we have
260 // done so.
261 const bool succeeded = SUCCEEDED(result);
262 if (succeeded) {
263 if (shortcut_existed) {
264 // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing
265 // required.
266 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
267 } else {
268 SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcut_path.value().c_str(),
269 NULL);
273 return succeeded;
276 bool ResolveShortcutProperties(const FilePath& shortcut_path,
277 uint32 options,
278 ShortcutProperties* properties) {
279 DCHECK(options && properties);
280 base::ThreadRestrictions::AssertIOAllowed();
282 if (options & ~ShortcutProperties::PROPERTIES_ALL)
283 NOTREACHED() << "Unhandled property is used.";
285 ScopedComPtr<IShellLink> i_shell_link;
287 // Get pointer to the IShellLink interface.
288 if (FAILED(i_shell_link.CreateInstance(CLSID_ShellLink, NULL,
289 CLSCTX_INPROC_SERVER))) {
290 return false;
293 ScopedComPtr<IPersistFile> persist;
294 // Query IShellLink for the IPersistFile interface.
295 if (FAILED(persist.QueryFrom(i_shell_link.get())))
296 return false;
298 // Load the shell link.
299 if (FAILED(persist->Load(shortcut_path.value().c_str(), STGM_READ)))
300 return false;
302 // Reset |properties|.
303 properties->options = 0;
305 wchar_t temp[MAX_PATH];
306 if (options & ShortcutProperties::PROPERTIES_TARGET) {
307 if (FAILED(i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY)))
308 return false;
309 properties->set_target(FilePath(temp));
312 if (options & ShortcutProperties::PROPERTIES_WORKING_DIR) {
313 if (FAILED(i_shell_link->GetWorkingDirectory(temp, MAX_PATH)))
314 return false;
315 properties->set_working_dir(FilePath(temp));
318 if (options & ShortcutProperties::PROPERTIES_ARGUMENTS) {
319 if (FAILED(i_shell_link->GetArguments(temp, MAX_PATH)))
320 return false;
321 properties->set_arguments(temp);
324 if (options & ShortcutProperties::PROPERTIES_DESCRIPTION) {
325 // Note: description length constrained by MAX_PATH.
326 if (FAILED(i_shell_link->GetDescription(temp, MAX_PATH)))
327 return false;
328 properties->set_description(temp);
331 if (options & ShortcutProperties::PROPERTIES_ICON) {
332 int temp_index;
333 if (FAILED(i_shell_link->GetIconLocation(temp, MAX_PATH, &temp_index)))
334 return false;
335 properties->set_icon(FilePath(temp), temp_index);
338 // Windows 7+ options, avoiding unnecessary work.
339 if ((options & ShortcutProperties::PROPERTIES_WIN7) &&
340 GetVersion() >= VERSION_WIN7) {
341 ScopedComPtr<IPropertyStore> property_store;
342 if (FAILED(property_store.QueryFrom(i_shell_link.get())))
343 return false;
345 if (options & ShortcutProperties::PROPERTIES_APP_ID) {
346 ScopedPropVariant pv_app_id;
347 if (property_store->GetValue(PKEY_AppUserModel_ID,
348 pv_app_id.Receive()) != S_OK) {
349 return false;
351 switch (pv_app_id.get().vt) {
352 case VT_EMPTY:
353 properties->set_app_id(L"");
354 break;
355 case VT_LPWSTR:
356 properties->set_app_id(pv_app_id.get().pwszVal);
357 break;
358 default:
359 NOTREACHED() << "Unexpected variant type: " << pv_app_id.get().vt;
360 return false;
364 if (options & ShortcutProperties::PROPERTIES_DUAL_MODE) {
365 ScopedPropVariant pv_dual_mode;
366 if (property_store->GetValue(PKEY_AppUserModel_IsDualMode,
367 pv_dual_mode.Receive()) != S_OK) {
368 return false;
370 switch (pv_dual_mode.get().vt) {
371 case VT_EMPTY:
372 properties->set_dual_mode(false);
373 break;
374 case VT_BOOL:
375 properties->set_dual_mode(pv_dual_mode.get().boolVal == VARIANT_TRUE);
376 break;
377 default:
378 NOTREACHED() << "Unexpected variant type: " << pv_dual_mode.get().vt;
379 return false;
384 return true;
387 bool ResolveShortcut(const FilePath& shortcut_path,
388 FilePath* target_path,
389 string16* args) {
390 uint32 options = 0;
391 if (target_path)
392 options |= ShortcutProperties::PROPERTIES_TARGET;
393 if (args)
394 options |= ShortcutProperties::PROPERTIES_ARGUMENTS;
395 DCHECK(options);
397 ShortcutProperties properties;
398 if (!ResolveShortcutProperties(shortcut_path, options, &properties))
399 return false;
401 if (target_path)
402 *target_path = properties.target;
403 if (args)
404 *args = properties.arguments;
405 return true;
408 bool PinShortcutToTaskbar(const FilePath& shortcut) {
409 base::ThreadRestrictions::AssertIOAllowed();
411 // "Pin to taskbar" is only supported after Win7.
412 if (GetVersion() < VERSION_WIN7)
413 return false;
415 return DoVerbOnFile(kPinToTaskbarID, shortcut);
418 bool UnpinShortcutFromTaskbar(const FilePath& shortcut) {
419 base::ThreadRestrictions::AssertIOAllowed();
421 // "Unpin from taskbar" is only supported after Win7.
422 if (GetVersion() < VERSION_WIN7)
423 return false;
425 return DoVerbOnFile(kUnpinFromTaskbarID, shortcut);
428 bool PinShortcutToStart(const FilePath& shortcut) {
429 base::ThreadRestrictions::AssertIOAllowed();
431 // While "Pin to Start" is supported as of Win8, it was never used by Chrome
432 // in Win8. The behaviour on Win8 is different (new shortcut every time
433 // instead of a single pin associated with its app id) and the Start Menu
434 // shortcut itself is visible on the Start Screen whereas it is not on Win10.
435 // For simplicity's sake and per greater necessity on Win10, it is only
436 // supported in Chrome on Win10+.
437 if (GetVersion() < VERSION_WIN10)
438 return false;
440 return DoVerbOnFile(kPinToStartID, shortcut);
443 bool UnpinShortcutFromStart(const FilePath& shortcut) {
444 base::ThreadRestrictions::AssertIOAllowed();
446 // "Unpin from Start Menu" is only supported after Win10.
447 if (GetVersion() < VERSION_WIN10)
448 return false;
450 return DoVerbOnFile(kUnpinFromStartID, shortcut);
453 } // namespace win
454 } // namespace base