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
18namespace {
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.
24bool 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
84ShellLinkItem::ShellLinkItem()
85    : command_line_(CommandLine::NO_PROGRAM),
86      icon_index_(0) {
87}
88
89ShellLinkItem::~ShellLinkItem() {}
90
91std::wstring ShellLinkItem::GetArguments() const {
92  return command_line_.GetArgumentsString();
93}
94
95CommandLine* ShellLinkItem::GetCommandLine() {
96  return &command_line_;
97}
98
99
100// JumpListUpdater
101
102JumpListUpdater::JumpListUpdater(const std::wstring& app_user_model_id)
103    : app_user_model_id_(app_user_model_id),
104      user_max_items_(0) {
105}
106
107JumpListUpdater::~JumpListUpdater() {
108}
109
110// static
111bool 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
118bool 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
154bool 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
162bool 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
197bool 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