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.
5 #include "base/win/shortcut.h"
11 #include "base/files/file_util.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "base/win/scoped_comptr.h"
14 #include "base/win/scoped_propvariant.h"
15 #include "base/win/win_util.h"
16 #include "base/win/windows_version.h"
23 // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they
24 // are already initialized).
25 // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|.
26 // If any of the above steps fail, both |i_shell_link| and |i_persist_file| will
28 void InitializeShortcutInterfaces(
29 const wchar_t* shortcut
,
30 ScopedComPtr
<IShellLink
>* i_shell_link
,
31 ScopedComPtr
<IPersistFile
>* i_persist_file
) {
32 i_shell_link
->Release();
33 i_persist_file
->Release();
34 if (FAILED(i_shell_link
->CreateInstance(CLSID_ShellLink
, NULL
,
35 CLSCTX_INPROC_SERVER
)) ||
36 FAILED(i_persist_file
->QueryFrom(i_shell_link
->get())) ||
37 (shortcut
&& FAILED((*i_persist_file
)->Load(shortcut
, STGM_READWRITE
)))) {
38 i_shell_link
->Release();
39 i_persist_file
->Release();
45 bool CreateOrUpdateShortcutLink(const FilePath
& shortcut_path
,
46 const ShortcutProperties
& properties
,
47 ShortcutOperation operation
) {
48 base::ThreadRestrictions::AssertIOAllowed();
50 // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING.
51 if (operation
!= SHORTCUT_UPDATE_EXISTING
&&
52 !(properties
.options
& ShortcutProperties::PROPERTIES_TARGET
)) {
57 bool shortcut_existed
= PathExists(shortcut_path
);
59 // Interfaces to the old shortcut when replacing an existing shortcut.
60 ScopedComPtr
<IShellLink
> old_i_shell_link
;
61 ScopedComPtr
<IPersistFile
> old_i_persist_file
;
63 // Interfaces to the shortcut being created/updated.
64 ScopedComPtr
<IShellLink
> i_shell_link
;
65 ScopedComPtr
<IPersistFile
> i_persist_file
;
67 case SHORTCUT_CREATE_ALWAYS
:
68 InitializeShortcutInterfaces(NULL
, &i_shell_link
, &i_persist_file
);
70 case SHORTCUT_UPDATE_EXISTING
:
71 InitializeShortcutInterfaces(shortcut_path
.value().c_str(), &i_shell_link
,
74 case SHORTCUT_REPLACE_EXISTING
:
75 InitializeShortcutInterfaces(shortcut_path
.value().c_str(),
76 &old_i_shell_link
, &old_i_persist_file
);
77 // Confirm |shortcut_path| exists and is a shortcut by verifying
78 // |old_i_persist_file| was successfully initialized in the call above. If
79 // so, initialize the interfaces to begin writing a new shortcut (to
80 // overwrite the current one if successful).
81 if (old_i_persist_file
.get())
82 InitializeShortcutInterfaces(NULL
, &i_shell_link
, &i_persist_file
);
88 // Return false immediately upon failure to initialize shortcut interfaces.
89 if (!i_persist_file
.get())
92 if ((properties
.options
& ShortcutProperties::PROPERTIES_TARGET
) &&
93 FAILED(i_shell_link
->SetPath(properties
.target
.value().c_str()))) {
97 if ((properties
.options
& ShortcutProperties::PROPERTIES_WORKING_DIR
) &&
98 FAILED(i_shell_link
->SetWorkingDirectory(
99 properties
.working_dir
.value().c_str()))) {
103 if (properties
.options
& ShortcutProperties::PROPERTIES_ARGUMENTS
) {
104 if (FAILED(i_shell_link
->SetArguments(properties
.arguments
.c_str())))
106 } else if (old_i_persist_file
.get()) {
107 wchar_t current_arguments
[MAX_PATH
] = {0};
108 if (SUCCEEDED(old_i_shell_link
->GetArguments(current_arguments
,
110 i_shell_link
->SetArguments(current_arguments
);
114 if ((properties
.options
& ShortcutProperties::PROPERTIES_DESCRIPTION
) &&
115 FAILED(i_shell_link
->SetDescription(properties
.description
.c_str()))) {
119 if ((properties
.options
& ShortcutProperties::PROPERTIES_ICON
) &&
120 FAILED(i_shell_link
->SetIconLocation(properties
.icon
.value().c_str(),
121 properties
.icon_index
))) {
126 (properties
.options
& ShortcutProperties::PROPERTIES_APP_ID
) != 0;
128 (properties
.options
& ShortcutProperties::PROPERTIES_DUAL_MODE
) != 0;
129 if ((has_app_id
|| has_dual_mode
) &&
130 GetVersion() >= VERSION_WIN7
) {
131 ScopedComPtr
<IPropertyStore
> property_store
;
132 if (FAILED(property_store
.QueryFrom(i_shell_link
.get())) ||
133 !property_store
.get())
137 !SetAppIdForPropertyStore(property_store
.get(),
138 properties
.app_id
.c_str())) {
142 !SetBooleanValueForPropertyStore(property_store
.get(),
143 PKEY_AppUserModel_IsDualMode
,
144 properties
.dual_mode
)) {
149 // Release the interfaces to the old shortcut to make sure it doesn't prevent
150 // overwriting it if needed.
151 old_i_persist_file
.Release();
152 old_i_shell_link
.Release();
154 HRESULT result
= i_persist_file
->Save(shortcut_path
.value().c_str(), TRUE
);
156 // Release the interfaces in case the SHChangeNotify call below depends on
157 // the operations above being fully completed.
158 i_persist_file
.Release();
159 i_shell_link
.Release();
161 // If we successfully created/updated the icon, notify the shell that we have
163 const bool succeeded
= SUCCEEDED(result
);
165 if (shortcut_existed
) {
166 // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing
168 SHChangeNotify(SHCNE_ASSOCCHANGED
, SHCNF_IDLIST
, NULL
, NULL
);
170 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATH
, shortcut_path
.value().c_str(),
178 bool ResolveShortcutProperties(const FilePath
& shortcut_path
,
180 ShortcutProperties
* properties
) {
181 DCHECK(options
&& properties
);
182 base::ThreadRestrictions::AssertIOAllowed();
184 if (options
& ~ShortcutProperties::PROPERTIES_ALL
)
185 NOTREACHED() << "Unhandled property is used.";
187 ScopedComPtr
<IShellLink
> i_shell_link
;
189 // Get pointer to the IShellLink interface.
190 if (FAILED(i_shell_link
.CreateInstance(CLSID_ShellLink
, NULL
,
191 CLSCTX_INPROC_SERVER
))) {
195 ScopedComPtr
<IPersistFile
> persist
;
196 // Query IShellLink for the IPersistFile interface.
197 if (FAILED(persist
.QueryFrom(i_shell_link
.get())))
200 // Load the shell link.
201 if (FAILED(persist
->Load(shortcut_path
.value().c_str(), STGM_READ
)))
204 // Reset |properties|.
205 properties
->options
= 0;
207 wchar_t temp
[MAX_PATH
];
208 if (options
& ShortcutProperties::PROPERTIES_TARGET
) {
209 if (FAILED(i_shell_link
->GetPath(temp
, MAX_PATH
, NULL
, SLGP_UNCPRIORITY
)))
211 properties
->set_target(FilePath(temp
));
214 if (options
& ShortcutProperties::PROPERTIES_WORKING_DIR
) {
215 if (FAILED(i_shell_link
->GetWorkingDirectory(temp
, MAX_PATH
)))
217 properties
->set_working_dir(FilePath(temp
));
220 if (options
& ShortcutProperties::PROPERTIES_ARGUMENTS
) {
221 if (FAILED(i_shell_link
->GetArguments(temp
, MAX_PATH
)))
223 properties
->set_arguments(temp
);
226 if (options
& ShortcutProperties::PROPERTIES_DESCRIPTION
) {
227 // Note: description length constrained by MAX_PATH.
228 if (FAILED(i_shell_link
->GetDescription(temp
, MAX_PATH
)))
230 properties
->set_description(temp
);
233 if (options
& ShortcutProperties::PROPERTIES_ICON
) {
235 if (FAILED(i_shell_link
->GetIconLocation(temp
, MAX_PATH
, &temp_index
)))
237 properties
->set_icon(FilePath(temp
), temp_index
);
240 // Windows 7+ options, avoiding unnecessary work.
241 if ((options
& ShortcutProperties::PROPERTIES_WIN7
) &&
242 GetVersion() >= VERSION_WIN7
) {
243 ScopedComPtr
<IPropertyStore
> property_store
;
244 if (FAILED(property_store
.QueryFrom(i_shell_link
.get())))
247 if (options
& ShortcutProperties::PROPERTIES_APP_ID
) {
248 ScopedPropVariant pv_app_id
;
249 if (property_store
->GetValue(PKEY_AppUserModel_ID
,
250 pv_app_id
.Receive()) != S_OK
) {
253 switch (pv_app_id
.get().vt
) {
255 properties
->set_app_id(L
"");
258 properties
->set_app_id(pv_app_id
.get().pwszVal
);
261 NOTREACHED() << "Unexpected variant type: " << pv_app_id
.get().vt
;
266 if (options
& ShortcutProperties::PROPERTIES_DUAL_MODE
) {
267 ScopedPropVariant pv_dual_mode
;
268 if (property_store
->GetValue(PKEY_AppUserModel_IsDualMode
,
269 pv_dual_mode
.Receive()) != S_OK
) {
272 switch (pv_dual_mode
.get().vt
) {
274 properties
->set_dual_mode(false);
277 properties
->set_dual_mode(pv_dual_mode
.get().boolVal
== VARIANT_TRUE
);
280 NOTREACHED() << "Unexpected variant type: " << pv_dual_mode
.get().vt
;
289 bool ResolveShortcut(const FilePath
& shortcut_path
,
290 FilePath
* target_path
,
294 options
|= ShortcutProperties::PROPERTIES_TARGET
;
296 options
|= ShortcutProperties::PROPERTIES_ARGUMENTS
;
299 ShortcutProperties properties
;
300 if (!ResolveShortcutProperties(shortcut_path
, options
, &properties
))
304 *target_path
= properties
.target
;
306 *args
= properties
.arguments
;
310 bool TaskbarPinShortcutLink(const wchar_t* shortcut
) {
311 base::ThreadRestrictions::AssertIOAllowed();
313 // "Pin to taskbar" is only supported after Win7.
314 if (GetVersion() < VERSION_WIN7
)
317 int result
= reinterpret_cast<int>(ShellExecute(NULL
, L
"taskbarpin", shortcut
,
322 bool TaskbarUnpinShortcutLink(const wchar_t* shortcut
) {
323 base::ThreadRestrictions::AssertIOAllowed();
325 // "Unpin from taskbar" is only supported after Win7.
326 if (GetVersion() < VERSION_WIN7
)
329 int result
= reinterpret_cast<int>(ShellExecute(NULL
, L
"taskbarunpin",
330 shortcut
, NULL
, NULL
, 0));