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 ShortcutProperties::ShortcutProperties()
46 : icon_index(-1), dual_mode(false), options(0U) {
49 ShortcutProperties::~ShortcutProperties() {
52 bool CreateOrUpdateShortcutLink(const FilePath
& shortcut_path
,
53 const ShortcutProperties
& properties
,
54 ShortcutOperation operation
) {
55 base::ThreadRestrictions::AssertIOAllowed();
57 // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING.
58 if (operation
!= SHORTCUT_UPDATE_EXISTING
&&
59 !(properties
.options
& ShortcutProperties::PROPERTIES_TARGET
)) {
64 bool shortcut_existed
= PathExists(shortcut_path
);
66 // Interfaces to the old shortcut when replacing an existing shortcut.
67 ScopedComPtr
<IShellLink
> old_i_shell_link
;
68 ScopedComPtr
<IPersistFile
> old_i_persist_file
;
70 // Interfaces to the shortcut being created/updated.
71 ScopedComPtr
<IShellLink
> i_shell_link
;
72 ScopedComPtr
<IPersistFile
> i_persist_file
;
74 case SHORTCUT_CREATE_ALWAYS
:
75 InitializeShortcutInterfaces(NULL
, &i_shell_link
, &i_persist_file
);
77 case SHORTCUT_UPDATE_EXISTING
:
78 InitializeShortcutInterfaces(shortcut_path
.value().c_str(), &i_shell_link
,
81 case SHORTCUT_REPLACE_EXISTING
:
82 InitializeShortcutInterfaces(shortcut_path
.value().c_str(),
83 &old_i_shell_link
, &old_i_persist_file
);
84 // Confirm |shortcut_path| exists and is a shortcut by verifying
85 // |old_i_persist_file| was successfully initialized in the call above. If
86 // so, initialize the interfaces to begin writing a new shortcut (to
87 // overwrite the current one if successful).
88 if (old_i_persist_file
.get())
89 InitializeShortcutInterfaces(NULL
, &i_shell_link
, &i_persist_file
);
95 // Return false immediately upon failure to initialize shortcut interfaces.
96 if (!i_persist_file
.get())
99 if ((properties
.options
& ShortcutProperties::PROPERTIES_TARGET
) &&
100 FAILED(i_shell_link
->SetPath(properties
.target
.value().c_str()))) {
104 if ((properties
.options
& ShortcutProperties::PROPERTIES_WORKING_DIR
) &&
105 FAILED(i_shell_link
->SetWorkingDirectory(
106 properties
.working_dir
.value().c_str()))) {
110 if (properties
.options
& ShortcutProperties::PROPERTIES_ARGUMENTS
) {
111 if (FAILED(i_shell_link
->SetArguments(properties
.arguments
.c_str())))
113 } else if (old_i_persist_file
.get()) {
114 wchar_t current_arguments
[MAX_PATH
] = {0};
115 if (SUCCEEDED(old_i_shell_link
->GetArguments(current_arguments
,
117 i_shell_link
->SetArguments(current_arguments
);
121 if ((properties
.options
& ShortcutProperties::PROPERTIES_DESCRIPTION
) &&
122 FAILED(i_shell_link
->SetDescription(properties
.description
.c_str()))) {
126 if ((properties
.options
& ShortcutProperties::PROPERTIES_ICON
) &&
127 FAILED(i_shell_link
->SetIconLocation(properties
.icon
.value().c_str(),
128 properties
.icon_index
))) {
133 (properties
.options
& ShortcutProperties::PROPERTIES_APP_ID
) != 0;
135 (properties
.options
& ShortcutProperties::PROPERTIES_DUAL_MODE
) != 0;
136 if ((has_app_id
|| has_dual_mode
) &&
137 GetVersion() >= VERSION_WIN7
) {
138 ScopedComPtr
<IPropertyStore
> property_store
;
139 if (FAILED(property_store
.QueryFrom(i_shell_link
.get())) ||
140 !property_store
.get())
144 !SetAppIdForPropertyStore(property_store
.get(),
145 properties
.app_id
.c_str())) {
149 !SetBooleanValueForPropertyStore(property_store
.get(),
150 PKEY_AppUserModel_IsDualMode
,
151 properties
.dual_mode
)) {
156 // Release the interfaces to the old shortcut to make sure it doesn't prevent
157 // overwriting it if needed.
158 old_i_persist_file
.Release();
159 old_i_shell_link
.Release();
161 HRESULT result
= i_persist_file
->Save(shortcut_path
.value().c_str(), TRUE
);
163 // Release the interfaces in case the SHChangeNotify call below depends on
164 // the operations above being fully completed.
165 i_persist_file
.Release();
166 i_shell_link
.Release();
168 // If we successfully created/updated the icon, notify the shell that we have
170 const bool succeeded
= SUCCEEDED(result
);
172 if (shortcut_existed
) {
173 // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing
175 SHChangeNotify(SHCNE_ASSOCCHANGED
, SHCNF_IDLIST
, NULL
, NULL
);
177 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATH
, shortcut_path
.value().c_str(),
185 bool ResolveShortcutProperties(const FilePath
& shortcut_path
,
187 ShortcutProperties
* properties
) {
188 DCHECK(options
&& properties
);
189 base::ThreadRestrictions::AssertIOAllowed();
191 if (options
& ~ShortcutProperties::PROPERTIES_ALL
)
192 NOTREACHED() << "Unhandled property is used.";
194 ScopedComPtr
<IShellLink
> i_shell_link
;
196 // Get pointer to the IShellLink interface.
197 if (FAILED(i_shell_link
.CreateInstance(CLSID_ShellLink
, NULL
,
198 CLSCTX_INPROC_SERVER
))) {
202 ScopedComPtr
<IPersistFile
> persist
;
203 // Query IShellLink for the IPersistFile interface.
204 if (FAILED(persist
.QueryFrom(i_shell_link
.get())))
207 // Load the shell link.
208 if (FAILED(persist
->Load(shortcut_path
.value().c_str(), STGM_READ
)))
211 // Reset |properties|.
212 properties
->options
= 0;
214 wchar_t temp
[MAX_PATH
];
215 if (options
& ShortcutProperties::PROPERTIES_TARGET
) {
216 if (FAILED(i_shell_link
->GetPath(temp
, MAX_PATH
, NULL
, SLGP_UNCPRIORITY
)))
218 properties
->set_target(FilePath(temp
));
221 if (options
& ShortcutProperties::PROPERTIES_WORKING_DIR
) {
222 if (FAILED(i_shell_link
->GetWorkingDirectory(temp
, MAX_PATH
)))
224 properties
->set_working_dir(FilePath(temp
));
227 if (options
& ShortcutProperties::PROPERTIES_ARGUMENTS
) {
228 if (FAILED(i_shell_link
->GetArguments(temp
, MAX_PATH
)))
230 properties
->set_arguments(temp
);
233 if (options
& ShortcutProperties::PROPERTIES_DESCRIPTION
) {
234 // Note: description length constrained by MAX_PATH.
235 if (FAILED(i_shell_link
->GetDescription(temp
, MAX_PATH
)))
237 properties
->set_description(temp
);
240 if (options
& ShortcutProperties::PROPERTIES_ICON
) {
242 if (FAILED(i_shell_link
->GetIconLocation(temp
, MAX_PATH
, &temp_index
)))
244 properties
->set_icon(FilePath(temp
), temp_index
);
247 // Windows 7+ options, avoiding unnecessary work.
248 if ((options
& ShortcutProperties::PROPERTIES_WIN7
) &&
249 GetVersion() >= VERSION_WIN7
) {
250 ScopedComPtr
<IPropertyStore
> property_store
;
251 if (FAILED(property_store
.QueryFrom(i_shell_link
.get())))
254 if (options
& ShortcutProperties::PROPERTIES_APP_ID
) {
255 ScopedPropVariant pv_app_id
;
256 if (property_store
->GetValue(PKEY_AppUserModel_ID
,
257 pv_app_id
.Receive()) != S_OK
) {
260 switch (pv_app_id
.get().vt
) {
262 properties
->set_app_id(L
"");
265 properties
->set_app_id(pv_app_id
.get().pwszVal
);
268 NOTREACHED() << "Unexpected variant type: " << pv_app_id
.get().vt
;
273 if (options
& ShortcutProperties::PROPERTIES_DUAL_MODE
) {
274 ScopedPropVariant pv_dual_mode
;
275 if (property_store
->GetValue(PKEY_AppUserModel_IsDualMode
,
276 pv_dual_mode
.Receive()) != S_OK
) {
279 switch (pv_dual_mode
.get().vt
) {
281 properties
->set_dual_mode(false);
284 properties
->set_dual_mode(pv_dual_mode
.get().boolVal
== VARIANT_TRUE
);
287 NOTREACHED() << "Unexpected variant type: " << pv_dual_mode
.get().vt
;
296 bool ResolveShortcut(const FilePath
& shortcut_path
,
297 FilePath
* target_path
,
301 options
|= ShortcutProperties::PROPERTIES_TARGET
;
303 options
|= ShortcutProperties::PROPERTIES_ARGUMENTS
;
306 ShortcutProperties properties
;
307 if (!ResolveShortcutProperties(shortcut_path
, options
, &properties
))
311 *target_path
= properties
.target
;
313 *args
= properties
.arguments
;
317 bool TaskbarPinShortcutLink(const wchar_t* shortcut
) {
318 base::ThreadRestrictions::AssertIOAllowed();
320 // "Pin to taskbar" is only supported after Win7.
321 if (GetVersion() < VERSION_WIN7
)
324 intptr_t result
= reinterpret_cast<intptr_t>(
325 ShellExecute(NULL
, L
"taskbarpin", shortcut
, NULL
, NULL
, 0));
329 bool TaskbarUnpinShortcutLink(const wchar_t* shortcut
) {
330 base::ThreadRestrictions::AssertIOAllowed();
332 // "Unpin from taskbar" is only supported after Win7.
333 if (GetVersion() < VERSION_WIN7
)
336 intptr_t result
= reinterpret_cast<intptr_t>(
337 ShellExecute(NULL
, L
"taskbarunpin", shortcut
, NULL
, NULL
, 0));