3 * Copyright 1997 Marcus Meissner
4 * Copyright 1998 Juergen Schmied
5 * Copyright 2005 Mike McCormack
6 * Copyright 2009 Andrew Hill
7 * Copyright 2013 Dominik Hornung
8 * Copyright 2017 Hermes Belusca-Maito
9 * Copyright 2018 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 * Nearly complete information about the binary formats
27 * of .lnk files available at http://www.wotsit.org
29 * You can use winedump to examine the contents of a link file:
32 * MSI advertised shortcuts are totally undocumented. They provide an
33 * icon for a program that is not yet installed, and invoke MSI to
34 * install the program when the shortcut is clicked on. They are
35 * created by passing a special string to SetPath, and the information
36 * in that string is parsed an stored.
39 * In the following is listed more documentation about the Shell Link file format,
40 * as well as its interface.
42 * General introduction about "Shell Links" (MSDN):
43 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb776891(v=vs.85).aspx
46 * Details of the file format:
48 * - Official MSDN documentation "[MS-SHLLINK]: Shell Link (.LNK) Binary File Format":
49 * https://msdn.microsoft.com/en-us/library/dd871305.aspx
52 * http://forensicswiki.org/wiki/LNK
53 * http://computerforensics.parsonage.co.uk/downloads/TheMeaningofLIFE.pdf
54 * https://ithreats.files.wordpress.com/2009/05/lnk_the_windows_shortcut_file_format.pdf
55 * https://github.com/libyal/liblnk/blob/master/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc
57 * - List of possible shell link header flags (SHELL_LINK_DATA_FLAGS enumeration):
58 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb762540(v=vs.85).aspx
59 * https://msdn.microsoft.com/en-us/library/dd891314.aspx
62 * In addition to storing its target by using a PIDL, a shell link file also
63 * stores metadata to make the shell able to track the link target, in situations
64 * where the link target is moved amongst local or network directories, or moved
65 * to different volumes. For this, two structures are used:
67 * - The first and oldest one (from NewShell/WinNT4) is the "LinkInfo" structure,
68 * stored in a serialized manner at the beginning of the shell link file:
69 * https://msdn.microsoft.com/en-us/library/dd871404.aspx
70 * The official API for manipulating this is located in LINKINFO.DLL .
72 * - The second, more recent one, is an extra binary block appended to the
73 * extra-data list of the shell link file: this is the "TrackerDataBlock":
74 * https://msdn.microsoft.com/en-us/library/dd891376.aspx
75 * Its purpose is for link tracking, and works in coordination with the
76 * "Distributed Link Tracking" service ('TrkWks' client, 'TrkSvr' server).
77 * See a detailed explanation at:
78 * http://www.serverwatch.com/tutorials/article.php/1476701/Searching-for-the-Missing-Link-Distributed-Link-Tracking.htm
81 * MSI installations most of the time create so-called "advertised shortcuts".
82 * They provide an icon for a program that may not be installed yet, and invoke
83 * MSI to install the program when the shortcut is opened (resolved).
84 * The philosophy of this approach is explained in detail inside the MSDN article
85 * "Application Resiliency: Unlock the Hidden Features of Windows Installer"
86 * (by Michael Sanford), here:
87 * https://msdn.microsoft.com/en-us/library/aa302344.aspx
89 * This functionality is implemented by adding a binary "Darwin" data block
90 * of type "EXP_DARWIN_LINK", signature EXP_DARWIN_ID_SIG == 0xA0000006,
91 * to the shell link file:
92 * https://msdn.microsoft.com/en-us/library/dd871369.aspx
93 * or, this could be done more simply by specifying a special link target path
94 * with the IShellLink::SetPath() function. Defining the following GUID:
95 * SHELL32_AdvtShortcutComponent = "::{9db1186e-40df-11d1-aa8c-00c04fb67863}:"
96 * setting a target of the form:
97 * "::{SHELL32_AdvtShortcutComponent}:<MSI_App_ID>"
98 * would automatically create the necessary binary block.
100 * With that, the target of the shortcut now becomes the MSI data. The latter
101 * is parsed from MSI and retrieved by the shell that then can run the program.
103 * This MSI functionality, dubbed "link blessing", actually originates from an
104 * older technology introduced in Internet Explorer 3 (and now obsolete since
105 * Internet Explorer 7), called "MS Internet Component Download (MSICD)", see
106 * this MSDN introductory article:
107 * https://msdn.microsoft.com/en-us/library/aa741198(v=vs.85).aspx
108 * and leveraged in Internet Explorer 4 with "Software Update Channels", see:
109 * https://msdn.microsoft.com/en-us/library/aa740931(v=vs.85).aspx
110 * Applications supporting this technology could present shell links having
111 * a special target, see subsection "Modifying the Shortcut" in the article:
112 * https://msdn.microsoft.com/en-us/library/aa741201(v=vs.85).aspx#pub_shor
114 * Similarly as for the MSI shortcuts, these MSICD shortcuts are created by
115 * specifying a special link target path with the IShellLink::SetPath() function,
116 * defining the following GUID:
117 * SHELL32_AdvtShortcutProduct = "::{9db1186f-40df-11d1-aa8c-00c04fb67863}:"
118 * and setting a target of the form:
119 * "::{SHELL32_AdvtShortcutProduct}:<AppName>::<Path>" .
120 * A tool, called "blesslnk.exe", was also provided for automatizing the process;
121 * its ReadMe can be found in the (now outdated) MS "Internet Client SDK" (INetSDK,
122 * for MS Windows 95 and NT), whose contents can be read at:
123 * http://www.msfn.org/board/topic/145352-new-windows-lnk-vulnerability/?page=4#comment-944223
124 * The MS INetSDK can be found at:
125 * https://web.archive.org/web/20100924000013/http://support.microsoft.com/kb/177877
127 * Internally the shell link target of these MSICD shortcuts is converted into
128 * a binary data block of a type similar to Darwin / "EXP_DARWIN_LINK", but with
129 * a different signature EXP_LOGO3_ID_SIG == 0xA0000007 . Such shell links are
130 * called "Logo3" shortcuts. They were evoked in this user comment in "The Old
132 * https://blogs.msdn.microsoft.com/oldnewthing/20121210-00/?p=5883#comment-1025083
134 * The shell exports the API 'SoftwareUpdateMessageBox' (in shdocvw.dll) that
135 * displays a message when an update for an application supporting this
136 * technology is available.
144 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
147 * Allows to define whether or not Windows-compatible behaviour
148 * should be adopted when setting and retrieving icon location paths.
149 * See CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon)
152 #define ICON_LINK_WINDOWS_COMPAT
154 #define SHLINK_LOCAL 0
155 #define SHLINK_REMOTE 1
157 /* link file formats */
159 #include "pshpack1.h"
167 DWORD dwLocalPathOfs
;
168 DWORD dwNetworkVolTableOfs
;
169 DWORD dwFinalPathOfs
;
172 struct LOCAL_VOLUME_INFO
184 WCHAR label
[12]; /* assume 8.3 */
189 /* IShellLink Implementation */
191 static HRESULT
ShellLink_UpdatePath(LPCWSTR sPathRel
, LPCWSTR path
, LPCWSTR sWorkDir
, LPWSTR
* psPath
);
193 /* strdup on the process heap */
194 static LPWSTR __inline
HEAP_strdupAtoW(HANDLE heap
, DWORD flags
, LPCSTR str
)
201 len
= MultiByteToWideChar(CP_ACP
, 0, str
, -1, NULL
, 0);
202 p
= (LPWSTR
)HeapAlloc(heap
, flags
, len
* sizeof(WCHAR
));
205 MultiByteToWideChar(CP_ACP
, 0, str
, -1, p
, len
);
209 static LPWSTR __inline
strdupW(LPCWSTR src
)
212 if (!src
) return NULL
;
213 dest
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, (wcslen(src
) + 1) * sizeof(WCHAR
));
219 // TODO: Use it for constructor & destructor too
220 VOID
CShellLink::Reset()
225 HeapFree(GetProcessHeap(), 0, m_sPath
);
227 ZeroMemory(&volume
, sizeof(volume
));
229 HeapFree(GetProcessHeap(), 0, m_sDescription
);
230 m_sDescription
= NULL
;
231 HeapFree(GetProcessHeap(), 0, m_sPathRel
);
233 HeapFree(GetProcessHeap(), 0, m_sWorkDir
);
235 HeapFree(GetProcessHeap(), 0, m_sArgs
);
237 HeapFree(GetProcessHeap(), 0, m_sIcoPath
);
244 SHFreeDataBlockList(m_pDBList
);
247 /**/sProduct
= sComponent
= NULL
;/**/
250 CShellLink::CShellLink()
252 m_Header
.dwSize
= sizeof(m_Header
);
253 m_Header
.clsid
= CLSID_ShellLink
;
254 m_Header
.dwFlags
= 0;
256 m_Header
.dwFileAttributes
= 0;
257 ZeroMemory(&m_Header
.ftCreationTime
, sizeof(m_Header
.ftCreationTime
));
258 ZeroMemory(&m_Header
.ftLastAccessTime
, sizeof(m_Header
.ftLastAccessTime
));
259 ZeroMemory(&m_Header
.ftLastWriteTime
, sizeof(m_Header
.ftLastWriteTime
));
260 m_Header
.nFileSizeLow
= 0;
262 m_Header
.nIconIndex
= 0;
263 m_Header
.nShowCommand
= SW_SHOWNORMAL
;
264 m_Header
.wHotKey
= 0;
269 ZeroMemory(&volume
, sizeof(volume
));
271 m_sDescription
= NULL
;
285 /**/sProduct
= sComponent
= NULL
;/**/
288 CShellLink::~CShellLink()
290 TRACE("-- destroying IShellLink(%p)\n", this);
294 HeapFree(GetProcessHeap(), 0, m_sPath
);
296 HeapFree(GetProcessHeap(), 0, m_sDescription
);
297 HeapFree(GetProcessHeap(), 0, m_sPathRel
);
298 HeapFree(GetProcessHeap(), 0, m_sWorkDir
);
299 HeapFree(GetProcessHeap(), 0, m_sArgs
);
300 HeapFree(GetProcessHeap(), 0, m_sIcoPath
);
301 HeapFree(GetProcessHeap(), 0, m_sLinkPath
);
302 SHFreeDataBlockList(m_pDBList
);
305 HRESULT STDMETHODCALLTYPE
CShellLink::GetClassID(CLSID
*pclsid
)
307 TRACE("%p %p\n", this, pclsid
);
311 *pclsid
= CLSID_ShellLink
;
315 /************************************************************************
316 * IPersistStream_IsDirty (IPersistStream)
318 HRESULT STDMETHODCALLTYPE
CShellLink::IsDirty()
320 TRACE("(%p)\n", this);
321 return (m_bDirty
? S_OK
: S_FALSE
);
324 HRESULT STDMETHODCALLTYPE
CShellLink::Load(LPCOLESTR pszFileName
, DWORD dwMode
)
326 TRACE("(%p, %s, %x)\n", this, debugstr_w(pszFileName
), dwMode
);
329 dwMode
= STGM_READ
| STGM_SHARE_DENY_WRITE
;
331 CComPtr
<IStream
> stm
;
332 HRESULT hr
= SHCreateStreamOnFileW(pszFileName
, dwMode
, &stm
);
335 HeapFree(GetProcessHeap(), 0, m_sLinkPath
);
336 m_sLinkPath
= strdupW(pszFileName
);
338 ShellLink_UpdatePath(m_sPathRel
, pszFileName
, m_sWorkDir
, &m_sPath
);
341 TRACE("-- returning hr %08x\n", hr
);
345 HRESULT STDMETHODCALLTYPE
CShellLink::Save(LPCOLESTR pszFileName
, BOOL fRemember
)
347 TRACE("(%p)->(%s)\n", this, debugstr_w(pszFileName
));
352 CComPtr
<IStream
> stm
;
353 HRESULT hr
= SHCreateStreamOnFileW(pszFileName
, STGM_READWRITE
| STGM_CREATE
| STGM_SHARE_EXCLUSIVE
, &stm
);
356 hr
= Save(stm
, FALSE
);
361 HeapFree(GetProcessHeap(), 0, m_sLinkPath
);
363 m_sLinkPath
= strdupW(pszFileName
);
368 DeleteFileW(pszFileName
);
369 WARN("Failed to create shortcut %s\n", debugstr_w(pszFileName
));
376 HRESULT STDMETHODCALLTYPE
CShellLink::SaveCompleted(LPCOLESTR pszFileName
)
378 FIXME("(%p)->(%s)\n", this, debugstr_w(pszFileName
));
382 HRESULT STDMETHODCALLTYPE
CShellLink::GetCurFile(LPOLESTR
*ppszFileName
)
384 *ppszFileName
= NULL
;
388 /* IPersistFile::GetCurFile called before IPersistFile::Save */
392 *ppszFileName
= (LPOLESTR
)CoTaskMemAlloc((wcslen(m_sLinkPath
) + 1) * sizeof(WCHAR
));
396 return E_OUTOFMEMORY
;
399 /* copy last saved filename */
400 wcscpy(*ppszFileName
, m_sLinkPath
);
405 static HRESULT
Stream_LoadString(IStream
* stm
, BOOL unicode
, LPWSTR
*pstr
)
411 HRESULT hr
= stm
->Read(&len
, sizeof(len
), &count
);
412 if (FAILED(hr
) || count
!= sizeof(len
))
416 len
*= sizeof(WCHAR
);
418 TRACE("reading %d\n", len
);
419 LPSTR temp
= (LPSTR
)HeapAlloc(GetProcessHeap(), 0, len
+ sizeof(WCHAR
));
421 return E_OUTOFMEMORY
;
423 hr
= stm
->Read(temp
, len
, &count
);
424 if (FAILED(hr
) || count
!= len
)
426 HeapFree(GetProcessHeap(), 0, temp
);
430 TRACE("read %s\n", debugstr_an(temp
, len
));
432 /* convert to unicode if necessary */
436 count
= MultiByteToWideChar(CP_ACP
, 0, temp
, len
, NULL
, 0);
437 str
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, (count
+ 1) * sizeof(WCHAR
));
440 HeapFree(GetProcessHeap(), 0, temp
);
441 return E_OUTOFMEMORY
;
443 MultiByteToWideChar(CP_ACP
, 0, temp
, len
, str
, count
);
444 HeapFree(GetProcessHeap(), 0, temp
);
448 count
/= sizeof(WCHAR
);
460 * NOTE: The following 5 functions are part of LINKINFO.DLL
462 static BOOL
ShellLink_GetVolumeInfo(LPCWSTR path
, CShellLink::volume_info
*volume
)
464 WCHAR drive
[4] = { path
[0], ':', '\\', 0 };
466 volume
->type
= GetDriveTypeW(drive
);
467 BOOL bRet
= GetVolumeInformationW(drive
, volume
->label
, _countof(volume
->label
), &volume
->serial
, NULL
, NULL
, NULL
, 0);
468 TRACE("ret = %d type %d serial %08x name %s\n", bRet
,
469 volume
->type
, volume
->serial
, debugstr_w(volume
->label
));
473 static HRESULT
Stream_ReadChunk(IStream
* stm
, LPVOID
*data
)
478 unsigned char data
[1];
485 HRESULT hr
= stm
->Read(&size
, sizeof(size
), &count
);
486 if (FAILED(hr
) || count
!= sizeof(size
))
489 chunk
= static_cast<sized_chunk
*>(HeapAlloc(GetProcessHeap(), 0, size
));
491 return E_OUTOFMEMORY
;
494 hr
= stm
->Read(chunk
->data
, size
- sizeof(size
), &count
);
495 if (FAILED(hr
) || count
!= (size
- sizeof(size
)))
497 HeapFree(GetProcessHeap(), 0, chunk
);
501 TRACE("Read %d bytes\n", chunk
->size
);
508 static BOOL
Stream_LoadVolume(LOCAL_VOLUME_INFO
*vol
, CShellLink::volume_info
*volume
)
510 volume
->serial
= vol
->dwVolSerial
;
511 volume
->type
= vol
->dwType
;
513 if (!vol
->dwVolLabelOfs
)
515 if (vol
->dwSize
<= vol
->dwVolLabelOfs
)
517 INT len
= vol
->dwSize
- vol
->dwVolLabelOfs
;
519 LPSTR label
= (LPSTR
)vol
;
520 label
+= vol
->dwVolLabelOfs
;
521 MultiByteToWideChar(CP_ACP
, 0, label
, len
, volume
->label
, _countof(volume
->label
));
526 static LPWSTR
Stream_LoadPath(LPCSTR p
, DWORD maxlen
)
530 while (len
< maxlen
&& p
[len
])
533 UINT wlen
= MultiByteToWideChar(CP_ACP
, 0, p
, len
, NULL
, 0);
534 LPWSTR path
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, (wlen
+ 1) * sizeof(WCHAR
));
537 MultiByteToWideChar(CP_ACP
, 0, p
, len
, path
, wlen
);
543 static HRESULT
Stream_LoadLocation(IStream
*stm
,
544 CShellLink::volume_info
*volume
, LPWSTR
*path
)
547 HRESULT hr
= Stream_ReadChunk(stm
, (LPVOID
*) &p
);
551 LOCATION_INFO
*loc
= reinterpret_cast<LOCATION_INFO
*>(p
);
552 if (loc
->dwTotalSize
< sizeof(LOCATION_INFO
))
554 HeapFree(GetProcessHeap(), 0, p
);
558 /* if there's valid local volume information, load it */
559 if (loc
->dwVolTableOfs
&&
560 ((loc
->dwVolTableOfs
+ sizeof(LOCAL_VOLUME_INFO
)) <= loc
->dwTotalSize
))
562 LOCAL_VOLUME_INFO
*volume_info
;
564 volume_info
= (LOCAL_VOLUME_INFO
*) &p
[loc
->dwVolTableOfs
];
565 Stream_LoadVolume(volume_info
, volume
);
568 /* if there's a local path, load it */
569 DWORD n
= loc
->dwLocalPathOfs
;
570 if (n
&& n
< loc
->dwTotalSize
)
571 *path
= Stream_LoadPath(&p
[n
], loc
->dwTotalSize
- n
);
573 TRACE("type %d serial %08x name %s path %s\n", volume
->type
,
574 volume
->serial
, debugstr_w(volume
->label
), debugstr_w(*path
));
576 HeapFree(GetProcessHeap(), 0, p
);
582 * The format of the advertised shortcut info is:
586 * 0 Length of the block (4 bytes, usually 0x314)
588 * 8 string data in ASCII
589 * 8+0x104 string data in UNICODE
591 * In the original Win32 implementation the buffers are not initialized
592 * to zero, so data trailing the string is random garbage.
594 HRESULT
CShellLink::GetAdvertiseInfo(LPWSTR
*str
, DWORD dwSig
)
596 LPEXP_DARWIN_LINK pInfo
;
600 pInfo
= (LPEXP_DARWIN_LINK
)SHFindDataBlock(m_pDBList
, dwSig
);
604 /* Make sure that the size of the structure is valid */
605 if (pInfo
->dbh
.cbSize
!= sizeof(*pInfo
))
607 ERR("Ooops. This structure is not as expected...\n");
611 TRACE("dwSig %08x string = '%s'\n", pInfo
->dbh
.dwSignature
, debugstr_w(pInfo
->szwDarwinID
));
613 *str
= pInfo
->szwDarwinID
;
617 /************************************************************************
618 * IPersistStream_Load (IPersistStream)
620 HRESULT STDMETHODCALLTYPE
CShellLink::Load(IStream
*stm
)
622 TRACE("%p %p\n", this, stm
);
625 return STG_E_INVALIDPOINTER
;
627 /* Free all the old stuff */
630 ULONG dwBytesRead
= 0;
631 HRESULT hr
= stm
->Read(&m_Header
, sizeof(m_Header
), &dwBytesRead
);
635 if (dwBytesRead
!= sizeof(m_Header
))
637 if (m_Header
.dwSize
!= sizeof(m_Header
))
639 if (!IsEqualIID(m_Header
.clsid
, CLSID_ShellLink
))
642 /* Load the new data in order */
646 SYSTEMTIME stCreationTime
;
647 SYSTEMTIME stLastAccessTime
;
648 SYSTEMTIME stLastWriteTime
;
649 WCHAR sTemp
[MAX_PATH
];
651 FileTimeToSystemTime(&m_Header
.ftCreationTime
, &stCreationTime
);
652 FileTimeToSystemTime(&m_Header
.ftLastAccessTime
, &stLastAccessTime
);
653 FileTimeToSystemTime(&m_Header
.ftLastWriteTime
, &stLastWriteTime
);
655 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &stCreationTime
,
656 NULL
, sTemp
, _countof(sTemp
));
657 TRACE("-- stCreationTime: %s\n", debugstr_w(sTemp
));
658 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &stLastAccessTime
,
659 NULL
, sTemp
, _countof(sTemp
));
660 TRACE("-- stLastAccessTime: %s\n", debugstr_w(sTemp
));
661 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &stLastWriteTime
,
662 NULL
, sTemp
, _countof(sTemp
));
663 TRACE("-- stLastWriteTime: %s\n", debugstr_w(sTemp
));
666 /* load all the new stuff */
667 if (m_Header
.dwFlags
& SLDF_HAS_ID_LIST
)
669 hr
= ILLoadFromStream(stm
, &m_pPidl
);
675 /* Load the location information... */
676 if (m_Header
.dwFlags
& SLDF_HAS_LINK_INFO
)
678 hr
= Stream_LoadLocation(stm
, &volume
, &m_sPath
);
682 /* ... but if it is required not to use it, clear it */
683 if (m_Header
.dwFlags
& SLDF_FORCE_NO_LINKINFO
)
685 HeapFree(GetProcessHeap(), 0, m_sPath
);
687 ZeroMemory(&volume
, sizeof(volume
));
690 BOOL unicode
= !!(m_Header
.dwFlags
& SLDF_UNICODE
);
692 if (m_Header
.dwFlags
& SLDF_HAS_NAME
)
694 hr
= Stream_LoadString(stm
, unicode
, &m_sDescription
);
697 TRACE("Description -> %s\n", debugstr_w(m_sDescription
));
700 if (m_Header
.dwFlags
& SLDF_HAS_RELPATH
)
702 hr
= Stream_LoadString(stm
, unicode
, &m_sPathRel
);
705 TRACE("Relative Path-> %s\n", debugstr_w(m_sPathRel
));
708 if (m_Header
.dwFlags
& SLDF_HAS_WORKINGDIR
)
710 hr
= Stream_LoadString(stm
, unicode
, &m_sWorkDir
);
713 PathRemoveBackslash(m_sWorkDir
);
714 TRACE("Working Dir -> %s\n", debugstr_w(m_sWorkDir
));
717 if (m_Header
.dwFlags
& SLDF_HAS_ARGS
)
719 hr
= Stream_LoadString(stm
, unicode
, &m_sArgs
);
722 TRACE("Arguments -> %s\n", debugstr_w(m_sArgs
));
725 if (m_Header
.dwFlags
& SLDF_HAS_ICONLOCATION
)
727 hr
= Stream_LoadString(stm
, unicode
, &m_sIcoPath
);
730 TRACE("Icon file -> %s\n", debugstr_w(m_sIcoPath
));
733 /* Now load the optional data block list */
734 hr
= SHReadDataBlockList(stm
, &m_pDBList
);
735 if (FAILED(hr
)) // FIXME: Should we fail?
740 #if (NTDDI_VERSION < NTDDI_LONGHORN)
741 if (m_Header
.dwFlags
& SLDF_HAS_LOGO3ID
)
743 hr
= GetAdvertiseInfo(&sProduct
, EXP_LOGO3_ID_SIG
);
745 TRACE("Product -> %s\n", debugstr_w(sProduct
));
748 if (m_Header
.dwFlags
& SLDF_HAS_DARWINID
)
750 hr
= GetAdvertiseInfo(&sComponent
, EXP_DARWIN_ID_SIG
);
752 TRACE("Component -> %s\n", debugstr_w(sComponent
));
756 if (m_Header
.dwFlags
& SLDF_RUNAS_USER
)
768 /************************************************************************
771 * Helper function for IPersistStream_Save. Writes a unicode string
772 * with terminating nul byte to a stream, preceded by the its length.
774 static HRESULT
Stream_WriteString(IStream
* stm
, LPCWSTR str
)
776 USHORT len
= wcslen(str
) + 1; // FIXME: Possible overflows?
779 HRESULT hr
= stm
->Write(&len
, sizeof(len
), &count
);
783 len
*= sizeof(WCHAR
);
785 hr
= stm
->Write(str
, len
, &count
);
792 /************************************************************************
793 * Stream_WriteLocationInfo
795 * Writes the location info to a stream
797 * FIXME: One day we might want to write the network volume information
798 * and the final path.
799 * Figure out how Windows deals with unicode paths here.
801 static HRESULT
Stream_WriteLocationInfo(IStream
* stm
, LPCWSTR path
,
802 CShellLink::volume_info
*volume
)
804 LOCAL_VOLUME_INFO
*vol
;
807 TRACE("%p %s %p\n", stm
, debugstr_w(path
), volume
);
809 /* figure out the size of everything */
810 DWORD label_size
= WideCharToMultiByte(CP_ACP
, 0, volume
->label
, -1,
811 NULL
, 0, NULL
, NULL
);
812 DWORD path_size
= WideCharToMultiByte(CP_ACP
, 0, path
, -1,
813 NULL
, 0, NULL
, NULL
);
814 DWORD volume_info_size
= sizeof(*vol
) + label_size
;
815 DWORD final_path_size
= 1;
816 DWORD total_size
= sizeof(*loc
) + volume_info_size
+ path_size
+ final_path_size
;
818 /* create pointers to everything */
819 loc
= static_cast<LOCATION_INFO
*>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, total_size
));
820 vol
= (LOCAL_VOLUME_INFO
*) &loc
[1];
821 LPSTR szLabel
= (LPSTR
) &vol
[1];
822 LPSTR szPath
= &szLabel
[label_size
];
823 LPSTR szFinalPath
= &szPath
[path_size
];
825 /* fill in the location information header */
826 loc
->dwTotalSize
= total_size
;
827 loc
->dwHeaderSize
= sizeof(*loc
);
829 loc
->dwVolTableOfs
= sizeof(*loc
);
830 loc
->dwLocalPathOfs
= sizeof(*loc
) + volume_info_size
;
831 loc
->dwNetworkVolTableOfs
= 0;
832 loc
->dwFinalPathOfs
= sizeof(*loc
) + volume_info_size
+ path_size
;
834 /* fill in the volume information */
835 vol
->dwSize
= volume_info_size
;
836 vol
->dwType
= volume
->type
;
837 vol
->dwVolSerial
= volume
->serial
;
838 vol
->dwVolLabelOfs
= sizeof(*vol
);
840 /* copy in the strings */
841 WideCharToMultiByte(CP_ACP
, 0, volume
->label
, -1,
842 szLabel
, label_size
, NULL
, NULL
);
843 WideCharToMultiByte(CP_ACP
, 0, path
, -1,
844 szPath
, path_size
, NULL
, NULL
);
848 HRESULT hr
= stm
->Write(loc
, total_size
, &count
);
849 HeapFree(GetProcessHeap(), 0, loc
);
854 /************************************************************************
855 * IPersistStream_Save (IPersistStream)
857 * FIXME: makes assumptions about byte order
859 HRESULT STDMETHODCALLTYPE
CShellLink::Save(IStream
*stm
, BOOL fClearDirty
)
861 TRACE("%p %p %x\n", this, stm
, fClearDirty
);
863 m_Header
.dwSize
= sizeof(m_Header
);
864 m_Header
.clsid
= CLSID_ShellLink
;
867 * Reset the flags: keep only the flags related to data blocks as they were
868 * already set in accordance by the different mutator member functions.
869 * The other flags will be determined now by the presence or absence of data.
871 m_Header
.dwFlags
&= (SLDF_RUN_WITH_SHIMLAYER
| SLDF_RUNAS_USER
|
872 SLDF_RUN_IN_SEPARATE
| SLDF_HAS_DARWINID
|
873 #if (NTDDI_VERSION < NTDDI_LONGHORN)
876 SLDF_HAS_EXP_ICON_SZ
| SLDF_HAS_EXP_SZ
);
877 // TODO: When we will support Vista+ functionality, add other flags to this list.
879 /* The stored strings are in UNICODE */
880 m_Header
.dwFlags
|= SLDF_UNICODE
;
883 m_Header
.dwFlags
|= SLDF_HAS_ID_LIST
;
885 m_Header
.dwFlags
|= SLDF_HAS_LINK_INFO
;
886 if (m_sDescription
&& *m_sDescription
)
887 m_Header
.dwFlags
|= SLDF_HAS_NAME
;
888 if (m_sPathRel
&& *m_sPathRel
)
889 m_Header
.dwFlags
|= SLDF_HAS_RELPATH
;
890 if (m_sWorkDir
&& *m_sWorkDir
)
891 m_Header
.dwFlags
|= SLDF_HAS_WORKINGDIR
;
892 if (m_sArgs
&& *m_sArgs
)
893 m_Header
.dwFlags
|= SLDF_HAS_ARGS
;
894 if (m_sIcoPath
&& *m_sIcoPath
)
895 m_Header
.dwFlags
|= SLDF_HAS_ICONLOCATION
;
897 m_Header
.dwFlags
|= SLDF_RUNAS_USER
;
899 /* Write the shortcut header */
901 HRESULT hr
= stm
->Write(&m_Header
, sizeof(m_Header
), &count
);
904 ERR("Write failed\n");
908 /* Save the data in order */
912 hr
= ILSaveToStream(stm
, m_pPidl
);
915 ERR("Failed to write PIDL\n");
922 hr
= Stream_WriteLocationInfo(stm
, m_sPath
, &volume
);
927 if (m_Header
.dwFlags
& SLDF_HAS_NAME
)
929 hr
= Stream_WriteString(stm
, m_sDescription
);
934 if (m_Header
.dwFlags
& SLDF_HAS_RELPATH
)
936 hr
= Stream_WriteString(stm
, m_sPathRel
);
941 if (m_Header
.dwFlags
& SLDF_HAS_WORKINGDIR
)
943 hr
= Stream_WriteString(stm
, m_sWorkDir
);
948 if (m_Header
.dwFlags
& SLDF_HAS_ARGS
)
950 hr
= Stream_WriteString(stm
, m_sArgs
);
955 if (m_Header
.dwFlags
& SLDF_HAS_ICONLOCATION
)
957 hr
= Stream_WriteString(stm
, m_sIcoPath
);
963 * Now save the data block list.
965 * NOTE that both advertised Product and Component are already saved
966 * inside Logo3 and Darwin data blocks in the m_pDBList list, and the
967 * m_Header.dwFlags is suitably initialized.
969 hr
= SHWriteDataBlockList(stm
, m_pDBList
);
973 /* Clear the dirty bit if requested */
980 /************************************************************************
981 * IPersistStream_GetSizeMax (IPersistStream)
983 HRESULT STDMETHODCALLTYPE
CShellLink::GetSizeMax(ULARGE_INTEGER
*pcbSize
)
985 TRACE("(%p)\n", this);
989 static BOOL
SHELL_ExistsFileW(LPCWSTR path
)
991 if (INVALID_FILE_ATTRIBUTES
== GetFileAttributesW(path
))
997 /**************************************************************************
998 * ShellLink_UpdatePath
999 * update absolute path in sPath using relative path in sPathRel
1001 static HRESULT
ShellLink_UpdatePath(LPCWSTR sPathRel
, LPCWSTR path
, LPCWSTR sWorkDir
, LPWSTR
* psPath
)
1003 if (!path
|| !psPath
)
1004 return E_INVALIDARG
;
1006 if (!*psPath
&& sPathRel
)
1008 WCHAR buffer
[2*MAX_PATH
], abs_path
[2*MAX_PATH
];
1009 LPWSTR final
= NULL
;
1011 /* first try if [directory of link file] + [relative path] finds an existing file */
1013 GetFullPathNameW(path
, MAX_PATH
* 2, buffer
, &final
);
1016 wcscpy(final
, sPathRel
);
1020 if (SHELL_ExistsFileW(buffer
))
1022 if (!GetFullPathNameW(buffer
, MAX_PATH
, abs_path
, &final
))
1023 wcscpy(abs_path
, buffer
);
1027 /* try if [working directory] + [relative path] finds an existing file */
1030 wcscpy(buffer
, sWorkDir
);
1031 wcscpy(PathAddBackslashW(buffer
), sPathRel
);
1033 if (SHELL_ExistsFileW(buffer
))
1034 if (!GetFullPathNameW(buffer
, MAX_PATH
, abs_path
, &final
))
1035 wcscpy(abs_path
, buffer
);
1039 /* FIXME: This is even not enough - not all shell links can be resolved using this algorithm. */
1041 wcscpy(abs_path
, sPathRel
);
1043 *psPath
= strdupW(abs_path
);
1045 return E_OUTOFMEMORY
;
1051 HRESULT STDMETHODCALLTYPE
CShellLink::GetPath(LPSTR pszFile
, INT cchMaxPath
, WIN32_FIND_DATAA
*pfd
, DWORD fFlags
)
1055 WIN32_FIND_DATAW wfd
;
1057 TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%u)(%s)\n",
1058 this, pszFile
, cchMaxPath
, pfd
, fFlags
, debugstr_w(m_sPath
));
1060 /* Allocate a temporary UNICODE buffer */
1061 pszFileW
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, cchMaxPath
* sizeof(WCHAR
));
1063 return E_OUTOFMEMORY
;
1065 /* Call the UNICODE function */
1066 hr
= GetPath(pszFileW
, cchMaxPath
, &wfd
, fFlags
);
1068 /* Convert the file path back to ANSI */
1069 WideCharToMultiByte(CP_ACP
, 0, pszFileW
, -1,
1070 pszFile
, cchMaxPath
, NULL
, NULL
);
1072 /* Free the temporary buffer */
1073 HeapFree(GetProcessHeap(), 0, pszFileW
);
1077 ZeroMemory(pfd
, sizeof(*pfd
));
1079 /* Copy the file data if a file path was returned */
1084 /* Copy the fixed part */
1085 CopyMemory(pfd
, &wfd
, FIELD_OFFSET(WIN32_FIND_DATAA
, cFileName
));
1087 /* Convert the file names to ANSI */
1088 len
= lstrlenW(wfd
.cFileName
);
1089 WideCharToMultiByte(CP_ACP
, 0, wfd
.cFileName
, len
+ 1,
1090 pfd
->cFileName
, sizeof(pfd
->cFileName
), NULL
, NULL
);
1091 len
= lstrlenW(wfd
.cAlternateFileName
);
1092 WideCharToMultiByte(CP_ACP
, 0, wfd
.cAlternateFileName
, len
+ 1,
1093 pfd
->cAlternateFileName
, sizeof(pfd
->cAlternateFileName
), NULL
, NULL
);
1100 HRESULT STDMETHODCALLTYPE
CShellLink::GetIDList(LPITEMIDLIST
*ppidl
)
1102 TRACE("(%p)->(ppidl=%p)\n", this, ppidl
);
1110 *ppidl
= ILClone(m_pPidl
);
1114 HRESULT STDMETHODCALLTYPE
CShellLink::SetIDList(LPCITEMIDLIST pidl
)
1116 TRACE("(%p)->(pidl=%p)\n", this, pidl
);
1117 return SetTargetFromPIDLOrPath(pidl
, NULL
);
1120 HRESULT STDMETHODCALLTYPE
CShellLink::GetDescription(LPSTR pszName
, INT cchMaxName
)
1122 TRACE("(%p)->(%p len=%u)\n", this, pszName
, cchMaxName
);
1128 WideCharToMultiByte(CP_ACP
, 0, m_sDescription
, -1,
1129 pszName
, cchMaxName
, NULL
, NULL
);
1134 HRESULT STDMETHODCALLTYPE
CShellLink::SetDescription(LPCSTR pszName
)
1136 TRACE("(%p)->(pName=%s)\n", this, pszName
);
1138 HeapFree(GetProcessHeap(), 0, m_sDescription
);
1139 m_sDescription
= NULL
;
1143 m_sDescription
= HEAP_strdupAtoW(GetProcessHeap(), 0, pszName
);
1144 if (!m_sDescription
)
1145 return E_OUTOFMEMORY
;
1152 HRESULT STDMETHODCALLTYPE
CShellLink::GetWorkingDirectory(LPSTR pszDir
, INT cchMaxPath
)
1154 TRACE("(%p)->(%p len=%u)\n", this, pszDir
, cchMaxPath
);
1160 WideCharToMultiByte(CP_ACP
, 0, m_sWorkDir
, -1,
1161 pszDir
, cchMaxPath
, NULL
, NULL
);
1166 HRESULT STDMETHODCALLTYPE
CShellLink::SetWorkingDirectory(LPCSTR pszDir
)
1168 TRACE("(%p)->(dir=%s)\n", this, pszDir
);
1170 HeapFree(GetProcessHeap(), 0, m_sWorkDir
);
1175 m_sWorkDir
= HEAP_strdupAtoW(GetProcessHeap(), 0, pszDir
);
1177 return E_OUTOFMEMORY
;
1184 HRESULT STDMETHODCALLTYPE
CShellLink::GetArguments(LPSTR pszArgs
, INT cchMaxPath
)
1186 TRACE("(%p)->(%p len=%u)\n", this, pszArgs
, cchMaxPath
);
1192 WideCharToMultiByte(CP_ACP
, 0, m_sArgs
, -1,
1193 pszArgs
, cchMaxPath
, NULL
, NULL
);
1198 HRESULT STDMETHODCALLTYPE
CShellLink::SetArguments(LPCSTR pszArgs
)
1200 TRACE("(%p)->(args=%s)\n", this, pszArgs
);
1202 HeapFree(GetProcessHeap(), 0, m_sArgs
);
1207 m_sArgs
= HEAP_strdupAtoW(GetProcessHeap(), 0, pszArgs
);
1209 return E_OUTOFMEMORY
;
1216 HRESULT STDMETHODCALLTYPE
CShellLink::GetHotkey(WORD
*pwHotkey
)
1218 TRACE("(%p)->(%p)(0x%08x)\n", this, pwHotkey
, m_Header
.wHotKey
);
1219 *pwHotkey
= m_Header
.wHotKey
;
1223 HRESULT STDMETHODCALLTYPE
CShellLink::SetHotkey(WORD wHotkey
)
1225 TRACE("(%p)->(hotkey=%x)\n", this, wHotkey
);
1227 m_Header
.wHotKey
= wHotkey
;
1233 HRESULT STDMETHODCALLTYPE
CShellLink::GetShowCmd(INT
*piShowCmd
)
1235 TRACE("(%p)->(%p) %d\n", this, piShowCmd
, m_Header
.nShowCommand
);
1236 *piShowCmd
= m_Header
.nShowCommand
;
1240 HRESULT STDMETHODCALLTYPE
CShellLink::SetShowCmd(INT iShowCmd
)
1242 TRACE("(%p) %d\n", this, iShowCmd
);
1244 m_Header
.nShowCommand
= iShowCmd
;
1250 HRESULT STDMETHODCALLTYPE
CShellLink::GetIconLocation(LPSTR pszIconPath
, INT cchIconPath
, INT
*piIcon
)
1253 LPWSTR pszIconPathW
;
1255 TRACE("(%p)->(%p len=%u iicon=%p)\n", this, pszIconPath
, cchIconPath
, piIcon
);
1257 /* Allocate a temporary UNICODE buffer */
1258 pszIconPathW
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, cchIconPath
* sizeof(WCHAR
));
1260 return E_OUTOFMEMORY
;
1262 /* Call the UNICODE function */
1263 hr
= GetIconLocation(pszIconPathW
, cchIconPath
, piIcon
);
1265 /* Convert the file path back to ANSI */
1266 WideCharToMultiByte(CP_ACP
, 0, pszIconPathW
, -1,
1267 pszIconPath
, cchIconPath
, NULL
, NULL
);
1269 /* Free the temporary buffer */
1270 HeapFree(GetProcessHeap(), 0, pszIconPathW
);
1275 HRESULT STDMETHODCALLTYPE
CShellLink::GetIconLocation(UINT uFlags
, PSTR pszIconFile
, UINT cchMax
, int *piIndex
, UINT
*pwFlags
)
1278 LPWSTR pszIconFileW
;
1280 TRACE("(%p)->(%u %p len=%u piIndex=%p pwFlags=%p)\n", this, uFlags
, pszIconFile
, cchMax
, piIndex
, pwFlags
);
1282 /* Allocate a temporary UNICODE buffer */
1283 pszIconFileW
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, cchMax
* sizeof(WCHAR
));
1285 return E_OUTOFMEMORY
;
1287 /* Call the UNICODE function */
1288 hr
= GetIconLocation(uFlags
, pszIconFileW
, cchMax
, piIndex
, pwFlags
);
1290 /* Convert the file path back to ANSI */
1291 WideCharToMultiByte(CP_ACP
, 0, pszIconFileW
, -1,
1292 pszIconFile
, cchMax
, NULL
, NULL
);
1294 /* Free the temporary buffer */
1295 HeapFree(GetProcessHeap(), 0, pszIconFileW
);
1300 HRESULT STDMETHODCALLTYPE
CShellLink::Extract(PCSTR pszFile
, UINT nIconIndex
, HICON
*phiconLarge
, HICON
*phiconSmall
, UINT nIconSize
)
1302 TRACE("(%p)->(path=%s iicon=%u)\n", this, pszFile
, nIconIndex
);
1307 str
= HEAP_strdupAtoW(GetProcessHeap(), 0, pszFile
);
1309 return E_OUTOFMEMORY
;
1312 HRESULT hr
= Extract(str
, nIconIndex
, phiconLarge
, phiconSmall
, nIconSize
);
1315 HeapFree(GetProcessHeap(), 0, str
);
1320 HRESULT STDMETHODCALLTYPE
CShellLink::SetIconLocation(LPCSTR pszIconPath
, INT iIcon
)
1322 TRACE("(%p)->(path=%s iicon=%u)\n", this, pszIconPath
, iIcon
);
1327 str
= HEAP_strdupAtoW(GetProcessHeap(), 0, pszIconPath
);
1329 return E_OUTOFMEMORY
;
1332 HRESULT hr
= SetIconLocation(str
, iIcon
);
1335 HeapFree(GetProcessHeap(), 0, str
);
1340 HRESULT STDMETHODCALLTYPE
CShellLink::SetRelativePath(LPCSTR pszPathRel
, DWORD dwReserved
)
1342 TRACE("(%p)->(path=%s %x)\n", this, pszPathRel
, dwReserved
);
1344 HeapFree(GetProcessHeap(), 0, m_sPathRel
);
1349 m_sPathRel
= HEAP_strdupAtoW(GetProcessHeap(), 0, pszPathRel
);
1353 return ShellLink_UpdatePath(m_sPathRel
, m_sPath
, m_sWorkDir
, &m_sPath
);
1357 shelllink_get_msi_component_path(LPWSTR component
)
1359 DWORD Result
, sz
= 0;
1361 Result
= CommandLineFromMsiDescriptor(component
, NULL
, &sz
);
1362 if (Result
!= ERROR_SUCCESS
)
1366 LPWSTR path
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, sz
* sizeof(WCHAR
));
1367 Result
= CommandLineFromMsiDescriptor(component
, path
, &sz
);
1368 if (Result
!= ERROR_SUCCESS
)
1370 HeapFree(GetProcessHeap(), 0, path
);
1374 TRACE("returning %s\n", debugstr_w(path
));
1379 HRESULT STDMETHODCALLTYPE
CShellLink::Resolve(HWND hwnd
, DWORD fFlags
)
1384 TRACE("(%p)->(hwnd=%p flags=%x)\n", this, hwnd
, fFlags
);
1386 /* FIXME: use IResolveShellLink interface? */
1388 // FIXME: See InvokeCommand().
1390 #if (NTDDI_VERSION < NTDDI_LONGHORN)
1391 // NOTE: For Logo3 (EXP_LOGO3_ID_SIG), check also for SHRestricted(REST_NOLOGO3CHANNELNOTIFY)
1392 if (m_Header
.dwFlags
& SLDF_HAS_LOGO3ID
)
1394 FIXME("Logo3 links are not supported yet!\n");
1399 /* Resolve Darwin (MSI) target */
1400 if (m_Header
.dwFlags
& SLDF_HAS_DARWINID
)
1402 LPWSTR component
= NULL
;
1403 hr
= GetAdvertiseInfo(&component
, EXP_DARWIN_ID_SIG
);
1407 /* Clear the cached path */
1408 HeapFree(GetProcessHeap(), 0, m_sPath
);
1409 m_sPath
= shelllink_get_msi_component_path(component
);
1414 if (!m_sPath
&& m_pPidl
)
1416 WCHAR buffer
[MAX_PATH
];
1418 bSuccess
= SHGetPathFromIDListW(m_pPidl
, buffer
);
1419 if (bSuccess
&& *buffer
)
1421 m_sPath
= strdupW(buffer
);
1423 return E_OUTOFMEMORY
;
1429 hr
= S_OK
; /* don't report an error occurred while just caching information */
1433 // FIXME: Strange to do that here...
1434 if (!m_sIcoPath
&& m_sPath
)
1436 m_sIcoPath
= strdupW(m_sPath
);
1438 return E_OUTOFMEMORY
;
1440 m_Header
.nIconIndex
= 0;
1448 HRESULT STDMETHODCALLTYPE
CShellLink::SetPath(LPCSTR pszFile
)
1450 TRACE("(%p)->(path=%s)\n", this, pszFile
);
1453 return E_INVALIDARG
;
1455 LPWSTR str
= HEAP_strdupAtoW(GetProcessHeap(), 0, pszFile
);
1457 return E_OUTOFMEMORY
;
1459 HRESULT hr
= SetPath(str
);
1460 HeapFree(GetProcessHeap(), 0, str
);
1465 HRESULT STDMETHODCALLTYPE
CShellLink::GetPath(LPWSTR pszFile
, INT cchMaxPath
, WIN32_FIND_DATAW
*pfd
, DWORD fFlags
)
1467 WCHAR buffer
[MAX_PATH
];
1469 TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%u)(%s)\n",
1470 this, pszFile
, cchMaxPath
, pfd
, fFlags
, debugstr_w(m_sPath
));
1474 // FIXME: What if cchMaxPath == 0 , or pszFile == NULL ??
1476 // FIXME: What about Darwin??
1479 * Retrieve the path to the target from the PIDL (if we have one).
1480 * NOTE: Do NOT use the cached path (m_sPath from link info).
1482 if (m_pPidl
&& SHGetPathFromIDListW(m_pPidl
, buffer
))
1484 if (fFlags
& SLGP_SHORTPATH
)
1485 GetShortPathNameW(buffer
, buffer
, _countof(buffer
));
1486 // FIXME: Add support for SLGP_UNCPRIORITY
1493 /* If we have a FindData structure, initialize it */
1496 ZeroMemory(pfd
, sizeof(*pfd
));
1498 /* Copy the file data if the target is a file path */
1501 pfd
->dwFileAttributes
= m_Header
.dwFileAttributes
;
1502 pfd
->ftCreationTime
= m_Header
.ftCreationTime
;
1503 pfd
->ftLastAccessTime
= m_Header
.ftLastAccessTime
;
1504 pfd
->ftLastWriteTime
= m_Header
.ftLastWriteTime
;
1505 pfd
->nFileSizeHigh
= 0;
1506 pfd
->nFileSizeLow
= m_Header
.nFileSizeLow
;
1509 * Build temporarily a short path in pfd->cFileName (of size MAX_PATH),
1510 * then extract and store the short file name in pfd->cAlternateFileName.
1512 GetShortPathNameW(buffer
, pfd
->cFileName
, _countof(pfd
->cFileName
));
1513 lstrcpynW(pfd
->cAlternateFileName
,
1514 PathFindFileNameW(pfd
->cFileName
),
1515 _countof(pfd
->cAlternateFileName
));
1517 /* Now extract and store the long file name in pfd->cFileName */
1518 lstrcpynW(pfd
->cFileName
,
1519 PathFindFileNameW(buffer
),
1520 _countof(pfd
->cFileName
));
1524 /* Finally check if we have a raw path the user actually wants to retrieve */
1525 if ((fFlags
& SLGP_RAWPATH
) && (m_Header
.dwFlags
& SLDF_HAS_EXP_SZ
))
1527 /* Search for a target environment block */
1528 LPEXP_SZ_LINK pInfo
;
1529 pInfo
= (LPEXP_SZ_LINK
)SHFindDataBlock(m_pDBList
, EXP_SZ_LINK_SIG
);
1530 if (pInfo
&& (pInfo
->cbSize
== sizeof(*pInfo
)))
1531 lstrcpynW(buffer
, pInfo
->szwTarget
, cchMaxPath
);
1534 /* For diagnostics purposes only... */
1535 // NOTE: SLGP_UNCPRIORITY is unsupported
1536 fFlags
&= ~(SLGP_RAWPATH
| SLGP_SHORTPATH
);
1537 if (fFlags
) FIXME("(%p): Unsupported flags %lu\n", this, fFlags
);
1539 /* Copy the data back to the user */
1541 lstrcpynW(pszFile
, buffer
, cchMaxPath
);
1543 return (*buffer
? S_OK
: S_FALSE
);
1546 HRESULT STDMETHODCALLTYPE
CShellLink::GetDescription(LPWSTR pszName
, INT cchMaxName
)
1548 TRACE("(%p)->(%p len=%u)\n", this, pszName
, cchMaxName
);
1552 lstrcpynW(pszName
, m_sDescription
, cchMaxName
);
1557 HRESULT STDMETHODCALLTYPE
CShellLink::SetDescription(LPCWSTR pszName
)
1559 TRACE("(%p)->(desc=%s)\n", this, debugstr_w(pszName
));
1561 HeapFree(GetProcessHeap(), 0, m_sDescription
);
1562 m_sDescription
= NULL
;
1566 m_sDescription
= strdupW(pszName
);
1567 if (!m_sDescription
)
1568 return E_OUTOFMEMORY
;
1575 HRESULT STDMETHODCALLTYPE
CShellLink::GetWorkingDirectory(LPWSTR pszDir
, INT cchMaxPath
)
1577 TRACE("(%p)->(%p len %u)\n", this, pszDir
, cchMaxPath
);
1583 lstrcpynW(pszDir
, m_sWorkDir
, cchMaxPath
);
1588 HRESULT STDMETHODCALLTYPE
CShellLink::SetWorkingDirectory(LPCWSTR pszDir
)
1590 TRACE("(%p)->(dir=%s)\n", this, debugstr_w(pszDir
));
1592 HeapFree(GetProcessHeap(), 0, m_sWorkDir
);
1597 m_sWorkDir
= strdupW(pszDir
);
1599 return E_OUTOFMEMORY
;
1606 HRESULT STDMETHODCALLTYPE
CShellLink::GetArguments(LPWSTR pszArgs
, INT cchMaxPath
)
1608 TRACE("(%p)->(%p len=%u)\n", this, pszArgs
, cchMaxPath
);
1614 lstrcpynW(pszArgs
, m_sArgs
, cchMaxPath
);
1619 HRESULT STDMETHODCALLTYPE
CShellLink::SetArguments(LPCWSTR pszArgs
)
1621 TRACE("(%p)->(args=%s)\n", this, debugstr_w(pszArgs
));
1623 HeapFree(GetProcessHeap(), 0, m_sArgs
);
1628 m_sArgs
= strdupW(pszArgs
);
1630 return E_OUTOFMEMORY
;
1637 HRESULT STDMETHODCALLTYPE
CShellLink::GetIconLocation(LPWSTR pszIconPath
, INT cchIconPath
, INT
*piIcon
)
1639 TRACE("(%p)->(%p len=%u iicon=%p)\n", this, pszIconPath
, cchIconPath
, piIcon
);
1646 /* Update the original icon path location */
1647 if (m_Header
.dwFlags
& SLDF_HAS_EXP_ICON_SZ
)
1649 WCHAR szPath
[MAX_PATH
];
1651 /* Search for an icon environment block */
1652 LPEXP_SZ_LINK pInfo
;
1653 pInfo
= (LPEXP_SZ_LINK
)SHFindDataBlock(m_pDBList
, EXP_SZ_ICON_SIG
);
1654 if (pInfo
&& (pInfo
->cbSize
== sizeof(*pInfo
)))
1656 SHExpandEnvironmentStringsW(pInfo
->szwTarget
, szPath
, _countof(szPath
));
1658 m_Header
.dwFlags
&= ~SLDF_HAS_ICONLOCATION
;
1659 HeapFree(GetProcessHeap(), 0, m_sIcoPath
);
1661 m_sIcoPath
= strdupW(szPath
);
1663 return E_OUTOFMEMORY
;
1665 m_Header
.dwFlags
|= SLDF_HAS_ICONLOCATION
;
1671 *piIcon
= m_Header
.nIconIndex
;
1674 lstrcpynW(pszIconPath
, m_sIcoPath
, cchIconPath
);
1679 static HRESULT
SHELL_PidlGetIconLocationW(PCIDLIST_ABSOLUTE pidl
,
1680 UINT uFlags
, PWSTR pszIconFile
, UINT cchMax
, int *piIndex
, UINT
*pwFlags
)
1682 LPCITEMIDLIST pidlLast
;
1683 CComPtr
<IShellFolder
> psf
;
1685 HRESULT hr
= SHBindToParent(pidl
, IID_PPV_ARG(IShellFolder
, &psf
), &pidlLast
);
1686 if (FAILED_UNEXPECTEDLY(hr
))
1689 CComPtr
<IExtractIconW
> pei
;
1690 hr
= psf
->GetUIObjectOf(0, 1, &pidlLast
, IID_NULL_PPV_ARG(IExtractIconW
, &pei
));
1691 if (FAILED_UNEXPECTEDLY(hr
))
1694 hr
= pei
->GetIconLocation(uFlags
, pszIconFile
, cchMax
, piIndex
, pwFlags
);
1695 if (FAILED_UNEXPECTEDLY(hr
))
1701 HRESULT STDMETHODCALLTYPE
CShellLink::GetIconLocation(UINT uFlags
, PWSTR pszIconFile
, UINT cchMax
, int *piIndex
, UINT
*pwFlags
)
1705 pszIconFile
[0] = UNICODE_NULL
;
1708 * It is possible for a shell link to point to another shell link,
1709 * and in particular there is the possibility to point to itself.
1710 * Now, suppose we ask such a link to retrieve its associated icon.
1711 * This function would be called, and due to COM would be called again
1712 * recursively. To solve this issue, we forbid calling GetIconLocation()
1713 * with GIL_FORSHORTCUT set in uFlags, as done by Windows (shown by tests).
1715 if (uFlags
& GIL_FORSHORTCUT
)
1716 return E_INVALIDARG
;
1719 * Now, we set GIL_FORSHORTCUT so that: i) we allow the icon extractor
1720 * of the target to give us a suited icon, and ii) we protect ourselves
1721 * against recursive call.
1723 uFlags
|= GIL_FORSHORTCUT
;
1725 if (uFlags
& GIL_DEFAULTICON
)
1728 hr
= GetIconLocation(pszIconFile
, cchMax
, piIndex
);
1729 if (FAILED(hr
) || pszIconFile
[0] == UNICODE_NULL
)
1731 hr
= SHELL_PidlGetIconLocationW(m_pPidl
, uFlags
, pszIconFile
, cchMax
, piIndex
, pwFlags
);
1735 *pwFlags
= GIL_NOTFILENAME
| GIL_PERCLASS
;
1741 HRESULT STDMETHODCALLTYPE
1742 CShellLink::Extract(PCWSTR pszFile
, UINT nIconIndex
, HICON
*phiconLarge
, HICON
*phiconSmall
, UINT nIconSize
)
1744 HRESULT hr
= NOERROR
;
1745 UINT cxyLarge
= LOWORD(nIconSize
), cxySmall
= HIWORD(nIconSize
);
1749 *phiconLarge
= NULL
;
1750 PrivateExtractIconsW(pszFile
, nIconIndex
, cxyLarge
, cxyLarge
, phiconLarge
, NULL
, 1, 0);
1752 if (*phiconLarge
== NULL
)
1758 *phiconSmall
= NULL
;
1759 PrivateExtractIconsW(pszFile
, nIconIndex
, cxySmall
, cxySmall
, phiconSmall
, NULL
, 1, 0);
1761 if (*phiconSmall
== NULL
)
1767 if (phiconLarge
&& *phiconLarge
)
1769 DestroyIcon(*phiconLarge
);
1770 *phiconLarge
= NULL
;
1772 if (phiconSmall
&& *phiconSmall
)
1774 DestroyIcon(*phiconSmall
);
1775 *phiconSmall
= NULL
;
1783 /* Extends the functionality of PathUnExpandEnvStringsW */
1784 BOOL
PathFullyUnExpandEnvStringsW(
1785 _In_ LPCWSTR pszPath
,
1786 _Out_ LPWSTR pszBuf
,
1789 BOOL Ret
= FALSE
; // Set to TRUE as soon as PathUnExpandEnvStrings starts unexpanding.
1794 while (*pszPath
&& cchBuf
> 0)
1796 /* Attempt unexpanding the path */
1797 res
= PathUnExpandEnvStringsW(pszPath
, pszBuf
, cchBuf
);
1800 /* The unexpansion failed. Try to find a path delimiter. */
1801 p
= wcspbrk(pszPath
, L
" /\\:*?\"<>|%");
1802 if (!p
) /* None found, we will copy the remaining path */
1803 p
= pszPath
+ wcslen(pszPath
);
1804 else /* Found one, we will copy the delimiter and skip it */
1806 /* If we overflow, we cannot unexpand more, so return FALSE */
1807 if (p
- pszPath
>= cchBuf
)
1808 return FALSE
; // *pszBuf = L'\0';
1810 /* Copy the untouched portion of path up to the delimiter, included */
1811 wcsncpy(pszBuf
, pszPath
, p
- pszPath
);
1812 pszBuf
[p
- pszPath
] = L
'\0'; // NULL-terminate
1814 /* Advance the pointers and decrease the remaining buffer size */
1815 cchBuf
-= (p
- pszPath
);
1816 pszBuf
+= (p
- pszPath
);
1817 pszPath
+= (p
- pszPath
);
1822 * The unexpansion succeeded. Skip the unexpanded part by trying
1823 * to find where the original path and the unexpanded string
1825 * NOTE: An alternative(?) would be to stop also at the last
1826 * path delimiter encountered in the loop (i.e. would be the
1827 * first path delimiter in the strings).
1832 * The algorithm starts at the end of the strings and loops back
1833 * while the characters are equal, until it finds a discrepancy.
1835 p
= pszPath
+ wcslen(pszPath
);
1836 q
= pszBuf
+ wcslen(pszBuf
); // This wcslen should be < cchBuf
1837 while ((*p
== *q
) && (p
> pszPath
) && (q
> pszBuf
))
1841 /* Skip discrepancy */
1844 /* Advance the pointers and decrease the remaining buffer size */
1845 cchBuf
-= (q
- pszBuf
);
1857 HRESULT STDMETHODCALLTYPE
CShellLink::SetIconLocation(LPCWSTR pszIconPath
, INT iIcon
)
1859 HRESULT hr
= E_FAIL
;
1860 WCHAR szIconPath
[MAX_PATH
];
1862 TRACE("(%p)->(path=%s iicon=%u)\n", this, debugstr_w(pszIconPath
), iIcon
);
1867 * Check whether the user-given file path contains unexpanded
1868 * environment variables. If so, create a target environment block.
1869 * Note that in this block we will store the user-given path.
1870 * It will contain the unexpanded environment variables, but
1871 * it can also contain already expanded path that the user does
1872 * not want to see them unexpanded (e.g. so that they always
1873 * refer to the same place even if the would-be corresponding
1874 * environment variable could change).
1876 #ifdef ICON_LINK_WINDOWS_COMPAT
1877 /* Try to fully unexpand the icon path */
1878 // if (PathFullyUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath)))
1879 BOOL bSuccess
= PathUnExpandEnvStringsW(pszIconPath
, szIconPath
, _countof(szIconPath
));
1880 if (bSuccess
&& wcscmp(pszIconPath
, szIconPath
) != 0)
1883 * In some situations, described in http://stackoverflow.com/questions/2976489/ishelllinkseticonlocation-translates-my-icon-path-into-program-files-which-i
1884 * the result of PathUnExpandEnvStringsW() could be wrong, and instead
1885 * one would have to store the actual provided icon location path, while
1886 * creating an icon environment block ONLY if that path already contains
1887 * environment variables. This is what the present case is trying to implement.
1889 SHExpandEnvironmentStringsW(pszIconPath
, szIconPath
, _countof(szIconPath
));
1890 if (wcscmp(pszIconPath
, szIconPath
) != 0)
1894 * The user-given file path contains unexpanded environment
1895 * variables, so we need an icon environment block.
1898 LPEXP_SZ_LINK pInfo
;
1900 #ifdef ICON_LINK_WINDOWS_COMPAT
1901 /* Make pszIconPath point to the unexpanded path */
1902 LPCWSTR pszOrgIconPath
= pszIconPath
;
1903 pszIconPath
= szIconPath
;
1905 pInfo
= (LPEXP_SZ_LINK
)SHFindDataBlock(m_pDBList
, EXP_SZ_ICON_SIG
);
1908 /* Make sure that the size of the structure is valid */
1909 if (pInfo
->cbSize
!= sizeof(*pInfo
))
1911 ERR("Ooops. This structure is not as expected...\n");
1913 /* Invalid structure, remove it altogether */
1914 m_Header
.dwFlags
&= ~SLDF_HAS_EXP_ICON_SZ
;
1915 RemoveDataBlock(EXP_SZ_ICON_SIG
);
1917 /* Reset the pointer and go use the static buffer */
1923 /* Use the static buffer */
1925 buffer
.cbSize
= sizeof(buffer
);
1926 buffer
.dwSignature
= EXP_SZ_ICON_SIG
;
1929 lstrcpynW(pInfo
->szwTarget
, pszIconPath
, _countof(pInfo
->szwTarget
));
1930 WideCharToMultiByte(CP_ACP
, 0, pszIconPath
, -1,
1931 pInfo
->szTarget
, _countof(pInfo
->szTarget
), NULL
, NULL
);
1934 if (pInfo
== &buffer
)
1935 hr
= AddDataBlock(pInfo
);
1937 m_Header
.dwFlags
|= SLDF_HAS_EXP_ICON_SZ
;
1939 #ifdef ICON_LINK_WINDOWS_COMPAT
1940 /* Set pszIconPath back to the original one */
1941 pszIconPath
= pszOrgIconPath
;
1943 /* Now, make pszIconPath point to the expanded path */
1944 pszIconPath
= szIconPath
;
1950 * The user-given file path does not contain unexpanded environment
1951 * variables, so we need to remove any icon environment block.
1953 m_Header
.dwFlags
&= ~SLDF_HAS_EXP_ICON_SZ
;
1954 RemoveDataBlock(EXP_SZ_ICON_SIG
);
1956 /* pszIconPath points to the user path */
1960 #ifdef ICON_LINK_WINDOWS_COMPAT
1961 /* Store the original icon path location (may contain unexpanded environment strings) */
1965 m_Header
.dwFlags
&= ~SLDF_HAS_ICONLOCATION
;
1966 HeapFree(GetProcessHeap(), 0, m_sIcoPath
);
1968 m_sIcoPath
= strdupW(pszIconPath
);
1970 return E_OUTOFMEMORY
;
1972 m_Header
.dwFlags
|= SLDF_HAS_ICONLOCATION
;
1977 m_Header
.nIconIndex
= iIcon
;
1983 HRESULT STDMETHODCALLTYPE
CShellLink::SetRelativePath(LPCWSTR pszPathRel
, DWORD dwReserved
)
1985 TRACE("(%p)->(path=%s %x)\n", this, debugstr_w(pszPathRel
), dwReserved
);
1987 HeapFree(GetProcessHeap(), 0, m_sPathRel
);
1992 m_sPathRel
= strdupW(pszPathRel
);
1994 return E_OUTOFMEMORY
;
1998 return ShellLink_UpdatePath(m_sPathRel
, m_sPath
, m_sWorkDir
, &m_sPath
);
2001 static LPWSTR
GetAdvertisedArg(LPCWSTR str
)
2006 LPCWSTR p
= wcschr(str
, L
':');
2010 DWORD len
= p
- str
;
2011 LPWSTR ret
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR
) * (len
+ 1));
2015 memcpy(ret
, str
, sizeof(WCHAR
)*len
);
2020 HRESULT
CShellLink::WriteAdvertiseInfo(LPCWSTR string
, DWORD dwSig
)
2022 EXP_DARWIN_LINK buffer
;
2023 LPEXP_DARWIN_LINK pInfo
;
2025 if ( (dwSig
!= EXP_DARWIN_ID_SIG
)
2026 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2027 && (dwSig
!= EXP_LOGO3_ID_SIG
)
2031 return E_INVALIDARG
;
2037 pInfo
= (LPEXP_DARWIN_LINK
)SHFindDataBlock(m_pDBList
, dwSig
);
2040 /* Make sure that the size of the structure is valid */
2041 if (pInfo
->dbh
.cbSize
!= sizeof(*pInfo
))
2043 ERR("Ooops. This structure is not as expected...\n");
2045 /* Invalid structure, remove it altogether */
2046 if (dwSig
== EXP_DARWIN_ID_SIG
)
2047 m_Header
.dwFlags
&= ~SLDF_HAS_DARWINID
;
2048 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2049 else if (dwSig
== EXP_LOGO3_ID_SIG
)
2050 m_Header
.dwFlags
&= ~SLDF_HAS_LOGO3ID
;
2052 RemoveDataBlock(dwSig
);
2054 /* Reset the pointer and go use the static buffer */
2060 /* Use the static buffer */
2062 buffer
.dbh
.cbSize
= sizeof(buffer
);
2063 buffer
.dbh
.dwSignature
= dwSig
;
2066 lstrcpynW(pInfo
->szwDarwinID
, string
, _countof(pInfo
->szwDarwinID
));
2067 WideCharToMultiByte(CP_ACP
, 0, string
, -1,
2068 pInfo
->szDarwinID
, _countof(pInfo
->szDarwinID
), NULL
, NULL
);
2071 if (pInfo
== &buffer
)
2072 hr
= AddDataBlock(pInfo
);
2075 if (dwSig
== EXP_DARWIN_ID_SIG
)
2076 m_Header
.dwFlags
|= SLDF_HAS_DARWINID
;
2077 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2078 else if (dwSig
== EXP_LOGO3_ID_SIG
)
2079 m_Header
.dwFlags
|= SLDF_HAS_LOGO3ID
;
2086 HRESULT
CShellLink::SetAdvertiseInfo(LPCWSTR str
)
2089 LPCWSTR szComponent
= NULL
, szProduct
= NULL
, p
;
2094 /**/sProduct
= sComponent
= NULL
;/**/
2098 /* each segment must start with two colons */
2099 if (str
[0] != ':' || str
[1] != ':')
2102 /* the last segment is just two colons */
2107 /* there must be a colon straight after a guid */
2108 p
= wcschr(str
, L
':');
2115 /* get the guid, and check if it's validly formatted */
2116 memcpy(szGuid
, str
, sizeof(WCHAR
)*len
);
2119 hr
= CLSIDFromString(szGuid
, &guid
);
2124 /* match it up to a guid that we care about */
2125 if (IsEqualGUID(guid
, SHELL32_AdvtShortcutComponent
) && !szComponent
)
2126 szComponent
= str
; /* Darwin */
2127 else if (IsEqualGUID(guid
, SHELL32_AdvtShortcutProduct
) && !szProduct
)
2128 szProduct
= str
; /* Logo3 */
2132 /* skip to the next field */
2133 str
= wcschr(str
, L
':');
2138 /* we have to have a component for an advertised shortcut */
2142 szComponent
= GetAdvertisedArg(szComponent
);
2143 szProduct
= GetAdvertisedArg(szProduct
);
2145 hr
= WriteAdvertiseInfo(szComponent
, EXP_DARWIN_ID_SIG
);
2148 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2149 hr
= WriteAdvertiseInfo(szProduct
, EXP_LOGO3_ID_SIG
);
2154 HeapFree(GetProcessHeap(), 0, (PVOID
)szComponent
);
2155 HeapFree(GetProcessHeap(), 0, (PVOID
)szProduct
);
2157 if (TRACE_ON(shell
))
2159 GetAdvertiseInfo(&sComponent
, EXP_DARWIN_ID_SIG
);
2160 TRACE("Component = %s\n", debugstr_w(sComponent
));
2161 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2162 GetAdvertiseInfo(&sProduct
, EXP_LOGO3_ID_SIG
);
2163 TRACE("Product = %s\n", debugstr_w(sProduct
));
2171 * Since the real PathResolve (from Wine) is unimplemented at the moment,
2172 * we use this local implementation, until a better one is written (using
2173 * code parts of the SHELL_xxx helpers in Wine's shellpath.c).
2175 static BOOL
HACKISH_PathResolve(
2176 IN OUT PWSTR pszPath
,
2177 IN PZPCWSTR dirs OPTIONAL
,
2180 // FIXME: This is unimplemented!!!
2182 return PathResolve(pszPath
, dirs
, fFlags
);
2184 BOOL Success
= FALSE
;
2186 LPWSTR fname
= NULL
;
2187 WCHAR szPath
[MAX_PATH
];
2189 /* First, search for a valid existing path */
2191 // NOTE: See also: SHELL_FindExecutable()
2194 * List of extensions searched for, by PathResolve with the flag
2195 * PRF_TRYPROGRAMEXTENSIONS == PRF_EXECUTABLE | PRF_VERIFYEXISTS set,
2196 * according to MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/bb776478(v=vs.85).aspx
2198 static PCWSTR Extensions
[] = {L
".pif", L
".com", L
".bat", L
".cmd", L
".lnk", L
".exe", NULL
};
2199 #define LNK_EXT_INDEX 4 // ".lnk" has index 4 in the array above
2202 * Start at the beginning of the list if PRF_EXECUTABLE is set, otherwise
2203 * just use the last element 'NULL' (no extension checking).
2205 i
= ((fFlags
& PRF_EXECUTABLE
) ? 0 : _countof(Extensions
) - 1);
2206 for (; i
< _countof(Extensions
); ++i
)
2208 /* Ignore shell links ".lnk" if needed */
2209 if ((fFlags
& PRF_DONTFINDLNK
) && (i
== LNK_EXT_INDEX
))
2212 Success
= (SearchPathW(NULL
, pszPath
, Extensions
[i
],
2213 _countof(szPath
), szPath
, NULL
) != 0);
2216 ERR("SearchPathW(pszPath = '%S') failed\n", pszPath
);
2220 ERR("SearchPathW(pszPath = '%S', szPath = '%S') succeeded\n", pszPath
, szPath
);
2227 ERR("SearchPathW(pszPath = '%S') failed\n", pszPath
);
2229 /* We failed, try with PathFindOnPath, as explained by MSDN */
2230 // Success = PathFindOnPathW(pszPath, dirs);
2231 StringCchCopyW(szPath
, _countof(szPath
), pszPath
);
2232 Success
= PathFindOnPathW(szPath
, dirs
);
2235 ERR("PathFindOnPathW(pszPath = '%S') failed\n", pszPath
);
2237 /* We failed again, fall back to building a possible non-existing path */
2238 if (!GetFullPathNameW(pszPath
, _countof(szPath
), szPath
, &fname
))
2240 ERR("GetFullPathNameW(pszPath = '%S') failed\n", pszPath
);
2244 Success
= PathFileExistsW(szPath
);
2246 ERR("PathFileExistsW(szPath = '%S') failed\n", szPath
);
2248 /******************************************************/
2249 /* Question: Why this line is needed only for files?? */
2250 if (fname
&& (_wcsicmp(pszPath
, fname
) == 0))
2252 /******************************************************/
2256 ERR("PathFindOnPathW(pszPath = '%S' ==> '%S') succeeded\n", pszPath
, szPath
);
2260 /* Copy back the results to the caller */
2261 StringCchCopyW(pszPath
, MAX_PATH
, szPath
);
2264 * Since the called functions always checked whether the file path existed,
2265 * we do not need to redo a final check: we can use instead the cached
2266 * result in 'Success'.
2268 return ((fFlags
& PRF_VERIFYEXISTS
) ? Success
: TRUE
);
2272 HRESULT
CShellLink::SetTargetFromPIDLOrPath(LPCITEMIDLIST pidl
, LPCWSTR pszFile
)
2275 LPITEMIDLIST pidlNew
= NULL
;
2276 WCHAR szPath
[MAX_PATH
];
2279 * Not both 'pidl' and 'pszFile' should be set.
2280 * But either one or both can be NULL.
2282 if (pidl
&& pszFile
)
2287 /* Clone the PIDL */
2288 pidlNew
= ILClone(pidl
);
2294 /* Build a PIDL for this path target */
2295 hr
= SHILCreateFromPathW(pszFile
, &pidlNew
, NULL
);
2298 /* This failed, try to resolve the path, then create a simple PIDL */
2300 StringCchCopyW(szPath
, _countof(szPath
), pszFile
);
2301 // FIXME: Because PathResolve is unimplemented, we use our hackish implementation!
2302 HACKISH_PathResolve(szPath
, NULL
, PRF_TRYPROGRAMEXTENSIONS
);
2304 pidlNew
= SHSimpleIDListFromPathW(szPath
);
2305 /******************************************************/
2306 /* Question: Why this line is needed only for files?? */
2307 hr
= (*szPath
? S_OK
: E_INVALIDARG
); // S_FALSE
2308 /******************************************************/
2311 // else if (!pidl && !pszFile) { pidlNew = NULL; hr = S_OK; }
2318 if (SHGetPathFromIDListW(pidlNew
, szPath
))
2322 // TODO: Fully update link info, tracker, file attribs...
2331 /* Update the cached path (for link info) */
2332 ShellLink_GetVolumeInfo(pszFile
, &volume
);
2335 HeapFree(GetProcessHeap(), 0, m_sPath
);
2337 m_sPath
= strdupW(pszFile
);
2339 return E_OUTOFMEMORY
;
2345 HRESULT STDMETHODCALLTYPE
CShellLink::SetPath(LPCWSTR pszFile
)
2347 LPWSTR unquoted
= NULL
;
2350 TRACE("(%p)->(path=%s)\n", this, debugstr_w(pszFile
));
2353 return E_INVALIDARG
;
2356 * Allow upgrading Logo3 shortcuts (m_Header.dwFlags & SLDF_HAS_LOGO3ID),
2357 * but forbid upgrading Darwin ones.
2359 if (m_Header
.dwFlags
& SLDF_HAS_DARWINID
)
2362 /* quotes at the ends of the string are stripped */
2363 SIZE_T len
= wcslen(pszFile
);
2364 if (pszFile
[0] == L
'"' && pszFile
[len
-1] == L
'"')
2366 unquoted
= strdupW(pszFile
);
2367 PathUnquoteSpacesW(unquoted
);
2371 /* any other quote marks are invalid */
2372 if (wcschr(pszFile
, L
'"'))
2378 /* Clear the cached path */
2379 HeapFree(GetProcessHeap(), 0, m_sPath
);
2382 /* Check for an advertised target (Logo3 or Darwin) */
2383 if (SetAdvertiseInfo(pszFile
) != S_OK
)
2385 /* This is not an advertised target, but a regular path */
2386 WCHAR szPath
[MAX_PATH
];
2389 * Check whether the user-given file path contains unexpanded
2390 * environment variables. If so, create a target environment block.
2391 * Note that in this block we will store the user-given path.
2392 * It will contain the unexpanded environment variables, but
2393 * it can also contain already expanded path that the user does
2394 * not want to see them unexpanded (e.g. so that they always
2395 * refer to the same place even if the would-be corresponding
2396 * environment variable could change).
2399 SHExpandEnvironmentStringsW(pszFile
, szPath
, _countof(szPath
));
2403 if (*pszFile
&& (wcscmp(pszFile
, szPath
) != 0))
2406 * The user-given file path contains unexpanded environment
2407 * variables, so we need a target environment block.
2410 LPEXP_SZ_LINK pInfo
;
2412 pInfo
= (LPEXP_SZ_LINK
)SHFindDataBlock(m_pDBList
, EXP_SZ_LINK_SIG
);
2415 /* Make sure that the size of the structure is valid */
2416 if (pInfo
->cbSize
!= sizeof(*pInfo
))
2418 ERR("Ooops. This structure is not as expected...\n");
2420 /* Invalid structure, remove it altogether */
2421 m_Header
.dwFlags
&= ~SLDF_HAS_EXP_SZ
;
2422 RemoveDataBlock(EXP_SZ_LINK_SIG
);
2424 /* Reset the pointer and go use the static buffer */
2430 /* Use the static buffer */
2432 buffer
.cbSize
= sizeof(buffer
);
2433 buffer
.dwSignature
= EXP_SZ_LINK_SIG
;
2436 lstrcpynW(pInfo
->szwTarget
, pszFile
, _countof(pInfo
->szwTarget
));
2437 WideCharToMultiByte(CP_ACP
, 0, pszFile
, -1,
2438 pInfo
->szTarget
, _countof(pInfo
->szTarget
), NULL
, NULL
);
2441 if (pInfo
== &buffer
)
2442 hr
= AddDataBlock(pInfo
);
2444 m_Header
.dwFlags
|= SLDF_HAS_EXP_SZ
;
2446 /* Now, make pszFile point to the expanded path */
2452 * The user-given file path does not contain unexpanded environment
2453 * variables, so we need to remove any target environment block.
2455 m_Header
.dwFlags
&= ~SLDF_HAS_EXP_SZ
;
2456 RemoveDataBlock(EXP_SZ_LINK_SIG
);
2458 /* pszFile points to the user path */
2461 /* Set the target */
2462 hr
= SetTargetFromPIDLOrPath(NULL
, pszFile
);
2468 HeapFree(GetProcessHeap(), 0, unquoted
);
2472 HRESULT STDMETHODCALLTYPE
CShellLink::AddDataBlock(void* pDataBlock
)
2474 if (SHAddDataBlock(&m_pDBList
, (DATABLOCK_HEADER
*)pDataBlock
))
2482 HRESULT STDMETHODCALLTYPE
CShellLink::CopyDataBlock(DWORD dwSig
, void** ppDataBlock
)
2484 DATABLOCK_HEADER
* pBlock
;
2487 TRACE("%p %08x %p\n", this, dwSig
, ppDataBlock
);
2489 *ppDataBlock
= NULL
;
2491 pBlock
= SHFindDataBlock(m_pDBList
, dwSig
);
2494 ERR("unknown datablock %08x (not found)\n", dwSig
);
2498 pDataBlock
= LocalAlloc(LMEM_ZEROINIT
, pBlock
->cbSize
);
2500 return E_OUTOFMEMORY
;
2502 CopyMemory(pDataBlock
, pBlock
, pBlock
->cbSize
);
2504 *ppDataBlock
= pDataBlock
;
2508 HRESULT STDMETHODCALLTYPE
CShellLink::RemoveDataBlock(DWORD dwSig
)
2510 if (SHRemoveDataBlock(&m_pDBList
, dwSig
))
2518 HRESULT STDMETHODCALLTYPE
CShellLink::GetFlags(DWORD
*pdwFlags
)
2520 TRACE("%p %p\n", this, pdwFlags
);
2521 *pdwFlags
= m_Header
.dwFlags
;
2525 HRESULT STDMETHODCALLTYPE
CShellLink::SetFlags(DWORD dwFlags
)
2528 m_Header
.dwFlags
= dwFlags
;
2537 /**************************************************************************
2538 * CShellLink implementation of IShellExtInit::Initialize()
2540 * Loads the shelllink from the dataobject the shell is pointing to.
2542 HRESULT STDMETHODCALLTYPE
CShellLink::Initialize(LPCITEMIDLIST pidlFolder
, IDataObject
*pdtobj
, HKEY hkeyProgID
)
2544 TRACE("%p %p %p %p\n", this, pidlFolder
, pdtobj
, hkeyProgID
);
2550 format
.cfFormat
= CF_HDROP
;
2552 format
.dwAspect
= DVASPECT_CONTENT
;
2554 format
.tymed
= TYMED_HGLOBAL
;
2557 HRESULT hr
= pdtobj
->GetData(&format
, &stgm
);
2561 UINT count
= DragQueryFileW((HDROP
)stgm
.hGlobal
, -1, NULL
, 0);
2564 count
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0, NULL
, 0);
2566 LPWSTR path
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, count
* sizeof(WCHAR
));
2569 count
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0, path
, count
);
2571 HeapFree(GetProcessHeap(), 0, path
);
2574 ReleaseStgMedium(&stgm
);
2579 HRESULT STDMETHODCALLTYPE
CShellLink::QueryContextMenu(HMENU hMenu
, UINT indexMenu
, UINT idCmdFirst
, UINT idCmdLast
, UINT uFlags
)
2583 TRACE("%p %p %u %u %u %u\n", this,
2584 hMenu
, indexMenu
, idCmdFirst
, idCmdLast
, uFlags
);
2587 return E_INVALIDARG
;
2590 if (!LoadStringW(shell32_hInstance
, IDS_OPEN_VERB
, wszOpen
, _countof(wszOpen
)))
2594 ZeroMemory(&mii
, sizeof(mii
));
2595 mii
.cbSize
= sizeof(mii
);
2596 mii
.fMask
= MIIM_TYPE
| MIIM_ID
| MIIM_STATE
;
2597 mii
.dwTypeData
= wszOpen
;
2598 mii
.cch
= wcslen(mii
.dwTypeData
);
2599 mii
.wID
= idCmdFirst
+ id
++;
2600 mii
.fState
= MFS_DEFAULT
| MFS_ENABLED
;
2601 mii
.fType
= MFT_STRING
;
2602 if (!InsertMenuItemW(hMenu
, indexMenu
, TRUE
, &mii
))
2606 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, id
);
2609 HRESULT STDMETHODCALLTYPE
CShellLink::InvokeCommand(LPCMINVOKECOMMANDINFO lpici
)
2614 TRACE("%p %p\n", this, lpici
);
2616 if (lpici
->cbSize
< sizeof(CMINVOKECOMMANDINFO
))
2617 return E_INVALIDARG
;
2619 // NOTE: We could use lpici->hwnd (certainly in case lpici->fMask doesn't contain CMIC_MASK_FLAG_NO_UI)
2620 // as the parent window handle... ?
2621 /* FIXME: get using interface set from IObjectWithSite?? */
2622 // NOTE: We might need an extended version of Resolve that provides us with paths...
2623 HRESULT hr
= Resolve(lpici
->hwnd
, 0);
2626 TRACE("failed to resolve component with error 0x%08x", hr
);
2630 path
= strdupW(m_sPath
);
2632 if ( lpici
->cbSize
== sizeof(CMINVOKECOMMANDINFOEX
) &&
2633 (lpici
->fMask
& CMIC_MASK_UNICODE
) )
2635 LPCMINVOKECOMMANDINFOEX iciex
= (LPCMINVOKECOMMANDINFOEX
)lpici
;
2639 len
+= wcslen(m_sArgs
);
2640 if (iciex
->lpParametersW
)
2641 len
+= wcslen(iciex
->lpParametersW
);
2643 args
= (LPWSTR
)HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
2646 wcscat(args
, m_sArgs
);
2647 if (iciex
->lpParametersW
)
2650 wcscat(args
, iciex
->lpParametersW
);
2653 else if (m_sArgs
!= NULL
)
2655 args
= strdupW(m_sArgs
);
2658 SHELLEXECUTEINFOW sei
;
2659 ZeroMemory(&sei
, sizeof(sei
));
2660 sei
.cbSize
= sizeof(sei
);
2661 sei
.fMask
= SEE_MASK_HASLINKNAME
| SEE_MASK_UNICODE
|
2662 (lpici
->fMask
& (SEE_MASK_NOASYNC
| SEE_MASK_ASYNCOK
| SEE_MASK_FLAG_NO_UI
));
2664 sei
.lpClass
= m_sLinkPath
;
2665 sei
.nShow
= m_Header
.nShowCommand
;
2666 sei
.lpDirectory
= m_sWorkDir
;
2667 sei
.lpParameters
= args
;
2668 sei
.lpVerb
= L
"open";
2670 // HACK for ShellExecuteExW
2671 if (m_sPath
&& wcsstr(m_sPath
, L
".cpl"))
2672 sei
.lpVerb
= L
"cplopen";
2674 if (ShellExecuteExW(&sei
))
2679 HeapFree(GetProcessHeap(), 0, args
);
2680 HeapFree(GetProcessHeap(), 0, path
);
2685 HRESULT STDMETHODCALLTYPE
CShellLink::GetCommandString(UINT_PTR idCmd
, UINT uType
, UINT
* pwReserved
, LPSTR pszName
, UINT cchMax
)
2687 FIXME("%p %lu %u %p %p %u\n", this, idCmd
, uType
, pwReserved
, pszName
, cchMax
);
2691 INT_PTR CALLBACK
ExtendedShortcutProc(HWND hwndDlg
, UINT uMsg
,
2692 WPARAM wParam
, LPARAM lParam
)
2699 HWND hDlgCtrl
= GetDlgItem(hwndDlg
, IDC_SHORTEX_RUN_DIFFERENT
);
2700 SendMessage(hDlgCtrl
, BM_SETCHECK
, BST_CHECKED
, 0);
2705 HWND hDlgCtrl
= GetDlgItem(hwndDlg
, IDC_SHORTEX_RUN_DIFFERENT
);
2706 if (LOWORD(wParam
) == IDOK
)
2708 if (SendMessage(hDlgCtrl
, BM_GETCHECK
, 0, 0) == BST_CHECKED
)
2709 EndDialog(hwndDlg
, 1);
2711 EndDialog(hwndDlg
, 0);
2713 else if (LOWORD(wParam
) == IDCANCEL
)
2715 EndDialog(hwndDlg
, -1);
2717 else if (LOWORD(wParam
) == IDC_SHORTEX_RUN_DIFFERENT
)
2719 if (SendMessage(hDlgCtrl
, BM_GETCHECK
, 0, 0) == BST_CHECKED
)
2720 SendMessage(hDlgCtrl
, BM_SETCHECK
, BST_UNCHECKED
, 0);
2722 SendMessage(hDlgCtrl
, BM_SETCHECK
, BST_CHECKED
, 0);
2731 SHOpenFolderAndSelectItems(LPITEMIDLIST pidlFolder
,
2733 PCUITEMID_CHILD_ARRAY apidl
,
2736 /**************************************************************************
2737 * SH_GetTargetTypeByPath
2739 * Function to get target type by passing full path to it
2741 LPWSTR
SH_GetTargetTypeByPath(LPCWSTR lpcwFullPath
)
2744 static WCHAR wszBuf
[MAX_PATH
];
2746 /* Get file information */
2748 if (!SHGetFileInfoW(lpcwFullPath
, 0, &fi
, sizeof(fi
), SHGFI_TYPENAME
| SHGFI_USEFILEATTRIBUTES
))
2750 ERR("SHGetFileInfoW failed for %ls (%lu)\n", lpcwFullPath
, GetLastError());
2751 fi
.szTypeName
[0] = L
'\0';
2755 pwszExt
= PathFindExtensionW(lpcwFullPath
);
2758 if (!fi
.szTypeName
[0])
2760 /* The file type is unknown, so default to string "FileExtension File" */
2761 size_t cchRemaining
= 0;
2762 LPWSTR pwszEnd
= NULL
;
2764 StringCchPrintfExW(wszBuf
, _countof(wszBuf
), &pwszEnd
, &cchRemaining
, 0, L
"%s ", pwszExt
+ 1);
2768 /* Update file type */
2769 StringCbPrintfW(wszBuf
, sizeof(wszBuf
), L
"%s (%s)", fi
.szTypeName
, pwszExt
);
2776 BOOL
CShellLink::OnInitDialog(HWND hwndDlg
, HWND hwndFocus
, LPARAM lParam
)
2778 TRACE("CShellLink::OnInitDialog(hwnd %p hwndFocus %p lParam %p)\n", hwndDlg
, hwndFocus
, lParam
);
2780 TRACE("m_sArgs: %S sComponent: %S m_sDescription: %S m_sIcoPath: %S m_sPath: %S m_sPathRel: %S sProduct: %S m_sWorkDir: %S\n", m_sArgs
, sComponent
, m_sDescription
,
2781 m_sIcoPath
, m_sPath
, m_sPathRel
, sProduct
, m_sWorkDir
);
2785 /* Get file information */
2786 // FIXME! FIXME! Shouldn't we use m_sIcoPath, m_Header.nIconIndex instead???
2788 if (!SHGetFileInfoW(m_sLinkPath
, 0, &fi
, sizeof(fi
), SHGFI_TYPENAME
| SHGFI_ICON
))
2790 ERR("SHGetFileInfoW failed for %ls (%lu)\n", m_sLinkPath
, GetLastError());
2791 fi
.szTypeName
[0] = L
'\0';
2798 DestroyIcon(m_hIcon
);
2800 SendDlgItemMessageW(hwndDlg
, IDC_SHORTCUT_ICON
, STM_SETICON
, (WPARAM
)m_hIcon
, 0);
2803 ERR("ExtractIconW failed %ls %u\n", m_sIcoPath
, m_Header
.nIconIndex
);
2807 SetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_TYPE_EDIT
, SH_GetTargetTypeByPath(m_sPath
));
2809 /* Target location */
2812 WCHAR target
[MAX_PATH
];
2813 StringCchCopyW(target
, _countof(target
), m_sPath
);
2814 PathRemoveFileSpecW(target
);
2815 SetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_LOCATION_EDIT
, PathFindFileNameW(target
));
2821 WCHAR newpath
[2*MAX_PATH
] = L
"\0";
2822 if (wcschr(m_sPath
, ' '))
2823 StringCchPrintfExW(newpath
, _countof(newpath
), NULL
, NULL
, 0, L
"\"%ls\"", m_sPath
);
2825 StringCchCopyExW(newpath
, _countof(newpath
), m_sPath
, NULL
, NULL
, 0);
2827 if (m_sArgs
&& m_sArgs
[0])
2829 StringCchCatW(newpath
, _countof(newpath
), L
" ");
2830 StringCchCatW(newpath
, _countof(newpath
), m_sArgs
);
2832 SetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_TARGET_TEXT
, newpath
);
2837 SetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_START_IN_EDIT
, m_sWorkDir
);
2841 SetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_COMMENT_EDIT
, m_sDescription
);
2848 void CShellLink::OnCommand(HWND hwndDlg
, int id
, HWND hwndCtl
, UINT codeNotify
)
2852 case IDC_SHORTCUT_FIND
:
2853 SHOpenFolderAndSelectItems(m_pPidl
, 0, NULL
, 0);
2856 /// open target directory
2860 case IDC_SHORTCUT_CHANGE_ICON
:
2862 WCHAR wszPath
[MAX_PATH
] = L
"";
2865 wcscpy(wszPath
, m_sIcoPath
);
2867 FindExecutableW(m_sPath
, NULL
, wszPath
);
2869 INT IconIndex
= m_Header
.nIconIndex
;
2870 if (PickIconDlg(hwndDlg
, wszPath
, _countof(wszPath
), &IconIndex
))
2872 SetIconLocation(wszPath
, IconIndex
);
2873 PropSheet_Changed(GetParent(hwndDlg
), hwndDlg
);
2875 HICON hIconLarge
= CreateShortcutIcon(wszPath
, IconIndex
);
2879 DestroyIcon(m_hIcon
);
2880 m_hIcon
= hIconLarge
;
2881 SendDlgItemMessageW(hwndDlg
, IDC_SHORTCUT_ICON
, STM_SETICON
, (WPARAM
)m_hIcon
, 0);
2887 case IDC_SHORTCUT_ADVANCED
:
2889 INT_PTR result
= DialogBoxParamW(shell32_hInstance
, MAKEINTRESOURCEW(IDD_SHORTCUT_EXTENDED_PROPERTIES
), hwndDlg
, ExtendedShortcutProc
, (LPARAM
)m_bRunAs
);
2890 if (result
== 1 || result
== 0)
2892 if (m_bRunAs
!= result
)
2894 PropSheet_Changed(GetParent(hwndDlg
), hwndDlg
);
2902 if (codeNotify
== EN_CHANGE
)
2905 PropSheet_Changed(GetParent(hwndDlg
), hwndDlg
);
2909 LRESULT
CShellLink::OnNotify(HWND hwndDlg
, int idFrom
, LPNMHDR pnmhdr
)
2911 WCHAR wszBuf
[MAX_PATH
];
2912 LPPSHNOTIFY lppsn
= (LPPSHNOTIFY
)pnmhdr
;
2914 if (lppsn
->hdr
.code
== PSN_APPLY
)
2916 /* set working directory */
2917 GetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_START_IN_EDIT
, wszBuf
, _countof(wszBuf
));
2918 SetWorkingDirectory(wszBuf
);
2920 /* set link destination */
2921 GetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_TARGET_TEXT
, wszBuf
, _countof(wszBuf
));
2922 LPWSTR lpszArgs
= NULL
;
2923 LPWSTR unquoted
= strdupW(wszBuf
);
2924 StrTrimW(unquoted
, L
" ");
2926 if (!PathFileExistsW(unquoted
))
2928 lpszArgs
= PathGetArgsW(unquoted
);
2929 PathRemoveArgsW(unquoted
);
2930 StrTrimW(lpszArgs
, L
" ");
2932 if (unquoted
[0] == '"' && unquoted
[wcslen(unquoted
) - 1] == '"')
2933 PathUnquoteSpacesW(unquoted
);
2935 WCHAR
*pwszExt
= PathFindExtensionW(unquoted
);
2936 if (!wcsicmp(pwszExt
, L
".lnk"))
2938 // FIXME load localized error msg
2939 MessageBoxW(hwndDlg
, L
"You cannot create a link to a shortcut", L
"Error", MB_ICONERROR
);
2940 SetWindowLongPtr(hwndDlg
, DWLP_MSGRESULT
, PSNRET_INVALID_NOCHANGEPAGE
);
2944 if (!PathFileExistsW(unquoted
))
2946 // FIXME load localized error msg
2947 MessageBoxW(hwndDlg
, L
"The specified file name in the target box is invalid", L
"Error", MB_ICONERROR
);
2948 SetWindowLongPtr(hwndDlg
, DWLP_MSGRESULT
, PSNRET_INVALID_NOCHANGEPAGE
);
2954 SetArguments(lpszArgs
);
2956 SetArguments(L
"\0");
2958 HeapFree(GetProcessHeap(), 0, unquoted
);
2960 TRACE("This %p m_sLinkPath %S\n", this, m_sLinkPath
);
2961 Save(m_sLinkPath
, TRUE
);
2962 SHChangeNotify(SHCNE_UPDATEITEM
, SHCNF_PATHW
, m_sLinkPath
, NULL
);
2963 SetWindowLongPtr(hwndDlg
, DWLP_MSGRESULT
, PSNRET_NOERROR
);
2969 void CShellLink::OnDestroy(HWND hwndDlg
)
2973 DestroyIcon(m_hIcon
);
2978 /**************************************************************************
2979 * SH_ShellLinkDlgProc
2981 * dialog proc of the shortcut property dialog
2985 CShellLink::SH_ShellLinkDlgProc(HWND hwndDlg
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
2987 LPPROPSHEETPAGEW ppsp
;
2988 CShellLink
*pThis
= reinterpret_cast<CShellLink
*>(GetWindowLongPtr(hwndDlg
, DWLP_USER
));
2993 ppsp
= (LPPROPSHEETPAGEW
)lParam
;
2997 pThis
= reinterpret_cast<CShellLink
*>(ppsp
->lParam
);
2998 SetWindowLongPtr(hwndDlg
, DWLP_USER
, (LONG_PTR
)pThis
);
2999 return pThis
->OnInitDialog(hwndDlg
, (HWND
)(wParam
), lParam
);
3002 return pThis
->OnNotify(hwndDlg
, (int)wParam
, (NMHDR
*)lParam
);
3005 pThis
->OnCommand(hwndDlg
, LOWORD(wParam
), (HWND
)lParam
, HIWORD(wParam
));
3009 pThis
->OnDestroy(hwndDlg
);
3019 /**************************************************************************
3020 * ShellLink_IShellPropSheetExt interface
3023 HRESULT STDMETHODCALLTYPE
CShellLink::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage
, LPARAM lParam
)
3025 HPROPSHEETPAGE hPage
= SH_CreatePropertySheetPage(IDD_SHORTCUT_PROPERTIES
, SH_ShellLinkDlgProc
, (LPARAM
)this, NULL
);
3028 ERR("failed to create property sheet page\n");
3032 if (!pfnAddPage(hPage
, lParam
))
3038 HRESULT STDMETHODCALLTYPE
CShellLink::ReplacePage(UINT uPageID
, LPFNADDPROPSHEETPAGE pfnReplacePage
, LPARAM lParam
)
3040 TRACE("(%p) (uPageID %u, pfnReplacePage %p lParam %p\n", this, uPageID
, pfnReplacePage
, lParam
);
3044 HRESULT STDMETHODCALLTYPE
CShellLink::SetSite(IUnknown
*punk
)
3046 TRACE("%p %p\n", this, punk
);
3053 HRESULT STDMETHODCALLTYPE
CShellLink::GetSite(REFIID iid
, void ** ppvSite
)
3055 TRACE("%p %s %p\n", this, debugstr_guid(&iid
), ppvSite
);
3060 return m_site
->QueryInterface(iid
, ppvSite
);
3063 HRESULT STDMETHODCALLTYPE
CShellLink::DragEnter(IDataObject
*pDataObject
,
3064 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
3066 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject
);
3067 LPCITEMIDLIST pidlLast
;
3068 CComPtr
<IShellFolder
> psf
;
3070 HRESULT hr
= SHBindToParent(m_pPidl
, IID_PPV_ARG(IShellFolder
, &psf
), &pidlLast
);
3074 hr
= psf
->GetUIObjectOf(0, 1, &pidlLast
, IID_NULL_PPV_ARG(IDropTarget
, &m_DropTarget
));
3077 hr
= m_DropTarget
->DragEnter(pDataObject
, dwKeyState
, pt
, pdwEffect
);
3079 *pdwEffect
= DROPEFFECT_NONE
;
3082 *pdwEffect
= DROPEFFECT_NONE
;
3087 HRESULT STDMETHODCALLTYPE
CShellLink::DragOver(DWORD dwKeyState
, POINTL pt
,
3090 TRACE("(%p)\n", this);
3093 hr
= m_DropTarget
->DragOver(dwKeyState
, pt
, pdwEffect
);
3097 HRESULT STDMETHODCALLTYPE
CShellLink::DragLeave()
3099 TRACE("(%p)\n", this);
3103 hr
= m_DropTarget
->DragLeave();
3104 m_DropTarget
.Release();
3110 HRESULT STDMETHODCALLTYPE
CShellLink::Drop(IDataObject
*pDataObject
,
3111 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
3113 TRACE("(%p)\n", this);
3116 hr
= m_DropTarget
->Drop(pDataObject
, dwKeyState
, pt
, pdwEffect
);
3121 /**************************************************************************
3122 * IShellLink_ConstructFromFile
3124 HRESULT WINAPI
IShellLink_ConstructFromPath(WCHAR
*path
, REFIID riid
, LPVOID
*ppv
)
3126 CComPtr
<IPersistFile
> ppf
;
3127 HRESULT hr
= CShellLink::_CreatorClass::CreateInstance(NULL
, IID_PPV_ARG(IPersistFile
, &ppf
));
3131 hr
= ppf
->Load(path
, 0);
3135 return ppf
->QueryInterface(riid
, ppv
);
3138 HRESULT WINAPI
IShellLink_ConstructFromFile(IShellFolder
* psf
, LPCITEMIDLIST pidl
, REFIID riid
, LPVOID
*ppv
)
3140 WCHAR path
[MAX_PATH
];
3141 if (!ILGetDisplayNameExW(psf
, pidl
, path
, 0))
3144 return IShellLink_ConstructFromPath(path
, riid
, ppv
);
3147 HICON
CShellLink::CreateShortcutIcon(LPCWSTR wszIconPath
, INT IconIndex
)
3149 const INT cx
= GetSystemMetrics(SM_CXICON
), cy
= GetSystemMetrics(SM_CYICON
);
3150 const COLORREF crMask
= GetSysColor(COLOR_3DFACE
);
3152 HIMAGELIST himl
= ImageList_Create(cx
, cy
, ILC_COLOR32
| ILC_MASK
, 1, 1);
3153 HICON hIcon
= NULL
, hNewIcon
= NULL
;
3154 HICON hShortcut
= (HICON
)LoadImageW(shell32_hInstance
, MAKEINTRESOURCE(IDI_SHELL_SHORTCUT
),
3155 IMAGE_ICON
, cx
, cy
, 0);
3157 ::ExtractIconExW(wszIconPath
, IconIndex
, &hIcon
, NULL
, 1);
3158 if (!hIcon
|| !hShortcut
|| !himl
)
3161 hDC
= CreateCompatibleDC(NULL
);
3164 // create 32bpp bitmap
3166 ZeroMemory(&bi
, sizeof(bi
));
3167 bi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
3168 bi
.bmiHeader
.biWidth
= cx
;
3169 bi
.bmiHeader
.biHeight
= cy
;
3170 bi
.bmiHeader
.biPlanes
= 1;
3171 bi
.bmiHeader
.biBitCount
= 32;
3173 HBITMAP hbm
= CreateDIBSection(hDC
, &bi
, DIB_RGB_COLORS
, &pvBits
, NULL
, 0);
3176 // draw the icon image
3177 HGDIOBJ hbmOld
= SelectObject(hDC
, hbm
);
3179 HBRUSH hbr
= CreateSolidBrush(crMask
);
3180 RECT rc
= { 0, 0, cx
, cy
};
3181 FillRect(hDC
, &rc
, hbr
);
3184 DrawIconEx(hDC
, 0, 0, hIcon
, cx
, cy
, 0, NULL
, DI_NORMAL
);
3185 DrawIconEx(hDC
, 0, 0, hShortcut
, cx
, cy
, 0, NULL
, DI_NORMAL
);
3187 SelectObject(hDC
, hbmOld
);
3189 INT iAdded
= ImageList_AddMasked(himl
, hbm
, crMask
);
3190 hNewIcon
= ImageList_GetIcon(himl
, iAdded
, ILD_NORMAL
| ILD_TRANSPARENT
);
3201 DestroyIcon(hShortcut
);
3203 ImageList_Destroy(himl
);