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/shortcut.h" 6 7#include <shellapi.h> 8#include <shlobj.h> 9#include <propkey.h> 10 11#include "base/files/file_util.h" 12#include "base/threading/thread_restrictions.h" 13#include "base/win/scoped_comptr.h" 14#include "base/win/scoped_propvariant.h" 15#include "base/win/win_util.h" 16#include "base/win/windows_version.h" 17 18namespace base { 19namespace win { 20 21namespace { 22 23// Initializes |i_shell_link| and |i_persist_file| (releasing them first if they 24// are already initialized). 25// If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|. 26// If any of the above steps fail, both |i_shell_link| and |i_persist_file| will 27// be released. 28void InitializeShortcutInterfaces( 29 const wchar_t* shortcut, 30 ScopedComPtr<IShellLink>* i_shell_link, 31 ScopedComPtr<IPersistFile>* i_persist_file) { 32 i_shell_link->Release(); 33 i_persist_file->Release(); 34 if (FAILED(i_shell_link->CreateInstance(CLSID_ShellLink, NULL, 35 CLSCTX_INPROC_SERVER)) || 36 FAILED(i_persist_file->QueryFrom(*i_shell_link)) || 37 (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) { 38 i_shell_link->Release(); 39 i_persist_file->Release(); 40 } 41} 42 43} // namespace 44 45bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path, 46 const ShortcutProperties& properties, 47 ShortcutOperation operation) { 48 base::ThreadRestrictions::AssertIOAllowed(); 49 50 // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING. 51 if (operation != SHORTCUT_UPDATE_EXISTING && 52 !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) { 53 NOTREACHED(); 54 return false; 55 } 56 57 bool shortcut_existed = PathExists(shortcut_path); 58 59 // Interfaces to the old shortcut when replacing an existing shortcut. 60 ScopedComPtr<IShellLink> old_i_shell_link; 61 ScopedComPtr<IPersistFile> old_i_persist_file; 62 63 // Interfaces to the shortcut being created/updated. 64 ScopedComPtr<IShellLink> i_shell_link; 65 ScopedComPtr<IPersistFile> i_persist_file; 66 switch (operation) { 67 case SHORTCUT_CREATE_ALWAYS: 68 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); 69 break; 70 case SHORTCUT_UPDATE_EXISTING: 71 InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link, 72 &i_persist_file); 73 break; 74 case SHORTCUT_REPLACE_EXISTING: 75 InitializeShortcutInterfaces(shortcut_path.value().c_str(), 76 &old_i_shell_link, &old_i_persist_file); 77 // Confirm |shortcut_path| exists and is a shortcut by verifying 78 // |old_i_persist_file| was successfully initialized in the call above. If 79 // so, initialize the interfaces to begin writing a new shortcut (to 80 // overwrite the current one if successful). 81 if (old_i_persist_file.get()) 82 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); 83 break; 84 default: 85 NOTREACHED(); 86 } 87 88 // Return false immediately upon failure to initialize shortcut interfaces. 89 if (!i_persist_file.get()) 90 return false; 91 92 if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) && 93 FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) { 94 return false; 95 } 96 97 if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) && 98 FAILED(i_shell_link->SetWorkingDirectory( 99 properties.working_dir.value().c_str()))) { 100 return false; 101 } 102 103 if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) { 104 if (FAILED(i_shell_link->SetArguments(properties.arguments.c_str()))) 105 return false; 106 } else if (old_i_persist_file.get()) { 107 wchar_t current_arguments[MAX_PATH] = {0}; 108 if (SUCCEEDED(old_i_shell_link->GetArguments(current_arguments, 109 MAX_PATH))) { 110 i_shell_link->SetArguments(current_arguments); 111 } 112 } 113 114 if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) && 115 FAILED(i_shell_link->SetDescription(properties.description.c_str()))) { 116 return false; 117 } 118 119 if ((properties.options & ShortcutProperties::PROPERTIES_ICON) && 120 FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(), 121 properties.icon_index))) { 122 return false; 123 } 124 125 bool has_app_id = 126 (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0; 127 bool has_dual_mode = 128 (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0; 129 if ((has_app_id || has_dual_mode) && 130 GetVersion() >= VERSION_WIN7) { 131 ScopedComPtr<IPropertyStore> property_store; 132 if (FAILED(property_store.QueryFrom(i_shell_link)) || !property_store.get()) 133 return false; 134 135 if (has_app_id && 136 !SetAppIdForPropertyStore(property_store, properties.app_id.c_str())) { 137 return false; 138 } 139 if (has_dual_mode && 140 !SetBooleanValueForPropertyStore(property_store, 141 PKEY_AppUserModel_IsDualMode, 142 properties.dual_mode)) { 143 return false; 144 } 145 } 146 147 // Release the interfaces to the old shortcut to make sure it doesn't prevent 148 // overwriting it if needed. 149 old_i_persist_file.Release(); 150 old_i_shell_link.Release(); 151 152 HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE); 153 154 // Release the interfaces in case the SHChangeNotify call below depends on 155 // the operations above being fully completed. 156 i_persist_file.Release(); 157 i_shell_link.Release(); 158 159 // If we successfully created/updated the icon, notify the shell that we have 160 // done so. 161 const bool succeeded = SUCCEEDED(result); 162 if (succeeded) { 163 if (shortcut_existed) { 164 // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing 165 // required. 166 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); 167 } else { 168 SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcut_path.value().c_str(), 169 NULL); 170 } 171 } 172 173 return succeeded; 174} 175 176bool ResolveShortcutProperties(const FilePath& shortcut_path, 177 uint32 options, 178 ShortcutProperties* properties) { 179 DCHECK(options && properties); 180 base::ThreadRestrictions::AssertIOAllowed(); 181 182 if (options & ~ShortcutProperties::PROPERTIES_ALL) 183 NOTREACHED() << "Unhandled property is used."; 184 185 ScopedComPtr<IShellLink> i_shell_link; 186 187 // Get pointer to the IShellLink interface. 188 if (FAILED(i_shell_link.CreateInstance(CLSID_ShellLink, NULL, 189 CLSCTX_INPROC_SERVER))) { 190 return false; 191 } 192 193 ScopedComPtr<IPersistFile> persist; 194 // Query IShellLink for the IPersistFile interface. 195 if (FAILED(persist.QueryFrom(i_shell_link))) 196 return false; 197 198 // Load the shell link. 199 if (FAILED(persist->Load(shortcut_path.value().c_str(), STGM_READ))) 200 return false; 201 202 // Reset |properties|. 203 properties->options = 0; 204 205 wchar_t temp[MAX_PATH]; 206 if (options & ShortcutProperties::PROPERTIES_TARGET) { 207 if (FAILED(i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY))) 208 return false; 209 properties->set_target(FilePath(temp)); 210 } 211 212 if (options & ShortcutProperties::PROPERTIES_WORKING_DIR) { 213 if (FAILED(i_shell_link->GetWorkingDirectory(temp, MAX_PATH))) 214 return false; 215 properties->set_working_dir(FilePath(temp)); 216 } 217 218 if (options & ShortcutProperties::PROPERTIES_ARGUMENTS) { 219 if (FAILED(i_shell_link->GetArguments(temp, MAX_PATH))) 220 return false; 221 properties->set_arguments(temp); 222 } 223 224 if (options & ShortcutProperties::PROPERTIES_DESCRIPTION) { 225 // Note: description length constrained by MAX_PATH. 226 if (FAILED(i_shell_link->GetDescription(temp, MAX_PATH))) 227 return false; 228 properties->set_description(temp); 229 } 230 231 if (options & ShortcutProperties::PROPERTIES_ICON) { 232 int temp_index; 233 if (FAILED(i_shell_link->GetIconLocation(temp, MAX_PATH, &temp_index))) 234 return false; 235 properties->set_icon(FilePath(temp), temp_index); 236 } 237 238 // Windows 7+ options, avoiding unnecessary work. 239 if ((options & ShortcutProperties::PROPERTIES_WIN7) && 240 GetVersion() >= VERSION_WIN7) { 241 ScopedComPtr<IPropertyStore> property_store; 242 if (FAILED(property_store.QueryFrom(i_shell_link))) 243 return false; 244 245 if (options & ShortcutProperties::PROPERTIES_APP_ID) { 246 ScopedPropVariant pv_app_id; 247 if (property_store->GetValue(PKEY_AppUserModel_ID, 248 pv_app_id.Receive()) != S_OK) { 249 return false; 250 } 251 switch (pv_app_id.get().vt) { 252 case VT_EMPTY: 253 properties->set_app_id(L""); 254 break; 255 case VT_LPWSTR: 256 properties->set_app_id(pv_app_id.get().pwszVal); 257 break; 258 default: 259 NOTREACHED() << "Unexpected variant type: " << pv_app_id.get().vt; 260 return false; 261 } 262 } 263 264 if (options & ShortcutProperties::PROPERTIES_DUAL_MODE) { 265 ScopedPropVariant pv_dual_mode; 266 if (property_store->GetValue(PKEY_AppUserModel_IsDualMode, 267 pv_dual_mode.Receive()) != S_OK) { 268 return false; 269 } 270 switch (pv_dual_mode.get().vt) { 271 case VT_EMPTY: 272 properties->set_dual_mode(false); 273 break; 274 case VT_BOOL: 275 properties->set_dual_mode(pv_dual_mode.get().boolVal == VARIANT_TRUE); 276 break; 277 default: 278 NOTREACHED() << "Unexpected variant type: " << pv_dual_mode.get().vt; 279 return false; 280 } 281 } 282 } 283 284 return true; 285} 286 287bool ResolveShortcut(const FilePath& shortcut_path, 288 FilePath* target_path, 289 string16* args) { 290 uint32 options = 0; 291 if (target_path) 292 options |= ShortcutProperties::PROPERTIES_TARGET; 293 if (args) 294 options |= ShortcutProperties::PROPERTIES_ARGUMENTS; 295 DCHECK(options); 296 297 ShortcutProperties properties; 298 if (!ResolveShortcutProperties(shortcut_path, options, &properties)) 299 return false; 300 301 if (target_path) 302 *target_path = properties.target; 303 if (args) 304 *args = properties.arguments; 305 return true; 306} 307 308bool TaskbarPinShortcutLink(const wchar_t* shortcut) { 309 base::ThreadRestrictions::AssertIOAllowed(); 310 311 // "Pin to taskbar" is only supported after Win7. 312 if (GetVersion() < VERSION_WIN7) 313 return false; 314 315 int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarpin", shortcut, 316 NULL, NULL, 0)); 317 return result > 32; 318} 319 320bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { 321 base::ThreadRestrictions::AssertIOAllowed(); 322 323 // "Unpin from taskbar" is only supported after Win7. 324 if (GetVersion() < VERSION_WIN7) 325 return false; 326 327 int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin", 328 shortcut, NULL, NULL, 0)); 329 return result > 32; 330} 331 332} // namespace win 333} // namespace base 334