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/win_util.h" 15 #include "base/win/windows_version.h" 16 17 namespace base { 18 namespace win { 19 20 namespace { 21 22 // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they 23 // are already initialized). 24 // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|. 25 // If any of the above steps fail, both |i_shell_link| and |i_persist_file| will 26 // be released. 27 void InitializeShortcutInterfaces( 28 const wchar_t* shortcut, 29 ScopedComPtr<IShellLink>* i_shell_link, 30 ScopedComPtr<IPersistFile>* i_persist_file) { 31 i_shell_link->Release(); 32 i_persist_file->Release(); 33 if (FAILED(i_shell_link->CreateInstance(CLSID_ShellLink, NULL, 34 CLSCTX_INPROC_SERVER)) || 35 FAILED(i_persist_file->QueryFrom(*i_shell_link)) || 36 (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) { 37 i_shell_link->Release(); 38 i_persist_file->Release(); 39 } 40 } 41 42 } // namespace 43 44 bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path, 45 const ShortcutProperties& properties, 46 ShortcutOperation operation) { 47 base::ThreadRestrictions::AssertIOAllowed(); 48 49 // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING. 50 if (operation != SHORTCUT_UPDATE_EXISTING && 51 !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) { 52 NOTREACHED(); 53 return false; 54 } 55 56 bool shortcut_existed = PathExists(shortcut_path); 57 58 // Interfaces to the old shortcut when replacing an existing shortcut. 59 ScopedComPtr<IShellLink> old_i_shell_link; 60 ScopedComPtr<IPersistFile> old_i_persist_file; 61 62 // Interfaces to the shortcut being created/updated. 63 ScopedComPtr<IShellLink> i_shell_link; 64 ScopedComPtr<IPersistFile> i_persist_file; 65 switch (operation) { 66 case SHORTCUT_CREATE_ALWAYS: 67 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); 68 break; 69 case SHORTCUT_UPDATE_EXISTING: 70 InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link, 71 &i_persist_file); 72 break; 73 case SHORTCUT_REPLACE_EXISTING: 74 InitializeShortcutInterfaces(shortcut_path.value().c_str(), 75 &old_i_shell_link, &old_i_persist_file); 76 // Confirm |shortcut_path| exists and is a shortcut by verifying 77 // |old_i_persist_file| was successfully initialized in the call above. If 78 // so, initialize the interfaces to begin writing a new shortcut (to 79 // overwrite the current one if successful). 80 if (old_i_persist_file.get()) 81 InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); 82 break; 83 default: 84 NOTREACHED(); 85 } 86 87 // Return false immediately upon failure to initialize shortcut interfaces. 88 if (!i_persist_file.get()) 89 return false; 90 91 if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) && 92 FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) { 93 return false; 94 } 95 96 if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) && 97 FAILED(i_shell_link->SetWorkingDirectory( 98 properties.working_dir.value().c_str()))) { 99 return false; 100 } 101 102 if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) { 103 if (FAILED(i_shell_link->SetArguments(properties.arguments.c_str()))) 104 return false; 105 } else if (old_i_persist_file.get()) { 106 wchar_t current_arguments[MAX_PATH] = {0}; 107 if (SUCCEEDED(old_i_shell_link->GetArguments(current_arguments, 108 MAX_PATH))) { 109 i_shell_link->SetArguments(current_arguments); 110 } 111 } 112 113 if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) && 114 FAILED(i_shell_link->SetDescription(properties.description.c_str()))) { 115 return false; 116 } 117 118 if ((properties.options & ShortcutProperties::PROPERTIES_ICON) && 119 FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(), 120 properties.icon_index))) { 121 return false; 122 } 123 124 bool has_app_id = 125 (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0; 126 bool has_dual_mode = 127 (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0; 128 if ((has_app_id || has_dual_mode) && 129 GetVersion() >= VERSION_WIN7) { 130 ScopedComPtr<IPropertyStore> property_store; 131 if (FAILED(property_store.QueryFrom(i_shell_link)) || !property_store.get()) 132 return false; 133 134 if (has_app_id && 135 !SetAppIdForPropertyStore(property_store, properties.app_id.c_str())) { 136 return false; 137 } 138 if (has_dual_mode && 139 !SetBooleanValueForPropertyStore(property_store, 140 PKEY_AppUserModel_IsDualMode, 141 properties.dual_mode)) { 142 return false; 143 } 144 } 145 146 // Release the interfaces to the old shortcut to make sure it doesn't prevent 147 // overwriting it if needed. 148 old_i_persist_file.Release(); 149 old_i_shell_link.Release(); 150 151 HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE); 152 153 // Release the interfaces in case the SHChangeNotify call below depends on 154 // the operations above being fully completed. 155 i_persist_file.Release(); 156 i_shell_link.Release(); 157 158 // If we successfully created/updated the icon, notify the shell that we have 159 // done so. 160 const bool succeeded = SUCCEEDED(result); 161 if (succeeded) { 162 if (shortcut_existed) { 163 // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing 164 // required. 165 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); 166 } else { 167 SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcut_path.value().c_str(), 168 NULL); 169 } 170 } 171 172 return succeeded; 173 } 174 175 bool ResolveShortcut(const FilePath& shortcut_path, 176 FilePath* target_path, 177 string16* args) { 178 base::ThreadRestrictions::AssertIOAllowed(); 179 180 HRESULT result; 181 ScopedComPtr<IShellLink> i_shell_link; 182 183 // Get pointer to the IShellLink interface. 184 result = i_shell_link.CreateInstance(CLSID_ShellLink, NULL, 185 CLSCTX_INPROC_SERVER); 186 if (FAILED(result)) 187 return false; 188 189 ScopedComPtr<IPersistFile> persist; 190 // Query IShellLink for the IPersistFile interface. 191 result = persist.QueryFrom(i_shell_link); 192 if (FAILED(result)) 193 return false; 194 195 // Load the shell link. 196 result = persist->Load(shortcut_path.value().c_str(), STGM_READ); 197 if (FAILED(result)) 198 return false; 199 200 WCHAR temp[MAX_PATH]; 201 if (target_path) { 202 // Try to find the target of a shortcut. 203 result = i_shell_link->Resolve(0, SLR_NO_UI | SLR_NOSEARCH); 204 if (FAILED(result)) 205 return false; 206 207 result = i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY); 208 if (FAILED(result)) 209 return false; 210 211 *target_path = FilePath(temp); 212 } 213 214 if (args) { 215 result = i_shell_link->GetArguments(temp, MAX_PATH); 216 if (FAILED(result)) 217 return false; 218 219 *args = string16(temp); 220 } 221 return true; 222 } 223 224 bool TaskbarPinShortcutLink(const wchar_t* shortcut) { 225 base::ThreadRestrictions::AssertIOAllowed(); 226 227 // "Pin to taskbar" is only supported after Win7. 228 if (GetVersion() < VERSION_WIN7) 229 return false; 230 231 int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarpin", shortcut, 232 NULL, NULL, 0)); 233 return result > 32; 234 } 235 236 bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { 237 base::ThreadRestrictions::AssertIOAllowed(); 238 239 // "Unpin from taskbar" is only supported after Win7. 240 if (base::win::GetVersion() < base::win::VERSION_WIN7) 241 return false; 242 243 int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin", 244 shortcut, NULL, NULL, 0)); 245 return result > 32; 246 } 247 248 } // namespace win 249 } // namespace base 250