• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
InitializeShortcutInterfaces(const wchar_t * shortcut,ScopedComPtr<IShellLink> * i_shell_link,ScopedComPtr<IPersistFile> * i_persist_file)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 
CreateOrUpdateShortcutLink(const FilePath & shortcut_path,const ShortcutProperties & properties,ShortcutOperation operation)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 
ResolveShortcut(const FilePath & shortcut_path,FilePath * target_path,string16 * args)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 
TaskbarPinShortcutLink(const wchar_t * shortcut)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 
TaskbarUnpinShortcutLink(const wchar_t * shortcut)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