Home | History | Annotate | Download | only in browser
      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