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