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/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 18 namespace base { 19 namespace win { 20 21 namespace { 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. 28 void 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 45 bool 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 176 bool 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 // Try to find the target of a shortcut. 208 if (FAILED(i_shell_link->Resolve(0, SLR_NO_UI | SLR_NOSEARCH))) 209 return false; 210 if (FAILED(i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY))) 211 return false; 212 properties->set_target(FilePath(temp)); 213 } 214 215 if (options & ShortcutProperties::PROPERTIES_WORKING_DIR) { 216 if (FAILED(i_shell_link->GetWorkingDirectory(temp, MAX_PATH))) 217 return false; 218 properties->set_working_dir(FilePath(temp)); 219 } 220 221 if (options & ShortcutProperties::PROPERTIES_ARGUMENTS) { 222 if (FAILED(i_shell_link->GetArguments(temp, MAX_PATH))) 223 return false; 224 properties->set_arguments(temp); 225 } 226 227 if (options & ShortcutProperties::PROPERTIES_DESCRIPTION) { 228 // Note: description length constrained by MAX_PATH. 229 if (FAILED(i_shell_link->GetDescription(temp, MAX_PATH))) 230 return false; 231 properties->set_description(temp); 232 } 233 234 if (options & ShortcutProperties::PROPERTIES_ICON) { 235 int temp_index; 236 if (FAILED(i_shell_link->GetIconLocation(temp, MAX_PATH, &temp_index))) 237 return false; 238 properties->set_icon(FilePath(temp), temp_index); 239 } 240 241 // Windows 7+ options, avoiding unnecessary work. 242 if ((options & ShortcutProperties::PROPERTIES_WIN7) && 243 GetVersion() >= VERSION_WIN7) { 244 ScopedComPtr<IPropertyStore> property_store; 245 if (FAILED(property_store.QueryFrom(i_shell_link))) 246 return false; 247 248 if (options & ShortcutProperties::PROPERTIES_APP_ID) { 249 ScopedPropVariant pv_app_id; 250 if (property_store->GetValue(PKEY_AppUserModel_ID, 251 pv_app_id.Receive()) != S_OK) { 252 return false; 253 } 254 switch (pv_app_id.get().vt) { 255 case VT_EMPTY: 256 properties->set_app_id(L""); 257 break; 258 case VT_LPWSTR: 259 properties->set_app_id(pv_app_id.get().pwszVal); 260 break; 261 default: 262 NOTREACHED() << "Unexpected variant type: " << pv_app_id.get().vt; 263 return false; 264 } 265 } 266 267 if (options & ShortcutProperties::PROPERTIES_DUAL_MODE) { 268 ScopedPropVariant pv_dual_mode; 269 if (property_store->GetValue(PKEY_AppUserModel_IsDualMode, 270 pv_dual_mode.Receive()) != S_OK) { 271 return false; 272 } 273 switch (pv_dual_mode.get().vt) { 274 case VT_EMPTY: 275 properties->set_dual_mode(false); 276 break; 277 case VT_BOOL: 278 properties->set_dual_mode(pv_dual_mode.get().boolVal == VARIANT_TRUE); 279 break; 280 default: 281 NOTREACHED() << "Unexpected variant type: " << pv_dual_mode.get().vt; 282 return false; 283 } 284 } 285 } 286 287 return true; 288 } 289 290 bool ResolveShortcut(const FilePath& shortcut_path, 291 FilePath* target_path, 292 string16* args) { 293 uint32 options = 0; 294 if (target_path) 295 options |= ShortcutProperties::PROPERTIES_TARGET; 296 if (args) 297 options |= ShortcutProperties::PROPERTIES_ARGUMENTS; 298 DCHECK(options); 299 300 ShortcutProperties properties; 301 if (!ResolveShortcutProperties(shortcut_path, options, &properties)) 302 return false; 303 304 if (target_path) 305 *target_path = properties.target; 306 if (args) 307 *args = properties.arguments; 308 return true; 309 } 310 311 bool TaskbarPinShortcutLink(const wchar_t* shortcut) { 312 base::ThreadRestrictions::AssertIOAllowed(); 313 314 // "Pin to taskbar" is only supported after Win7. 315 if (GetVersion() < VERSION_WIN7) 316 return false; 317 318 int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarpin", shortcut, 319 NULL, NULL, 0)); 320 return result > 32; 321 } 322 323 bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { 324 base::ThreadRestrictions::AssertIOAllowed(); 325 326 // "Unpin from taskbar" is only supported after Win7. 327 if (GetVersion() < VERSION_WIN7) 328 return false; 329 330 int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin", 331 shortcut, NULL, NULL, 0)); 332 return result > 32; 333 } 334 335 } // namespace win 336 } // namespace base 337