NaCl: Update revision in DEPS, b3d4cc1 -> a0efd24, update UMA test
[chromium-blink-merge.git] / base / win / shortcut.cc
blob57f8e615d1dc5609062fc4a9e84460b434afcfea
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;
30 const uint32_t kUnpinFromTaskbarID = 5387;
32 // Traits for a GenericScopedHandle that will free a module on closure.
33 struct ModuleTraits {
34 typedef HMODULE Handle;
35 static Handle NullHandle() { return nullptr; }
36 static bool IsHandleValid(Handle module) { return !!module; }
37 static bool CloseHandle(Handle module) { return !!::FreeLibrary(module); }
39 private:
40 DISALLOW_IMPLICIT_CONSTRUCTORS(ModuleTraits);
43 // An object that will free a module when it goes out of scope.
44 using ScopedLibrary = GenericScopedHandle<ModuleTraits, DummyVerifierTraits>;
46 // Returns the shell resource string identified by |resource_id|, or an empty
47 // string on error.
48 string16 LoadShellResourceString(uint32_t resource_id) {
49 ScopedLibrary shell32(::LoadLibrary(L"shell32.dll"));
50 if (!shell32.IsValid())
51 return string16();
53 const wchar_t* resource_ptr = nullptr;
54 int length = ::LoadStringW(shell32.Get(), resource_id,
55 reinterpret_cast<wchar_t*>(&resource_ptr), 0);
56 if (!length || !resource_ptr)
57 return string16();
58 return string16(resource_ptr, length);
61 // Uses the shell to perform the verb identified by |resource_id| on |path|.
62 bool DoVerbOnFile(uint32_t resource_id, const FilePath& path) {
63 string16 verb_name(LoadShellResourceString(resource_id));
64 if (verb_name.empty())
65 return false;
67 ScopedComPtr<IShellDispatch> shell_dispatch;
68 HRESULT hresult =
69 shell_dispatch.CreateInstance(CLSID_Shell, nullptr, CLSCTX_INPROC_SERVER);
70 if (FAILED(hresult) || !shell_dispatch.get())
71 return false;
73 ScopedComPtr<Folder> folder;
74 hresult = shell_dispatch->NameSpace(
75 ScopedVariant(path.DirName().value().c_str()), folder.Receive());
76 if (FAILED(hresult) || !folder.get())
77 return false;
79 ScopedComPtr<FolderItem> item;
80 hresult = folder->ParseName(ScopedBstr(path.BaseName().value().c_str()),
81 item.Receive());
82 if (FAILED(hresult) || !item.get())
83 return false;
85 ScopedComPtr<FolderItemVerbs> verbs;
86 hresult = item->Verbs(verbs.Receive());
87 if (FAILED(hresult) || !verbs.get())
88 return false;
90 long verb_count = 0;
91 hresult = verbs->get_Count(&verb_count);
92 if (FAILED(hresult))
93 return false;
95 for (long i = 0; i < verb_count; ++i) {
96 ScopedComPtr<FolderItemVerb> verb;
97 hresult = verbs->Item(ScopedVariant(i, VT_I4), verb.Receive());
98 if (FAILED(hresult) || !verb.get())
99 continue;
100 ScopedBstr name;
101 hresult = verb->get_Name(name.Receive());
102 if (FAILED(hresult))
103 continue;
104 if (StringPiece16(name, name.Length()) == verb_name) {
105 hresult = verb->DoIt();
106 return SUCCEEDED(hresult);
109 return false;
112 // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they
113 // are already initialized).
114 // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|.
115 // If any of the above steps fail, both |i_shell_link| and |i_persist_file| will
116 // be released.
117 void InitializeShortcutInterfaces(
118 const wchar_t* shortcut,
119 ScopedComPtr<IShellLink>* i_shell_link,
120 ScopedComPtr<IPersistFile>* i_persist_file) {
121 i_shell_link->Release();
122 i_persist_file->Release();
123 if (FAILED(i_shell_link->CreateInstance(CLSID_ShellLink, NULL,
124 CLSCTX_INPROC_SERVER)) ||
125 FAILED(i_persist_file->QueryFrom(i_shell_link->get())) ||
126 (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) {
127 i_shell_link->Release();
128 i_persist_file->Release();
132 } // namespace
134 ShortcutProperties::ShortcutProperties()
135 : icon_index(-1), dual_mode(false), options(0U) {
138 ShortcutProperties::~ShortcutProperties() {
141 bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path,
142 const ShortcutProperties& properties,
143 ShortcutOperation operation) {
144 base::ThreadRestrictions::AssertIOAllowed();
146 // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING.
147 if (operation != SHORTCUT_UPDATE_EXISTING &&
148 !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) {
149 NOTREACHED();
150 return false;
153 bool shortcut_existed = PathExists(shortcut_path);
155 // Interfaces to the old shortcut when replacing an existing shortcut.
156 ScopedComPtr<IShellLink> old_i_shell_link;
157 ScopedComPtr<IPersistFile> old_i_persist_file;
159 // Interfaces to the shortcut being created/updated.
160 ScopedComPtr<IShellLink> i_shell_link;
161 ScopedComPtr<IPersistFile> i_persist_file;
162 switch (operation) {
163 case SHORTCUT_CREATE_ALWAYS:
164 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file);
165 break;
166 case SHORTCUT_UPDATE_EXISTING:
167 InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link,
168 &i_persist_file);
169 break;
170 case SHORTCUT_REPLACE_EXISTING:
171 InitializeShortcutInterfaces(shortcut_path.value().c_str(),
172 &old_i_shell_link, &old_i_persist_file);
173 // Confirm |shortcut_path| exists and is a shortcut by verifying
174 // |old_i_persist_file| was successfully initialized in the call above. If
175 // so, initialize the interfaces to begin writing a new shortcut (to
176 // overwrite the current one if successful).
177 if (old_i_persist_file.get())
178 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file);
179 break;
180 default:
181 NOTREACHED();
184 // Return false immediately upon failure to initialize shortcut interfaces.
185 if (!i_persist_file.get())
186 return false;
188 if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) &&
189 FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) {
190 return false;
193 if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) &&
194 FAILED(i_shell_link->SetWorkingDirectory(
195 properties.working_dir.value().c_str()))) {
196 return false;
199 if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) {
200 if (FAILED(i_shell_link->SetArguments(properties.arguments.c_str())))
201 return false;
202 } else if (old_i_persist_file.get()) {
203 wchar_t current_arguments[MAX_PATH] = {0};
204 if (SUCCEEDED(old_i_shell_link->GetArguments(current_arguments,
205 MAX_PATH))) {
206 i_shell_link->SetArguments(current_arguments);
210 if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) &&
211 FAILED(i_shell_link->SetDescription(properties.description.c_str()))) {
212 return false;
215 if ((properties.options & ShortcutProperties::PROPERTIES_ICON) &&
216 FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(),
217 properties.icon_index))) {
218 return false;
221 bool has_app_id =
222 (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0;
223 bool has_dual_mode =
224 (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0;
225 if ((has_app_id || has_dual_mode) &&
226 GetVersion() >= VERSION_WIN7) {
227 ScopedComPtr<IPropertyStore> property_store;
228 if (FAILED(property_store.QueryFrom(i_shell_link.get())) ||
229 !property_store.get())
230 return false;
232 if (has_app_id &&
233 !SetAppIdForPropertyStore(property_store.get(),
234 properties.app_id.c_str())) {
235 return false;
237 if (has_dual_mode &&
238 !SetBooleanValueForPropertyStore(property_store.get(),
239 PKEY_AppUserModel_IsDualMode,
240 properties.dual_mode)) {
241 return false;
245 // Release the interfaces to the old shortcut to make sure it doesn't prevent
246 // overwriting it if needed.
247 old_i_persist_file.Release();
248 old_i_shell_link.Release();
250 HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE);
252 // Release the interfaces in case the SHChangeNotify call below depends on
253 // the operations above being fully completed.
254 i_persist_file.Release();
255 i_shell_link.Release();
257 // If we successfully created/updated the icon, notify the shell that we have
258 // done so.
259 const bool succeeded = SUCCEEDED(result);
260 if (succeeded) {
261 if (shortcut_existed) {
262 // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing
263 // required.
264 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
265 } else {
266 SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcut_path.value().c_str(),
267 NULL);
271 return succeeded;
274 bool ResolveShortcutProperties(const FilePath& shortcut_path,
275 uint32 options,
276 ShortcutProperties* properties) {
277 DCHECK(options && properties);
278 base::ThreadRestrictions::AssertIOAllowed();
280 if (options & ~ShortcutProperties::PROPERTIES_ALL)
281 NOTREACHED() << "Unhandled property is used.";
283 ScopedComPtr<IShellLink> i_shell_link;
285 // Get pointer to the IShellLink interface.
286 if (FAILED(i_shell_link.CreateInstance(CLSID_ShellLink, NULL,
287 CLSCTX_INPROC_SERVER))) {
288 return false;
291 ScopedComPtr<IPersistFile> persist;
292 // Query IShellLink for the IPersistFile interface.
293 if (FAILED(persist.QueryFrom(i_shell_link.get())))
294 return false;
296 // Load the shell link.
297 if (FAILED(persist->Load(shortcut_path.value().c_str(), STGM_READ)))
298 return false;
300 // Reset |properties|.
301 properties->options = 0;
303 wchar_t temp[MAX_PATH];
304 if (options & ShortcutProperties::PROPERTIES_TARGET) {
305 if (FAILED(i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY)))
306 return false;
307 properties->set_target(FilePath(temp));
310 if (options & ShortcutProperties::PROPERTIES_WORKING_DIR) {
311 if (FAILED(i_shell_link->GetWorkingDirectory(temp, MAX_PATH)))
312 return false;
313 properties->set_working_dir(FilePath(temp));
316 if (options & ShortcutProperties::PROPERTIES_ARGUMENTS) {
317 if (FAILED(i_shell_link->GetArguments(temp, MAX_PATH)))
318 return false;
319 properties->set_arguments(temp);
322 if (options & ShortcutProperties::PROPERTIES_DESCRIPTION) {
323 // Note: description length constrained by MAX_PATH.
324 if (FAILED(i_shell_link->GetDescription(temp, MAX_PATH)))
325 return false;
326 properties->set_description(temp);
329 if (options & ShortcutProperties::PROPERTIES_ICON) {
330 int temp_index;
331 if (FAILED(i_shell_link->GetIconLocation(temp, MAX_PATH, &temp_index)))
332 return false;
333 properties->set_icon(FilePath(temp), temp_index);
336 // Windows 7+ options, avoiding unnecessary work.
337 if ((options & ShortcutProperties::PROPERTIES_WIN7) &&
338 GetVersion() >= VERSION_WIN7) {
339 ScopedComPtr<IPropertyStore> property_store;
340 if (FAILED(property_store.QueryFrom(i_shell_link.get())))
341 return false;
343 if (options & ShortcutProperties::PROPERTIES_APP_ID) {
344 ScopedPropVariant pv_app_id;
345 if (property_store->GetValue(PKEY_AppUserModel_ID,
346 pv_app_id.Receive()) != S_OK) {
347 return false;
349 switch (pv_app_id.get().vt) {
350 case VT_EMPTY:
351 properties->set_app_id(L"");
352 break;
353 case VT_LPWSTR:
354 properties->set_app_id(pv_app_id.get().pwszVal);
355 break;
356 default:
357 NOTREACHED() << "Unexpected variant type: " << pv_app_id.get().vt;
358 return false;
362 if (options & ShortcutProperties::PROPERTIES_DUAL_MODE) {
363 ScopedPropVariant pv_dual_mode;
364 if (property_store->GetValue(PKEY_AppUserModel_IsDualMode,
365 pv_dual_mode.Receive()) != S_OK) {
366 return false;
368 switch (pv_dual_mode.get().vt) {
369 case VT_EMPTY:
370 properties->set_dual_mode(false);
371 break;
372 case VT_BOOL:
373 properties->set_dual_mode(pv_dual_mode.get().boolVal == VARIANT_TRUE);
374 break;
375 default:
376 NOTREACHED() << "Unexpected variant type: " << pv_dual_mode.get().vt;
377 return false;
382 return true;
385 bool ResolveShortcut(const FilePath& shortcut_path,
386 FilePath* target_path,
387 string16* args) {
388 uint32 options = 0;
389 if (target_path)
390 options |= ShortcutProperties::PROPERTIES_TARGET;
391 if (args)
392 options |= ShortcutProperties::PROPERTIES_ARGUMENTS;
393 DCHECK(options);
395 ShortcutProperties properties;
396 if (!ResolveShortcutProperties(shortcut_path, options, &properties))
397 return false;
399 if (target_path)
400 *target_path = properties.target;
401 if (args)
402 *args = properties.arguments;
403 return true;
406 bool TaskbarPinShortcutLink(const FilePath& shortcut) {
407 base::ThreadRestrictions::AssertIOAllowed();
409 // "Pin to taskbar" is only supported after Win7.
410 if (GetVersion() < VERSION_WIN7)
411 return false;
413 return DoVerbOnFile(kPinToTaskbarID, shortcut);
416 bool TaskbarUnpinShortcutLink(const FilePath& shortcut) {
417 base::ThreadRestrictions::AssertIOAllowed();
419 // "Unpin from taskbar" is only supported after Win7.
420 if (GetVersion() < VERSION_WIN7)
421 return false;
423 return DoVerbOnFile(kUnpinFromTaskbarID, shortcut);
426 } // namespace win
427 } // namespace base