1 // Copyright 2014 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 "chrome/browser/jumplist_updater_win.h" 6 7 #include <windows.h> 8 #include <propkey.h> 9 #include <shobjidl.h> 10 11 #include "base/command_line.h" 12 #include "base/files/file_path.h" 13 #include "base/path_service.h" 14 #include "base/win/win_util.h" 15 #include "base/win/windows_version.h" 16 #include "chrome/common/chrome_switches.h" 17 18 namespace { 19 20 // Creates an IShellLink object. 21 // An IShellLink object is almost the same as an application shortcut, and it 22 // requires three items: the absolute path to an application, an argument 23 // string, and a title string. 24 bool AddShellLink(base::win::ScopedComPtr<IObjectCollection> collection, 25 const std::wstring& application_path, 26 scoped_refptr<ShellLinkItem> item) { 27 // Create an IShellLink object. 28 base::win::ScopedComPtr<IShellLink> link; 29 HRESULT result = link.CreateInstance(CLSID_ShellLink, NULL, 30 CLSCTX_INPROC_SERVER); 31 if (FAILED(result)) 32 return false; 33 34 // Set the application path. 35 // We should exit this function when this call fails because it doesn't make 36 // any sense to add a shortcut that we cannot execute. 37 result = link->SetPath(application_path.c_str()); 38 if (FAILED(result)) 39 return false; 40 41 // Attach the command-line switches of this process before the given 42 // arguments and set it as the arguments of this IShellLink object. 43 // We also exit this function when this call fails because it isn't useful to 44 // add a shortcut that cannot open the given page. 45 std::wstring arguments(item->GetArguments()); 46 if (!arguments.empty()) { 47 result = link->SetArguments(arguments.c_str()); 48 if (FAILED(result)) 49 return false; 50 } 51 52 // Attach the given icon path to this IShellLink object. 53 // Since an icon is an optional item for an IShellLink object, so we don't 54 // have to exit even when it fails. 55 if (!item->icon_path().empty()) 56 link->SetIconLocation(item->icon_path().c_str(), item->icon_index()); 57 58 // Set the title of the IShellLink object. 59 // The IShellLink interface does not have any functions which update its 60 // title because this interface is originally for creating an application 61 // shortcut which doesn't have titles. 62 // So, we should use the IPropertyStore interface to set its title. 63 base::win::ScopedComPtr<IPropertyStore> property_store; 64 result = link.QueryInterface(property_store.Receive()); 65 if (FAILED(result)) 66 return false; 67 68 if (!base::win::SetStringValueForPropertyStore( 69 property_store.get(), 70 PKEY_Title, 71 item->title().c_str())) { 72 return false; 73 } 74 75 // Add this IShellLink object to the given collection. 76 return SUCCEEDED(collection->AddObject(link)); 77 } 78 79 } // namespace 80 81 82 // ShellLinkItem 83 84 ShellLinkItem::ShellLinkItem() 85 : command_line_(CommandLine::NO_PROGRAM), 86 icon_index_(0) { 87 } 88 89 ShellLinkItem::~ShellLinkItem() {} 90 91 std::wstring ShellLinkItem::GetArguments() const { 92 return command_line_.GetArgumentsString(); 93 } 94 95 CommandLine* ShellLinkItem::GetCommandLine() { 96 return &command_line_; 97 } 98 99 100 // JumpListUpdater 101 102 JumpListUpdater::JumpListUpdater(const std::wstring& app_user_model_id) 103 : app_user_model_id_(app_user_model_id), 104 user_max_items_(0) { 105 } 106 107 JumpListUpdater::~JumpListUpdater() { 108 } 109 110 // static 111 bool JumpListUpdater::IsEnabled() { 112 // JumpList is implemented only on Windows 7 or later. 113 // Do not create custom JumpLists in tests. See http://crbug.com/389375. 114 return base::win::GetVersion() >= base::win::VERSION_WIN7 && 115 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType); 116 } 117 118 bool JumpListUpdater::BeginUpdate() { 119 // This instance is expected to be one-time-use only. 120 DCHECK(!destination_list_.get()); 121 122 // Check preconditions. 123 if (!JumpListUpdater::IsEnabled() || app_user_model_id_.empty()) 124 return false; 125 126 // Create an ICustomDestinationList object and attach it to our application. 127 HRESULT result = destination_list_.CreateInstance(CLSID_DestinationList, NULL, 128 CLSCTX_INPROC_SERVER); 129 if (FAILED(result)) 130 return false; 131 132 // Set the App ID for this JumpList. 133 result = destination_list_->SetAppID(app_user_model_id_.c_str()); 134 if (FAILED(result)) 135 return false; 136 137 // Start a transaction that updates the JumpList of this application. 138 // This implementation just replaces the all items in this JumpList, so 139 // we don't have to use the IObjectArray object returned from this call. 140 // It seems Windows 7 RC (Build 7100) automatically checks the items in this 141 // removed list and prevent us from adding the same item. 142 UINT max_slots; 143 base::win::ScopedComPtr<IObjectArray> removed; 144 result = destination_list_->BeginList(&max_slots, __uuidof(*removed), 145 removed.ReceiveVoid()); 146 if (FAILED(result)) 147 return false; 148 149 user_max_items_ = max_slots; 150 151 return true; 152 } 153 154 bool JumpListUpdater::CommitUpdate() { 155 if (!destination_list_.get()) 156 return false; 157 158 // Commit this transaction and send the updated JumpList to Windows. 159 return SUCCEEDED(destination_list_->CommitList()); 160 } 161 162 bool JumpListUpdater::AddTasks(const ShellLinkItemList& link_items) { 163 if (!destination_list_.get()) 164 return false; 165 166 // Retrieve the absolute path to "chrome.exe". 167 base::FilePath application_path; 168 if (!PathService::Get(base::FILE_EXE, &application_path)) 169 return false; 170 171 // Create an EnumerableObjectCollection object to be added items of the 172 // "Task" category. 173 base::win::ScopedComPtr<IObjectCollection> collection; 174 HRESULT result = collection.CreateInstance(CLSID_EnumerableObjectCollection, 175 NULL, CLSCTX_INPROC_SERVER); 176 if (FAILED(result)) 177 return false; 178 179 // Add items to the "Task" category. 180 for (ShellLinkItemList::const_iterator it = link_items.begin(); 181 it != link_items.end(); ++it) { 182 AddShellLink(collection, application_path.value(), *it); 183 } 184 185 // We can now add the new list to the JumpList. 186 // ICustomDestinationList::AddUserTasks() also uses the IObjectArray 187 // interface to retrieve each item in the list. So, we retrieve the 188 // IObjectArray interface from the EnumerableObjectCollection object. 189 base::win::ScopedComPtr<IObjectArray> object_array; 190 result = collection.QueryInterface(object_array.Receive()); 191 if (FAILED(result)) 192 return false; 193 194 return SUCCEEDED(destination_list_->AddUserTasks(object_array)); 195 } 196 197 bool JumpListUpdater::AddCustomCategory(const std::wstring& category_name, 198 const ShellLinkItemList& link_items, 199 size_t max_items) { 200 if (!destination_list_.get()) 201 return false; 202 203 // Retrieve the absolute path to "chrome.exe". 204 base::FilePath application_path; 205 if (!PathService::Get(base::FILE_EXE, &application_path)) 206 return false; 207 208 // Exit this function when the given vector does not contain any items 209 // because an ICustomDestinationList::AppendCategory() call fails in this 210 // case. 211 if (link_items.empty() || !max_items) 212 return true; 213 214 // Create an EnumerableObjectCollection object. 215 // We once add the given items to this collection object and add this 216 // collection to the JumpList. 217 base::win::ScopedComPtr<IObjectCollection> collection; 218 HRESULT result = collection.CreateInstance(CLSID_EnumerableObjectCollection, 219 NULL, CLSCTX_INPROC_SERVER); 220 if (FAILED(result)) 221 return false; 222 223 for (ShellLinkItemList::const_iterator item = link_items.begin(); 224 item != link_items.end() && max_items > 0; ++item, --max_items) { 225 scoped_refptr<ShellLinkItem> link(*item); 226 AddShellLink(collection, application_path.value(), link); 227 } 228 229 // We can now add the new list to the JumpList. 230 // The ICustomDestinationList::AppendCategory() function needs the 231 // IObjectArray interface to retrieve each item in the list. So, we retrive 232 // the IObjectArray interface from the IEnumerableObjectCollection object 233 // and use it. 234 // It seems the ICustomDestinationList::AppendCategory() function just 235 // replaces all items in the given category with the ones in the new list. 236 base::win::ScopedComPtr<IObjectArray> object_array; 237 result = collection.QueryInterface(object_array.Receive()); 238 if (FAILED(result)) 239 return false; 240 241 return SUCCEEDED(destination_list_->AppendCategory(category_name.c_str(), 242 object_array)); 243 } 244