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