4 * Copyright 1997 Marcus Meissner
5 * Copyright 1998, 1999, 2002 Juergen Schmied
7 * IShellFolder2 and related interfaces
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
28 /***************************************************************************
29 * SHELL32_GetCustomFolderAttributeFromPath (internal function)
31 * Gets a value from the folder's desktop.ini file, if one exists.
34 * pwszFolderPath[I] Folder containing the desktop.ini file.
35 * pwszHeading [I] Heading in .ini file.
36 * pwszAttribute [I] Attribute in .ini file.
37 * pwszValue [O] Buffer to store value into.
38 * cchValue [I] Size in characters including NULL of buffer pointed to
42 * TRUE if returned non-NULL value.
45 static BOOL __inline
SHELL32_GetCustomFolderAttributeFromPath(
46 LPWSTR pwszFolderPath
, LPCWSTR pwszHeading
, LPCWSTR pwszAttribute
,
47 LPWSTR pwszValue
, DWORD cchValue
)
49 static const WCHAR wszDesktopIni
[] =
50 {'d','e','s','k','t','o','p','.','i','n','i',0};
51 static const WCHAR wszDefault
[] = {0};
53 PathAddBackslashW(pwszFolderPath
);
54 PathAppendW(pwszFolderPath
, wszDesktopIni
);
55 return GetPrivateProfileStringW(pwszHeading
, pwszAttribute
, wszDefault
,
56 pwszValue
, cchValue
, pwszFolderPath
);
59 /***************************************************************************
60 * GetNextElement (internal function)
62 * Gets a part of a string till the first backslash.
65 * pszNext [IN] string to get the element from
66 * pszOut [IN] pointer to buffer which receives string
67 * dwOut [IN] length of pszOut
70 * LPSTR pointer to first, not yet parsed char
73 LPCWSTR
GetNextElementW (LPCWSTR pszNext
, LPWSTR pszOut
, DWORD dwOut
)
75 LPCWSTR pszTail
= pszNext
;
78 TRACE ("(%s %p 0x%08x)\n", debugstr_w (pszNext
), pszOut
, dwOut
);
82 if (!pszNext
|| !*pszNext
)
85 while (*pszTail
&& (*pszTail
!= (WCHAR
) '\\'))
88 dwCopy
= pszTail
- pszNext
+ 1;
89 lstrcpynW (pszOut
, pszNext
, (dwOut
< dwCopy
) ? dwOut
: dwCopy
);
96 TRACE ("--(%s %s 0x%08x %p)\n", debugstr_w (pszNext
), debugstr_w (pszOut
), dwOut
, pszTail
);
100 HRESULT
SHELL32_ParseNextElement (IShellFolder2
* psf
, HWND hwndOwner
, LPBC pbc
,
101 LPITEMIDLIST
* pidlInOut
, LPOLESTR szNext
, DWORD
* pEaten
, DWORD
* pdwAttributes
)
103 HRESULT hr
= E_INVALIDARG
;
104 LPITEMIDLIST pidlIn
= pidlInOut
? *pidlInOut
: NULL
;
105 LPITEMIDLIST pidlOut
= NULL
;
106 LPITEMIDLIST pidlTemp
= NULL
;
107 CComPtr
<IShellFolder
> psfChild
;
109 TRACE ("(%p, %p, %p, %s)\n", psf
, pbc
, pidlIn
, debugstr_w (szNext
));
111 /* get the shellfolder for the child pidl and let it analyse further */
112 hr
= psf
->BindToObject(pidlIn
, pbc
, IID_PPV_ARG(IShellFolder
, &psfChild
));
116 hr
= psfChild
->ParseDisplayName(hwndOwner
, pbc
, szNext
, pEaten
, &pidlOut
, pdwAttributes
);
120 pidlTemp
= ILCombine (pidlIn
, pidlOut
);
135 *pidlInOut
= pidlTemp
;
137 TRACE ("-- pidl=%p ret=0x%08x\n", pidlInOut
? *pidlInOut
: NULL
, hr
);
141 /***********************************************************************
142 * SHELL32_CoCreateInitSF
144 * Creates a shell folder and initializes it with a pidl and a root folder
145 * via IPersistFolder3 or IPersistFolder.
148 * pathRoot can be NULL for Folders being a drive.
149 * In this case the absolute path is built from pidlChild (eg. C:)
151 static HRESULT
SHELL32_CoCreateInitSF (LPCITEMIDLIST pidlRoot
, LPCWSTR pathRoot
,
152 LPCITEMIDLIST pidlChild
, REFCLSID clsid
, LPVOID
* ppvOut
)
155 CComPtr
<IShellFolder
> pShellFolder
;
157 TRACE ("%p %s %p\n", pidlRoot
, debugstr_w(pathRoot
), pidlChild
);
159 hr
= SHCoCreateInstance(NULL
, &clsid
, NULL
, IID_PPV_ARG(IShellFolder
, &pShellFolder
));
162 LPITEMIDLIST pidlAbsolute
= ILCombine (pidlRoot
, pidlChild
);
163 CComPtr
<IPersistFolder
> ppf
;
164 CComPtr
<IPersistFolder3
> ppf3
;
166 if ((_ILIsFolder(pidlChild
) || _ILIsDrive(pidlChild
)) &&
167 SUCCEEDED(pShellFolder
->QueryInterface(IID_PPV_ARG(IPersistFolder3
, &ppf3
))))
169 PERSIST_FOLDER_TARGET_INFO ppfti
;
171 ZeroMemory (&ppfti
, sizeof (ppfti
));
173 /* fill the PERSIST_FOLDER_TARGET_INFO */
174 ppfti
.dwAttributes
= -1;
180 lstrcpynW (ppfti
.szTargetParsingName
, pathRoot
, MAX_PATH
- 1);
181 PathAddBackslashW(ppfti
.szTargetParsingName
); /* FIXME: why have drives a backslash here ? */
186 int len
= wcslen(ppfti
.szTargetParsingName
);
188 if (!_ILSimpleGetTextW(pidlChild
, ppfti
.szTargetParsingName
+ len
, MAX_PATH
- len
))
192 ppf3
->InitializeEx(NULL
, pidlAbsolute
, &ppfti
);
194 else if (SUCCEEDED((hr
= pShellFolder
->QueryInterface(IID_PPV_ARG(IPersistFolder
, &ppf
)))))
196 ppf
->Initialize(pidlAbsolute
);
198 ILFree (pidlAbsolute
);
201 *ppvOut
= pShellFolder
.Detach();
203 TRACE ("-- (%p) ret=0x%08x\n", *ppvOut
, hr
);
208 /***********************************************************************
209 * SHELL32_BindToChild [Internal]
211 * Common code for IShellFolder_BindToObject.
214 * pidlRoot [I] The parent shell folder's absolute pidl.
215 * pathRoot [I] Absolute dos path of the parent shell folder.
216 * pidlComplete [I] PIDL of the child. Relative to pidlRoot.
217 * riid [I] GUID of the interface, which ppvOut shall be bound to.
218 * ppvOut [O] A reference to the child's interface (riid).
221 * pidlComplete has to contain at least one non empty SHITEMID.
222 * This function makes special assumptions on the shell namespace, which
223 * means you probably can't use it for your IShellFolder implementation.
225 HRESULT
SHELL32_BindToChild (LPCITEMIDLIST pidlRoot
,
226 LPCWSTR pathRoot
, LPCITEMIDLIST pidlComplete
, REFIID riid
, LPVOID
* ppvOut
)
228 static const WCHAR wszDotShellClassInfo
[] = {
229 '.','S','h','e','l','l','C','l','a','s','s','I','n','f','o',0 };
232 CComPtr
<IShellFolder
> pSF
;
234 LPITEMIDLIST pidlChild
;
236 if (!pidlRoot
|| !ppvOut
|| !pidlComplete
|| !pidlComplete
->mkid
.cb
)
241 pidlChild
= ILCloneFirst (pidlComplete
);
243 if ((clsid
= _ILGetGUIDPointer (pidlChild
))) {
245 hr
= SHELL32_CoCreateInitSF (pidlRoot
, pathRoot
, pidlChild
, *clsid
, (LPVOID
*)&pSF
);
247 /* file system folder */
248 CLSID clsidFolder
= CLSID_ShellFSFolder
;
249 static const WCHAR wszCLSID
[] = {'C','L','S','I','D',0};
250 WCHAR wszCLSIDValue
[CHARS_IN_GUID
], wszFolderPath
[MAX_PATH
], *pwszPathTail
= wszFolderPath
;
252 /* see if folder CLSID should be overridden by desktop.ini file */
254 lstrcpynW(wszFolderPath
, pathRoot
, MAX_PATH
);
255 pwszPathTail
= PathAddBackslashW(wszFolderPath
);
258 _ILSimpleGetTextW(pidlChild
,pwszPathTail
,MAX_PATH
- (int)(pwszPathTail
- wszFolderPath
));
260 if (SHELL32_GetCustomFolderAttributeFromPath (wszFolderPath
,
261 wszDotShellClassInfo
, wszCLSID
, wszCLSIDValue
, CHARS_IN_GUID
))
262 CLSIDFromString (wszCLSIDValue
, &clsidFolder
);
264 hr
= SHELL32_CoCreateInitSF (pidlRoot
, pathRoot
, pidlChild
,
265 clsidFolder
, (LPVOID
*)&pSF
);
269 if (SUCCEEDED (hr
)) {
270 if (_ILIsPidlSimple (pidlComplete
)) {
272 hr
= pSF
->QueryInterface(riid
, ppvOut
);
275 hr
= pSF
->BindToObject(ILGetNext (pidlComplete
), NULL
, riid
, ppvOut
);
279 TRACE ("-- returning (%p) %08x\n", *ppvOut
, hr
);
284 HRESULT
SHELL32_BindToGuidItem(LPCITEMIDLIST pidlRoot
,
285 PCUIDLIST_RELATIVE pidl
,
290 CComPtr
<IPersistFolder
> pFolder
;
293 GUID
*pGUID
= _ILGetGUIDPointer(pidl
);
296 ERR("SHELL32_BindToGuidItem called for non guid item!\n");
300 hr
= SHCoCreateInstance(NULL
, pGUID
, NULL
, IID_PPV_ARG(IPersistFolder
, &pFolder
));
304 hr
= pFolder
->Initialize(ILCombine(pidlRoot
, pidl
));
308 if (_ILIsPidlSimple (pidl
))
310 return pFolder
->QueryInterface(riid
, ppvOut
);
314 CComPtr
<IShellFolder
> psf
;
315 hr
= pFolder
->QueryInterface(IID_PPV_ARG(IShellFolder
, &psf
));
319 return psf
->BindToObject(ILGetNext (pidl
), pbcReserved
, riid
, ppvOut
);
323 /***********************************************************************
324 * SHELL32_GetDisplayNameOfChild
326 * Retrieves the display name of a child object of a shellfolder.
328 * For a pidl eg. [subpidl1][subpidl2][subpidl3]:
329 * - it binds to the child shellfolder [subpidl1]
330 * - asks it for the displayname of [subpidl2][subpidl3]
332 * Is possible the pidl is a simple pidl. In this case it asks the
333 * subfolder for the displayname of an empty pidl. The subfolder
334 * returns the own displayname eg. "::{guid}". This is used for
335 * virtual folders with the registry key WantsFORPARSING set.
337 HRESULT
SHELL32_GetDisplayNameOfChild (IShellFolder2
* psf
,
338 LPCITEMIDLIST pidl
, DWORD dwFlags
, LPSTRRET strRet
)
340 LPITEMIDLIST pidlFirst
= ILCloneFirst(pidl
);
342 return E_OUTOFMEMORY
;
344 CComPtr
<IShellFolder
> psfChild
;
345 HRESULT hr
= psf
->BindToObject(pidlFirst
, NULL
, IID_PPV_ARG(IShellFolder
, &psfChild
));
348 hr
= psfChild
->GetDisplayNameOf(ILGetNext (pidl
), dwFlags
, strRet
);
355 HRESULT
SHELL32_GetDisplayNameOfGUIDItem(IShellFolder2
* psf
, LPCWSTR pszFolderPath
, PCUITEMID_CHILD pidl
, DWORD dwFlags
, LPSTRRET strRet
)
358 GUID
const *clsid
= _ILGetGUIDPointer (pidl
);
363 /* First of all check if we need to query the name from the child item */
364 if (GET_SHGDN_FOR (dwFlags
) == SHGDN_FORPARSING
&&
365 GET_SHGDN_RELATION (dwFlags
) == SHGDN_NORMAL
)
367 int bWantsForParsing
;
370 * We can only get a filesystem path from a shellfolder if the
371 * value WantsFORPARSING in CLSID\\{...}\\shellfolder exists.
373 * Exception: The MyComputer folder doesn't have this key,
374 * but any other filesystem backed folder it needs it.
376 if (IsEqualIID (*clsid
, CLSID_MyComputer
))
378 bWantsForParsing
= TRUE
;
383 if (HCR_RegOpenClassIDKey(*clsid
, &hkeyClass
))
385 LONG res
= SHGetValueW(hkeyClass
, L
"Shellfolder", L
"WantsForParsing", NULL
, NULL
, NULL
);
386 bWantsForParsing
= (res
== ERROR_SUCCESS
);
387 RegCloseKey(hkeyClass
);
391 if (bWantsForParsing
)
394 * we need the filesystem path to the destination folder.
395 * Only the folder itself can know it
397 return SHELL32_GetDisplayNameOfChild (psf
, pidl
, dwFlags
, strRet
);
401 /* Allocate the buffer for the result */
402 LPWSTR pszPath
= (LPWSTR
)CoTaskMemAlloc((MAX_PATH
+ 1) * sizeof(WCHAR
));
404 return E_OUTOFMEMORY
;
408 if (GET_SHGDN_FOR (dwFlags
) == SHGDN_FORPARSING
)
410 wcscpy(pszPath
, pszFolderPath
);
411 PWCHAR pItemName
= &pszPath
[wcslen(pszPath
)];
413 /* parsing name like ::{...} */
416 SHELL32_GUIDToStringW (*clsid
, &pItemName
[2]);
420 /* user friendly name */
421 if (!HCR_GetClassNameW (*clsid
, pszPath
, MAX_PATH
))
427 strRet
->uType
= STRRET_WSTR
;
428 strRet
->pOleStr
= pszPath
;
432 CoTaskMemFree(pszPath
);
438 /***********************************************************************
439 * SHELL32_GetItemAttributes
443 * folder: 0xE0000177 FILESYSTEM | HASSUBFOLDER | FOLDER
444 * file: 0x40000177 FILESYSTEM
445 * drive: 0xf0000144 FILESYSTEM | HASSUBFOLDER | FOLDER | FILESYSANCESTOR
446 * mycomputer: 0xb0000154 HASSUBFOLDER | FOLDER | FILESYSANCESTOR
447 * (seems to be default for shell extensions if no registry entry exists)
450 * folder: 0xF0400177 FILESYSTEM | HASSUBFOLDER | FOLDER | FILESYSANCESTOR | CANMONIKER
451 * file: 0x40400177 FILESYSTEM | CANMONIKER
452 * drive 0xF0400154 FILESYSTEM | HASSUBFOLDER | FOLDER | FILESYSANCESTOR | CANMONIKER | CANRENAME (LABEL)
454 * According to the MSDN documentation this function should not set flags. It claims only to reset flags when necessary.
455 * However it turns out the native shell32.dll _sets_ flags in several cases - so do we.
458 static const DWORD dwSupportedAttr
=
459 SFGAO_CANCOPY
| /*0x00000001 */
460 SFGAO_CANMOVE
| /*0x00000002 */
461 SFGAO_CANLINK
| /*0x00000004 */
462 SFGAO_CANRENAME
| /*0x00000010 */
463 SFGAO_CANDELETE
| /*0x00000020 */
464 SFGAO_HASPROPSHEET
| /*0x00000040 */
465 SFGAO_DROPTARGET
| /*0x00000100 */
466 SFGAO_LINK
| /*0x00010000 */
467 SFGAO_READONLY
| /*0x00040000 */
468 SFGAO_HIDDEN
| /*0x00080000 */
469 SFGAO_FILESYSANCESTOR
| /*0x10000000 */
470 SFGAO_FOLDER
| /*0x20000000 */
471 SFGAO_FILESYSTEM
| /*0x40000000 */
472 SFGAO_HASSUBFOLDER
; /*0x80000000 */
474 HRESULT
SHELL32_GetGuidItemAttributes (IShellFolder
* psf
, LPCITEMIDLIST pidl
, LPDWORD pdwAttributes
)
476 if (!_ILIsSpecialFolder(pidl
))
478 ERR("Got wrong type of pidl!\n");
479 *pdwAttributes
&= SFGAO_CANLINK
;
483 if (*pdwAttributes
& ~dwSupportedAttr
)
485 WARN ("attributes 0x%08x not implemented\n", (*pdwAttributes
& ~dwSupportedAttr
));
486 *pdwAttributes
&= dwSupportedAttr
;
489 /* First try to get them from the registry */
490 if (HCR_GetFolderAttributes(pidl
, pdwAttributes
) && *pdwAttributes
)
496 /* If we can't get it from the registry we have to query the child */
497 CComPtr
<IShellFolder
> psf2
;
498 if (SUCCEEDED(psf
->BindToObject(pidl
, 0, IID_PPV_ARG(IShellFolder
, &psf2
))))
500 return psf2
->GetAttributesOf(0, NULL
, pdwAttributes
);
504 *pdwAttributes
&= SFGAO_CANLINK
;
508 HRESULT
SHELL32_GetFSItemAttributes(IShellFolder
* psf
, LPCITEMIDLIST pidl
, LPDWORD pdwAttributes
)
510 DWORD dwFileAttributes
, dwShellAttributes
;
512 if (!_ILIsFolder(pidl
) && !_ILIsValue(pidl
))
514 ERR("Got wrong type of pidl!\n");
515 *pdwAttributes
&= SFGAO_CANLINK
;
519 if (*pdwAttributes
& ~dwSupportedAttr
)
521 WARN ("attributes 0x%08x not implemented\n", (*pdwAttributes
& ~dwSupportedAttr
));
522 *pdwAttributes
&= dwSupportedAttr
;
525 dwFileAttributes
= _ILGetFileAttributes(pidl
, NULL
, 0);
527 /* Set common attributes */
528 dwShellAttributes
= *pdwAttributes
;
529 dwShellAttributes
|= SFGAO_FILESYSTEM
| SFGAO_DROPTARGET
| SFGAO_HASPROPSHEET
| SFGAO_CANDELETE
|
530 SFGAO_CANRENAME
| SFGAO_CANLINK
| SFGAO_CANMOVE
| SFGAO_CANCOPY
;
532 if (dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
534 dwShellAttributes
|= (SFGAO_FOLDER
| SFGAO_HASSUBFOLDER
| SFGAO_FILESYSANCESTOR
);
537 dwShellAttributes
&= ~(SFGAO_FOLDER
| SFGAO_HASSUBFOLDER
| SFGAO_FILESYSANCESTOR
);
539 if (dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
)
540 dwShellAttributes
|= SFGAO_HIDDEN
;
542 dwShellAttributes
&= ~SFGAO_HIDDEN
;
544 if (dwFileAttributes
& FILE_ATTRIBUTE_READONLY
)
545 dwShellAttributes
|= SFGAO_READONLY
;
547 dwShellAttributes
&= ~SFGAO_READONLY
;
549 if (SFGAO_LINK
& *pdwAttributes
)
553 if (!_ILGetExtension(pidl
, ext
, MAX_PATH
) || lstrcmpiA(ext
, "lnk"))
554 dwShellAttributes
&= ~SFGAO_LINK
;
557 if (SFGAO_HASSUBFOLDER
& *pdwAttributes
)
559 CComPtr
<IShellFolder
> psf2
;
560 if (SUCCEEDED(psf
->BindToObject(pidl
, 0, IID_PPV_ARG(IShellFolder
, &psf2
))))
562 CComPtr
<IEnumIDList
> pEnumIL
;
563 if (SUCCEEDED(psf2
->EnumObjects(0, SHCONTF_FOLDERS
, &pEnumIL
)))
565 if (pEnumIL
->Skip(1) != S_OK
)
566 dwShellAttributes
&= ~SFGAO_HASSUBFOLDER
;
571 *pdwAttributes
&= dwShellAttributes
;
573 TRACE ("-- 0x%08x\n", *pdwAttributes
);
577 /***********************************************************************
580 HRESULT
SHELL32_CompareIDs(IShellFolder
* iface
, LPARAM lParam
, LPCITEMIDLIST pidl1
, LPCITEMIDLIST pidl2
)
584 char szTemp1
[MAX_PATH
];
585 char szTemp2
[MAX_PATH
];
587 LPITEMIDLIST firstpidl
;
588 LPITEMIDLIST nextpidl1
;
589 LPITEMIDLIST nextpidl2
;
590 CComPtr
<IShellFolder
> psf
;
592 /* test for empty pidls */
593 BOOL isEmpty1
= _ILIsDesktop(pidl1
);
594 BOOL isEmpty2
= _ILIsDesktop(pidl2
);
596 if (isEmpty1
&& isEmpty2
)
597 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 0);
599 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, (WORD
) -1);
601 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 1);
603 /* test for different types. Sort order is the PT_* constant */
604 type1
= _ILGetDataPointer(pidl1
)->type
;
605 type2
= _ILGetDataPointer(pidl2
)->type
;
607 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, (WORD
) -1);
608 else if (type1
> type2
)
609 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 1);
611 /* test for name of pidl */
612 _ILSimpleGetText(pidl1
, szTemp1
, MAX_PATH
);
613 _ILSimpleGetText(pidl2
, szTemp2
, MAX_PATH
);
614 nReturn
= lstrcmpiA(szTemp1
, szTemp2
);
616 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, (WORD
) -1);
617 else if (nReturn
> 0)
618 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 1);
620 /* test of complex pidls */
621 firstpidl
= ILCloneFirst(pidl1
);
622 nextpidl1
= ILGetNext(pidl1
);
623 nextpidl2
= ILGetNext(pidl2
);
625 /* optimizing: test special cases and bind not deeper */
626 /* the deeper shellfolder would do the same */
627 isEmpty1
= _ILIsDesktop(nextpidl1
);
628 isEmpty2
= _ILIsDesktop(nextpidl2
);
630 if (isEmpty1
&& isEmpty2
)
632 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 0);
636 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, (WORD
) -1);
640 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 1);
643 else if (SUCCEEDED(iface
->BindToObject(firstpidl
, NULL
, IID_PPV_ARG(IShellFolder
, &psf
)))) {
644 nReturn
= psf
->CompareIDs(lParam
, nextpidl1
, nextpidl2
);
650 /***********************************************************************
655 HRESULT WINAPI
SHCreateLinks( HWND hWnd
, LPCSTR lpszDir
, IDataObject
* lpDataObject
,
656 UINT uFlags
, LPITEMIDLIST
*lppidlLinks
)
658 FIXME("%p %s %p %08x %p\n", hWnd
, lpszDir
, lpDataObject
, uFlags
, lppidlLinks
);
662 /***********************************************************************
663 * SHOpenFolderAndSelectItems
669 SHOpenFolderAndSelectItems(LPITEMIDLIST pidlFolder
,
671 PCUITEMID_CHILD_ARRAY apidl
,
674 FIXME("SHOpenFolderAndSelectItems() stub\n");