Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1356 lines
39 KiB

  1. /*****************************************************************************
  2. *
  3. * (C) COPYRIGHT MICROSOFT CORPORATION, 2000
  4. *
  5. * TITLE: tmplutil.cpp
  6. *
  7. * VERSION: 1.0
  8. *
  9. * AUTHOR: LazarI
  10. *
  11. * DATE: 10-Mar-2000
  12. *
  13. * DESCRIPTION: Smart pointers, utility templates, etc...
  14. *
  15. *****************************************************************************/
  16. #include "precomp.h"
  17. #pragma hdrstop
  18. /*****************************************************************************
  19. IMPORTANT!
  20. all those headers should be included in the pch file before including tmplutil.h
  21. (in the same order!) in order to be able to compile this file.
  22. // some common headers
  23. #include <shlobj.h> // shell OM interfaces
  24. #include <shlwapi.h> // shell common API
  25. #include <winspool.h> // spooler
  26. #include <assert.h> // assert
  27. #include <commctrl.h> // common controls
  28. #include <lm.h> // Lan manager (netapi32.dll)
  29. #include <wininet.h> // inet core - necessary for INTERNET_MAX_HOST_NAME_LENGTH
  30. // some private shell headers
  31. #include <shlwapip.h> // private shell common API
  32. #include <shpriv.h> // private shell interfaces
  33. #include <comctrlp.h> // private common controls
  34. *****************************************************************************/
  35. #include "tmplutil.h"
  36. #define gszBackwardSlash TEXT('\\')
  37. #define gszLeadingSlashes TEXT("\\\\")
  38. /*****************************************************************************
  39. COMObjects_GetCount
  40. *****************************************************************************/
  41. static LONG g_lCOMObjectsCount = 0;
  42. LONG COMObjects_GetCount()
  43. {
  44. return g_lCOMObjectsCount;
  45. }
  46. HRESULT PinCurrentDLL()
  47. {
  48. HRESULT hr = S_OK;
  49. HINSTANCE hModuleSelf = NULL;
  50. // Let's get the handle of the current module -
  51. // the one this function belongs to.
  52. MEMORY_BASIC_INFORMATION mbi;
  53. if (VirtualQuery(reinterpret_cast<LPCVOID>(PinCurrentDLL), &mbi, sizeof(mbi)))
  54. {
  55. hModuleSelf = reinterpret_cast<HINSTANCE>(mbi.AllocationBase);
  56. }
  57. else
  58. {
  59. // VirtualQuery failed.
  60. hr = CreateHRFromWin32();
  61. }
  62. if (SUCCEEDED(hr))
  63. {
  64. // Get the module name and call LoadLibrary on it.
  65. TCHAR szModuleName[MAX_PATH];
  66. hr = SafeGetModuleFileName(hModuleSelf, szModuleName, ARRAYSIZE(szModuleName));
  67. if (SUCCEEDED(hr))
  68. {
  69. if (NULL == LoadLibrary(szModuleName))
  70. {
  71. // LoadLibrary failed.
  72. hr = CreateHRFromWin32();
  73. }
  74. }
  75. }
  76. return hr;
  77. }
  78. /*****************************************************************************
  79. PrinterSplitFullName
  80. Routine Description:
  81. splits a fully qualified printer connection name into server and printer name parts.
  82. Arguments:
  83. pszFullName - full qualifier printer name ('printer' or '\\server\printer')
  84. pszBuffer - scratch buffer used to store output strings.
  85. nMaxLength - the size of the scratch buffer in characters
  86. ppszServer - receives pointer to the server string. If it is a
  87. local printer, empty string is returned.
  88. ppszPrinter - receives a pointer to the printer string. OPTIONAL
  89. Return Value:
  90. returns S_OK on sucess or COM error otherwise
  91. *****************************************************************************/
  92. HRESULT PrinterSplitFullName(LPCTSTR pszFullName, TCHAR szBuffer[], int nMaxLength, LPCTSTR *ppszServer,LPCTSTR *ppszPrinter)
  93. {
  94. HRESULT hr = S_OK;
  95. lstrcpyn(szBuffer, pszFullName, nMaxLength);
  96. LPTSTR pszPrinter = szBuffer;
  97. if (pszFullName[0] != TEXT('\\') || pszFullName[1] != TEXT('\\'))
  98. {
  99. pszPrinter = szBuffer;
  100. *ppszServer = TEXT("");
  101. }
  102. else
  103. {
  104. *ppszServer = szBuffer;
  105. pszPrinter = _tcschr(*ppszServer + 2, TEXT('\\'));
  106. if (NULL == pszPrinter)
  107. {
  108. //
  109. // We've encountered a printer called "\\server"
  110. // (only two backslashes in the string). We'll treat
  111. // it as a local printer. We should never hit this,
  112. // but the spooler doesn't enforce this. We won't
  113. // format the string. Server is local, so set to szNULL.
  114. //
  115. pszPrinter = szBuffer;
  116. *ppszServer = TEXT("");
  117. }
  118. else
  119. {
  120. //
  121. // We found the third backslash; null terminate our
  122. // copy and set bRemote TRUE to format the string.
  123. //
  124. *pszPrinter++ = 0;
  125. }
  126. }
  127. if (ppszPrinter)
  128. {
  129. *ppszPrinter = pszPrinter;
  130. }
  131. return hr;
  132. }
  133. ////////////////////////////////////////////////
  134. //
  135. // class COleComInitializer
  136. //
  137. // smart OLE2, COM initializer - just declare
  138. // an instance wherever need to use COM, OLE2
  139. //
  140. COleComInitializer::COleComInitializer(BOOL bOleInit)
  141. : m_hr(E_FAIL),
  142. m_bOleInit(bOleInit)
  143. {
  144. if( m_bOleInit )
  145. {
  146. m_hr = OleInitialize(NULL);
  147. }
  148. else
  149. {
  150. m_hr = CoInitialize(NULL);
  151. }
  152. }
  153. COleComInitializer::~COleComInitializer()
  154. {
  155. if( SUCCEEDED(m_hr) )
  156. {
  157. if( m_bOleInit )
  158. {
  159. OleUninitialize();
  160. }
  161. else
  162. {
  163. CoUninitialize();
  164. }
  165. }
  166. }
  167. COleComInitializer::operator BOOL () const
  168. {
  169. if( FAILED(m_hr) )
  170. {
  171. return (RPC_E_CHANGED_MODE == m_hr);
  172. }
  173. else
  174. {
  175. return TRUE;
  176. }
  177. }
  178. ////////////////////////////////////////////////
  179. //
  180. // class CDllLoader
  181. //
  182. // smart DLL loader - calls LoadLibrary
  183. // FreeLibrary for you.
  184. //
  185. CDllLoader::CDllLoader(LPCTSTR pszDllName)
  186. : m_hLib(NULL)
  187. {
  188. m_hLib = LoadLibrary(pszDllName);
  189. }
  190. CDllLoader::~CDllLoader()
  191. {
  192. if( m_hLib )
  193. {
  194. FreeLibrary( m_hLib );
  195. m_hLib = NULL;
  196. }
  197. }
  198. CDllLoader::operator BOOL () const
  199. {
  200. return (NULL != m_hLib);
  201. }
  202. FARPROC CDllLoader::GetProcAddress( LPCSTR lpProcName )
  203. {
  204. if( m_hLib )
  205. {
  206. return ::GetProcAddress( m_hLib, lpProcName );
  207. }
  208. return NULL;
  209. }
  210. FARPROC CDllLoader::GetProcAddress( WORD wProcOrd )
  211. {
  212. if( m_hLib )
  213. {
  214. return ::GetProcAddress( m_hLib, (LPCSTR)MAKEINTRESOURCE(wProcOrd) );
  215. }
  216. return NULL;
  217. }
  218. ////////////////////////////////////////////////
  219. // class CCookiesHolder
  220. //
  221. // this a utility class which allows us to pass more
  222. // than one pointer through a single cookie.
  223. //
  224. CCookiesHolder::CCookiesHolder()
  225. : m_pCookies(NULL),
  226. m_uCount(0)
  227. {
  228. }
  229. CCookiesHolder::CCookiesHolder(UINT uCount)
  230. : m_pCookies(NULL),
  231. m_uCount(0)
  232. {
  233. SetCount(uCount);
  234. }
  235. CCookiesHolder::~CCookiesHolder()
  236. {
  237. SetCount(0);
  238. }
  239. BOOL CCookiesHolder::SetCount(UINT uCount)
  240. {
  241. BOOL bReturn = FALSE;
  242. if( uCount )
  243. {
  244. // reset first
  245. SetCount(0);
  246. // attempt to allocate memory for the cookies
  247. LPVOID *pCookies = new LPVOID[uCount];
  248. if( pCookies )
  249. {
  250. m_uCount = uCount;
  251. m_pCookies = pCookies;
  252. bReturn = TRUE;
  253. }
  254. }
  255. else
  256. {
  257. // zero means - reset
  258. if( m_pCookies )
  259. {
  260. delete[] m_pCookies;
  261. m_pCookies = NULL;
  262. m_uCount = 0;
  263. }
  264. bReturn = TRUE;
  265. }
  266. return bReturn;
  267. }
  268. ////////////////////////////////////////////////
  269. // class CPrintersAutoCompleteSource
  270. //
  271. // printer's autocomplete source impl.
  272. //
  273. QITABLE_DECLARE(CPrintersAutoCompleteSource)
  274. class CPrintersAutoCompleteSource: public CUnknownMT<QITABLE_GET(CPrintersAutoCompleteSource)>, // MT impl. of IUnknown
  275. public IEnumString, // string enumerator
  276. public IACList // autocomplete list generator
  277. {
  278. public:
  279. CPrintersAutoCompleteSource();
  280. ~CPrintersAutoCompleteSource();
  281. //////////////////
  282. // IUnknown
  283. //
  284. IMPLEMENT_IUNKNOWN()
  285. //////////////////
  286. // IEnumString
  287. //
  288. STDMETHODIMP Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched);
  289. STDMETHODIMP Skip(ULONG celt);
  290. STDMETHODIMP Reset(void);
  291. STDMETHODIMP Clone(IEnumString **ppenum) { return E_NOTIMPL; }
  292. //////////////////
  293. // IACList
  294. //
  295. STDMETHODIMP Expand(LPCOLESTR pszExpand);
  296. private:
  297. CAutoPtrArray<BYTE> m_spBufferPrinters;
  298. CAutoPtrArray<BYTE> m_spBufferShares;
  299. PRINTER_INFO_5 *m_pPI5;
  300. SHARE_INFO_1 *m_pSI1;
  301. ULONG m_ulCount;
  302. ULONG m_ulPos;
  303. TCHAR m_szServer[PRINTER_MAX_PATH];
  304. CRefPtrCOM<IEnumString> m_spCustomMRUEnum;
  305. BOOL _IsServerName(LPCTSTR psz, BOOL *pbPartial);
  306. static BOOL _IsMasqPrinter(const PRINTER_INFO_5 &pi5);
  307. static HRESULT _CreateCustomMRU(REFIID riid, void **ppv);
  308. static HRESULT _AddCustomMRU(LPCTSTR psz);
  309. };
  310. // QueryInterface table
  311. QITABLE_BEGIN(CPrintersAutoCompleteSource)
  312. QITABENT(CPrintersAutoCompleteSource, IEnumString), // IID_IEnumString
  313. QITABENT(CPrintersAutoCompleteSource, IACList), // IID_IACList
  314. QITABLE_END()
  315. #define SZ_REGKEY_PRNCONNECTMRU L"Printers\\Settings\\Wizard\\ConnectMRU"
  316. // comctrlp.h defines this as AddMRUStringW preventing us from using the IACLCustomMRU interface
  317. #undef AddMRUString
  318. HRESULT CPrintersACS_CreateInstance(IUnknown **ppUnk)
  319. {
  320. HRESULT hr = E_INVALIDARG;
  321. if( ppUnk )
  322. {
  323. hr = PinCurrentDLL();
  324. if( SUCCEEDED(hr) )
  325. {
  326. CPrintersAutoCompleteSource *pObj = new CPrintersAutoCompleteSource();
  327. hr = pObj ? S_OK : E_OUTOFMEMORY;
  328. if( SUCCEEDED(hr) )
  329. {
  330. hr = pObj->QueryInterface(IID_IUnknown, (void**)ppUnk);
  331. pObj->Release();
  332. }
  333. }
  334. }
  335. return hr;
  336. }
  337. CPrintersAutoCompleteSource::CPrintersAutoCompleteSource():
  338. m_pPI5(NULL),
  339. m_ulCount(0),
  340. m_ulPos(0)
  341. {
  342. InterlockedIncrement(&g_lCOMObjectsCount);
  343. }
  344. CPrintersAutoCompleteSource::~CPrintersAutoCompleteSource()
  345. {
  346. InterlockedDecrement(&g_lCOMObjectsCount);
  347. }
  348. //////////////////
  349. // IUnknown
  350. //
  351. STDMETHODIMP CPrintersAutoCompleteSource::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
  352. {
  353. HRESULT hr = S_FALSE;
  354. if( pceltFetched )
  355. {
  356. *pceltFetched = 0;
  357. }
  358. if( m_ulCount && (m_pPI5 || m_pSI1) )
  359. {
  360. ULONG cFetched = 0;
  361. if( m_pPI5 )
  362. {
  363. // printers enumerated
  364. for( ; m_ulPos < m_ulCount && cFetched < celt; m_ulPos++ )
  365. {
  366. // if this is a valid (non-masq) printer just return it
  367. if( m_pPI5[m_ulPos].pPrinterName[0] && SUCCEEDED(SHStrDup(m_pPI5[m_ulPos].pPrinterName, &rgelt[cFetched])) )
  368. {
  369. cFetched++;
  370. }
  371. }
  372. }
  373. else
  374. {
  375. // shares enumerated
  376. TCHAR szBuffer[PRINTER_MAX_PATH];
  377. for( ; m_ulPos < m_ulCount && cFetched < celt; m_ulPos++ )
  378. {
  379. // if this is a valid printer share name, just return it
  380. if( m_pSI1[m_ulPos].shi1_netname[0] &&
  381. -1 != wnsprintf(szBuffer, ARRAYSIZE(szBuffer), TEXT("%s\\%s"),
  382. m_szServer, m_pSI1[m_ulPos].shi1_netname) &&
  383. SUCCEEDED(SHStrDup(szBuffer, &rgelt[cFetched])) )
  384. {
  385. cFetched++;
  386. }
  387. }
  388. }
  389. if( pceltFetched )
  390. {
  391. *pceltFetched = cFetched;
  392. }
  393. hr = cFetched == celt ? S_OK : S_FALSE;
  394. }
  395. else
  396. {
  397. // use our custom MRU if any...
  398. if( m_spCustomMRUEnum )
  399. {
  400. hr = m_spCustomMRUEnum->Next(celt, rgelt, pceltFetched);
  401. }
  402. }
  403. return hr;
  404. }
  405. STDMETHODIMP CPrintersAutoCompleteSource::Skip(ULONG celt)
  406. {
  407. HRESULT hr = S_FALSE;
  408. if( m_ulCount && (m_pPI5 || m_pSI1) )
  409. {
  410. hr = ((m_ulPos + celt) <= m_ulCount) ? S_OK : S_FALSE;
  411. m_ulPos = min(m_ulPos + celt, m_ulCount);
  412. }
  413. else
  414. {
  415. // use our custom MRU if any...
  416. if( m_spCustomMRUEnum )
  417. {
  418. hr = m_spCustomMRUEnum->Skip(celt);
  419. }
  420. }
  421. return hr;
  422. }
  423. STDMETHODIMP CPrintersAutoCompleteSource::Reset(void)
  424. {
  425. HRESULT hr = S_OK;
  426. if( m_ulCount && (m_pPI5 || m_pSI1) )
  427. {
  428. m_ulPos = 0;
  429. }
  430. else
  431. {
  432. // use our custom MRU if any...
  433. if( m_spCustomMRUEnum )
  434. {
  435. hr = m_spCustomMRUEnum->Reset();
  436. }
  437. }
  438. return hr;
  439. }
  440. typedef bool PI5_less_type(const PRINTER_INFO_5 &i1, const PRINTER_INFO_5 &i2);
  441. static bool PI5_less(const PRINTER_INFO_5 &i1, const PRINTER_INFO_5 &i2)
  442. {
  443. return (lstrcmp(i1.pPrinterName, i2.pPrinterName) < 0);
  444. }
  445. typedef bool SI1_less_type(const SHARE_INFO_1 &i1, const SHARE_INFO_1 &i2);
  446. static bool SI1_less(const SHARE_INFO_1 &i1, const SHARE_INFO_1 &i2)
  447. {
  448. return (lstrcmp(i1.shi1_netname, i2.shi1_netname) < 0);
  449. }
  450. //////////////////
  451. // IACList
  452. //
  453. STDMETHODIMP CPrintersAutoCompleteSource::Expand(LPCOLESTR pszExpand)
  454. {
  455. HRESULT hr = E_FAIL;
  456. DWORD cReturned = 0;
  457. BOOL bPartial = FALSE;
  458. // assume this is not a server name, so reset the list first
  459. m_pPI5 = NULL;
  460. m_pSI1 = NULL;
  461. m_spBufferPrinters = NULL;
  462. m_spBufferShares = NULL;
  463. m_ulCount = m_ulPos = 0;
  464. m_szServer[0] = 0;
  465. m_spCustomMRUEnum = NULL;
  466. if( _IsServerName(pszExpand, &bPartial) )
  467. {
  468. // make a copy of the print buffer & cut off the last slash
  469. TCHAR szBuffer[PRINTER_MAX_PATH];
  470. lstrcpyn(szBuffer, pszExpand, ARRAYSIZE(szBuffer));
  471. szBuffer[lstrlen(szBuffer)-1] = 0;
  472. // enum the printers on that server
  473. if( SUCCEEDED(hr = ShellServices::EnumPrintersWrap(PRINTER_ENUM_NAME, 5, szBuffer, &m_spBufferPrinters, &cReturned)) && cReturned )
  474. {
  475. m_ulPos = 0;
  476. m_ulCount = cReturned;
  477. m_pPI5 = m_spBufferPrinters.GetPtrAs<PRINTER_INFO_5*>();
  478. lstrcpyn(m_szServer, szBuffer, ARRAYSIZE(m_szServer));
  479. // successful expand - remember the MRU string
  480. _AddCustomMRU(szBuffer);
  481. // traverse to check for masq printers
  482. for( ULONG ulPos = 0; ulPos < m_ulCount; ulPos++ )
  483. {
  484. if( _IsMasqPrinter(m_pPI5[ulPos]) )
  485. {
  486. // we don't really care for masq printer's since they are
  487. // an obsolete concept and can't be truly shared/connected to
  488. m_pPI5[ulPos].pPrinterName = TEXT("");
  489. }
  490. }
  491. // invoke STL to sort
  492. std::sort<PRINTER_INFO_5*, PI5_less_type*>(m_pPI5, m_pPI5 + m_ulCount, PI5_less);
  493. }
  494. else
  495. {
  496. // enumeration of the printers failed, this could be because the remote spooler is down
  497. // or it is a downlevel print provider (win9x, novell, linux, sun...) in this case we
  498. // would like to try enumerating the shares as possible connection points.
  499. if( SUCCEEDED(hr = ShellServices::NetAPI_EnumShares(szBuffer, 1, &m_spBufferShares, &cReturned)) && cReturned )
  500. {
  501. m_ulPos = 0;
  502. m_ulCount = cReturned;
  503. m_pSI1 = m_spBufferShares.GetPtrAs<SHARE_INFO_1*>();
  504. lstrcpyn(m_szServer, szBuffer, ARRAYSIZE(m_szServer));
  505. // successful expand - remember the MRU string
  506. _AddCustomMRU(szBuffer);
  507. // traverse to remove the non-printer shares
  508. for( ULONG ulPos = 0; ulPos < m_ulCount; ulPos++ )
  509. {
  510. if( STYPE_PRINTQ != m_pSI1[ulPos].shi1_type )
  511. {
  512. // this is a non-printer share, remove
  513. m_pSI1[ulPos].shi1_netname[0] = 0;
  514. }
  515. }
  516. // invoke STL to sort
  517. std::sort<SHARE_INFO_1*, SI1_less_type*>(m_pSI1, m_pSI1 + m_ulCount, SI1_less);
  518. }
  519. }
  520. }
  521. else
  522. {
  523. if( bPartial )
  524. {
  525. // use our custom MRU for autocomplete
  526. hr = _CreateCustomMRU(IID_IEnumString, m_spCustomMRUEnum.GetPPV());
  527. }
  528. }
  529. return hr;
  530. }
  531. BOOL CPrintersAutoCompleteSource::_IsServerName(LPCTSTR psz, BOOL *pbPartial)
  532. {
  533. ASSERT(pbPartial);
  534. BOOL bRet = FALSE;
  535. int i, iSepCount = 0, iLen = lstrlen(psz);
  536. for( i=0; i<iLen; i++ )
  537. {
  538. if( psz[i] == gszBackwardSlash )
  539. {
  540. iSepCount++;
  541. }
  542. }
  543. if( (1 == iSepCount && psz[0] == gszBackwardSlash) ||
  544. (2 == iSepCount && psz[0] == gszBackwardSlash && psz[1] == gszBackwardSlash) )
  545. {
  546. *pbPartial = TRUE;
  547. }
  548. if( 3 < iLen &&
  549. 3 == iSepCount &&
  550. psz[0] == gszBackwardSlash &&
  551. psz[1] == gszBackwardSlash &&
  552. psz[iLen-1] == gszBackwardSlash )
  553. {
  554. bRet = TRUE;
  555. }
  556. return bRet;
  557. }
  558. BOOL CPrintersAutoCompleteSource::_IsMasqPrinter(const PRINTER_INFO_5 &pi5)
  559. {
  560. // this is a little bit hacky, but there is no other way to tell the masq printer
  561. // in the remote case. the spooler APIs suffer from some severe design flaws and
  562. // we have to put up with that.
  563. LPCTSTR pszServer;
  564. LPCTSTR pszPrinter;
  565. TCHAR szScratch[PRINTER_MAX_PATH];
  566. // split the full printer name into its components.
  567. if( SUCCEEDED(PrinterSplitFullName(pi5.pPrinterName,
  568. szScratch, ARRAYSIZE(szScratch), &pszServer, &pszPrinter)) )
  569. {
  570. return (0 == _tcsnicmp(pszPrinter, gszLeadingSlashes, _tcslen(gszLeadingSlashes)));
  571. }
  572. else
  573. {
  574. return FALSE;
  575. }
  576. }
  577. HRESULT CPrintersAutoCompleteSource::_CreateCustomMRU(REFIID riid, void **ppv)
  578. {
  579. HRESULT hr = E_INVALIDARG;
  580. CRefPtrCOM<IACLCustomMRU> spCustomMRU;
  581. if( ppv &&
  582. SUCCEEDED(hr = CoCreateInstance(CLSID_ACLCustomMRU, NULL,
  583. CLSCTX_INPROC_SERVER, IID_IACLCustomMRU, spCustomMRU.GetPPV())) &&
  584. SUCCEEDED(hr = spCustomMRU->Initialize(SZ_REGKEY_PRNCONNECTMRU, 26)) )
  585. {
  586. // query the specified interface
  587. hr = spCustomMRU->QueryInterface(riid, ppv);
  588. }
  589. return hr;
  590. }
  591. HRESULT CPrintersAutoCompleteSource::_AddCustomMRU(LPCTSTR psz)
  592. {
  593. HRESULT hr = E_INVALIDARG;
  594. CRefPtrCOM<IACLCustomMRU> spCustomMRU;
  595. if( psz &&
  596. SUCCEEDED(hr = _CreateCustomMRU(IID_IACLCustomMRU, spCustomMRU.GetPPV())) )
  597. {
  598. // just remember the MRU string
  599. hr = spCustomMRU->AddMRUString(psz);
  600. }
  601. return hr;
  602. }
  603. ////////////////////////////////////////////////
  604. // shell related services
  605. namespace ShellServices
  606. {
  607. // creates a PIDL to a printer in the local printers folder by using ParseDisplayName
  608. // see the description of CreatePrinterPIDL below.
  609. HRESULT CreatePrinterPIDL_Parse(HWND hwnd, LPCTSTR pszPrinterName, IShellFolder **ppLocalPrnFolder, LPITEMIDLIST *ppidlPrinter)
  610. {
  611. HRESULT hr = E_UNEXPECTED;
  612. CRefPtrCOM<IShellFolder> spDesktopFolder;
  613. CRefPtrCOM<IShellFolder> spPrnFolder;
  614. CAutoPtrPIDL pidlPrinters;
  615. CAutoPtrPIDL pidlPrinter;
  616. // attempt to get the fully qualified name (for parsing) of the printers folder
  617. if( SUCCEEDED(hr = SHGetDesktopFolder(&spDesktopFolder)) &&
  618. SUCCEEDED(hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidlPrinters)) &&
  619. SUCCEEDED(hr = spDesktopFolder->BindToObject(pidlPrinters, 0, IID_IShellFolder, spPrnFolder.GetPPV())) )
  620. {
  621. ULONG uEaten = 0;
  622. ULONG uAttributes = SFGAO_DROPTARGET;
  623. // attempt parse the printer name into PIDL
  624. hr = spPrnFolder->ParseDisplayName(hwnd, 0, (LPOLESTR )pszPrinterName,
  625. &uEaten, &pidlPrinter, &uAttributes);
  626. if( SUCCEEDED(hr) )
  627. {
  628. if( ppLocalPrnFolder )
  629. {
  630. // return the local printers folder
  631. *ppLocalPrnFolder = spPrnFolder.Detach();
  632. }
  633. if( ppidlPrinter )
  634. {
  635. // return the printer PIDL
  636. *ppidlPrinter = pidlPrinter.Detach();
  637. }
  638. }
  639. }
  640. return hr;
  641. }
  642. // creates a PIDL to a printer in the local printers folder by enumerating the printers
  643. // see the description of CreatePrinterPIDL below.
  644. HRESULT CreatePrinterPIDL_Enum(HWND hwnd, LPCTSTR pszPrinterName, IShellFolder **ppLocalPrnFolder, LPITEMIDLIST *ppidlPrinter)
  645. {
  646. HRESULT hr = E_UNEXPECTED;
  647. CRefPtrCOM<IShellFolder> spDesktopFolder;
  648. CRefPtrCOM<IShellFolder> spPrnFolder;
  649. CRefPtrCOM<IEnumIDList> spPrnEnum;
  650. CAutoPtrPIDL pidlPrinters;
  651. STRRET str = {0};
  652. // attempt to get the fully qualified name (for parsing) of the printers folder
  653. if( SUCCEEDED(hr = SHGetDesktopFolder(&spDesktopFolder)) &&
  654. SUCCEEDED(hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidlPrinters)) &&
  655. SUCCEEDED(hr = spDesktopFolder->BindToObject(pidlPrinters, 0, IID_IShellFolder, spPrnFolder.GetPPV())) &&
  656. SUCCEEDED(hr = spPrnFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS, &spPrnEnum)) )
  657. {
  658. TCHAR szBuffer[PRINTER_MAX_PATH];
  659. CAutoPtrPIDL pidlPrinter;
  660. ULONG uFetched = 0;
  661. for( ;; )
  662. {
  663. // get next printer
  664. hr = spPrnEnum->Next(1, &pidlPrinter, &uFetched);
  665. if( S_OK != hr )
  666. {
  667. // no more printers, or error
  668. break;
  669. }
  670. if( SUCCEEDED(hr = spPrnFolder->GetDisplayNameOf(pidlPrinter, SHGDN_FORPARSING, &str)) &&
  671. SUCCEEDED(hr = StrRetToBuf(&str, pidlPrinter, szBuffer, COUNTOF(szBuffer))) &&
  672. !lstrcmp(szBuffer, pszPrinterName) )
  673. {
  674. // found!
  675. if( ppLocalPrnFolder )
  676. {
  677. // return the local printers folder
  678. *ppLocalPrnFolder = spPrnFolder.Detach();
  679. }
  680. if( ppidlPrinter )
  681. {
  682. // return the printer PIDL
  683. *ppidlPrinter = pidlPrinter.Detach();
  684. }
  685. break;
  686. }
  687. // release the PIDL
  688. pidlPrinter = NULL;
  689. }
  690. if( hr == S_FALSE )
  691. {
  692. // printer name not found. setup the correct HRESULT.
  693. hr = HRESULT_FROM_WIN32(ERROR_INVALID_PRINTER_NAME);
  694. }
  695. }
  696. return hr;
  697. }
  698. // creates a PIDL to a printer in the local printers folder.
  699. // args:
  700. // [in] hwnd - window handle (in case we need to show UI - message box)
  701. // [in] pszPrinterName - full printer name.
  702. // [out] ppLocalPrnFolder - the printers folder (optional - may be NULL)
  703. // [out] ppidlPrinter - the PIDL of the printer pointed by pszPrinterName (optional - may be NULL)
  704. //
  705. // remarks:
  706. // pszPrinterName should be fully qualified printer name, i.e. if printer connection it should be
  707. // like "\\server\printer", if local printer just the printer name.
  708. //
  709. // returns:
  710. // S_OK on success, or OLE2 error otherwise
  711. HRESULT CreatePrinterPIDL(HWND hwnd, LPCTSTR pszPrinterName, IShellFolder **ppLocalPrnFolder, LPITEMIDLIST *ppidlPrinter)
  712. {
  713. // attempt to obtain the printer PIDL by parsing first - it's much quicker.
  714. HRESULT hr = CreatePrinterPIDL_Parse(hwnd, pszPrinterName, ppLocalPrnFolder, ppidlPrinter);
  715. if( E_NOTIMPL == hr )
  716. {
  717. // if parsing is not implemented then go ahead and enum the printers - slower.
  718. hr = CreatePrinterPIDL_Enum(hwnd, pszPrinterName, ppLocalPrnFolder, ppidlPrinter);
  719. }
  720. return hr;
  721. }
  722. // loads a popup menu
  723. HMENU LoadPopupMenu(HINSTANCE hInstance, UINT id, UINT uSubOffset)
  724. {
  725. HMENU hMenuPopup = NULL;
  726. CAutoHandleMenu shMenuParent = LoadMenu(hInstance, MAKEINTRESOURCE(id));
  727. if( shMenuParent && (hMenuPopup = GetSubMenu(shMenuParent, uSubOffset)) )
  728. {
  729. // tear off our submenu before destroying the parent
  730. RemoveMenu(shMenuParent, uSubOffset, MF_BYPOSITION);
  731. }
  732. return hMenuPopup;
  733. }
  734. // initializes enum printer's autocomplete
  735. HRESULT InitPrintersAutoComplete(HWND hwndEdit)
  736. {
  737. HRESULT hr = E_INVALIDARG;
  738. if( hwndEdit )
  739. {
  740. // create an autocomplete object
  741. CRefPtrCOM<IAutoComplete> spAC; // auto complete interface
  742. CRefPtrCOM<IAutoComplete2> spAC2; // auto complete 2 interface
  743. CRefPtrCOM<IUnknown> spACS; // auto complete source (IEnumString & IACList)
  744. // initialize all the objects & hook them up
  745. if( SUCCEEDED(hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,
  746. IID_IAutoComplete, (void**)&spAC)) &&
  747. SUCCEEDED(hr = CPrintersACS_CreateInstance(&spACS)) &&
  748. SUCCEEDED(hr = spAC->Init(hwndEdit, spACS, NULL, NULL)) &&
  749. SUCCEEDED(hr = spAC->QueryInterface(IID_IAutoComplete2, (void **)&spAC2)) &&
  750. SUCCEEDED(hr = spAC2->SetOptions(ACO_AUTOSUGGEST)) )
  751. {
  752. hr = S_OK;
  753. }
  754. }
  755. return hr;
  756. }
  757. // helpers for the Enum* idioms
  758. HRESULT EnumPrintersWrap(DWORD dwFlags, DWORD dwLevel, LPCTSTR pszName, BYTE **ppBuffer, DWORD *pcReturned)
  759. {
  760. HRESULT hr = E_INVALIDARG;
  761. if( ppBuffer && pcReturned )
  762. {
  763. int iTry = -1;
  764. DWORD cbNeeded = 0;
  765. DWORD cReturned = 0;
  766. CAutoPtrArray<BYTE> pData;
  767. BOOL bStatus = FALSE;
  768. for( ;; )
  769. {
  770. if( iTry++ >= ENUM_MAX_RETRY )
  771. {
  772. // max retry count reached. this is also
  773. // considered out of memory case
  774. pData = NULL;
  775. break;
  776. }
  777. // call EnumPrinters...
  778. bStatus = EnumPrinters(dwFlags, const_cast<LPTSTR>(pszName), dwLevel,
  779. pData, cbNeeded, &cbNeeded, &cReturned);
  780. if( !bStatus && ERROR_INSUFFICIENT_BUFFER == GetLastError() && cbNeeded )
  781. {
  782. // buffer too small case
  783. pData = new BYTE[cbNeeded];
  784. continue;
  785. }
  786. break;
  787. }
  788. // setup the error code properly
  789. hr = bStatus ? S_OK : GetLastError() != ERROR_SUCCESS ? HRESULT_FROM_WIN32(GetLastError()) :
  790. !pData ? E_OUTOFMEMORY : E_FAIL;
  791. // setup the out parameters
  792. if( SUCCEEDED(hr) )
  793. {
  794. *ppBuffer = pData.Detach();
  795. *pcReturned = cReturned;
  796. }
  797. else
  798. {
  799. *ppBuffer = NULL;
  800. *pcReturned = 0;
  801. }
  802. }
  803. return hr;
  804. }
  805. // helpers for the GetJob API - see the SDK for mor information.
  806. HRESULT GetJobWrap(HANDLE hPrinter, DWORD JobId, DWORD dwLevel, BYTE **ppBuffer, DWORD *pcReturned)
  807. {
  808. HRESULT hr = E_INVALIDARG;
  809. if( ppBuffer && pcReturned )
  810. {
  811. int iTry = -1;
  812. DWORD cbNeeded = 0;
  813. CAutoPtrArray<BYTE> pData;
  814. BOOL bStatus = FALSE;
  815. for( ;; )
  816. {
  817. if( iTry++ >= ENUM_MAX_RETRY )
  818. {
  819. // max retry count reached. this is also
  820. // considered out of memory case
  821. pData = NULL;
  822. break;
  823. }
  824. // call GetJob...
  825. bStatus = GetJob(hPrinter, JobId, dwLevel, pData, cbNeeded, &cbNeeded);
  826. if( !bStatus && ERROR_INSUFFICIENT_BUFFER == GetLastError() && cbNeeded )
  827. {
  828. // buffer too small case
  829. pData = new BYTE[cbNeeded];
  830. continue;
  831. }
  832. break;
  833. }
  834. // setup the error code properly
  835. hr = bStatus ? S_OK : GetLastError() != ERROR_SUCCESS ? HRESULT_FROM_WIN32(GetLastError()) :
  836. !pData ? E_OUTOFMEMORY : E_FAIL;
  837. // setup the out parameters
  838. if( SUCCEEDED(hr) )
  839. {
  840. *ppBuffer = pData.Detach();
  841. *pcReturned = cbNeeded;
  842. }
  843. else
  844. {
  845. *ppBuffer = NULL;
  846. *pcReturned = 0;
  847. }
  848. }
  849. return hr;
  850. }
  851. typedef NET_API_STATUS
  852. type_NetAPI_NetShareEnum(
  853. LPWSTR servername,
  854. DWORD level,
  855. LPBYTE *bufptr,
  856. DWORD prefmaxlen,
  857. LPDWORD entriesread,
  858. LPDWORD totalentries,
  859. LPDWORD resume_handle
  860. );
  861. typedef NET_API_STATUS
  862. type_NetAPI_NetApiBufferFree(
  863. LPVOID Buffer
  864. );
  865. typedef NET_API_STATUS
  866. type_NetAPI_NetApiBufferSize(
  867. LPVOID Buffer,
  868. LPDWORD ByteCount
  869. );
  870. // enumerates the shared resources on a server, for more info see SDK for NetShareEnum API.
  871. HRESULT NetAPI_EnumShares(LPCTSTR pszServer, DWORD dwLevel, BYTE **ppBuffer, DWORD *pcReturned)
  872. {
  873. HRESULT hr = E_INVALIDARG;
  874. if( ppBuffer && pcReturned )
  875. {
  876. hr = E_FAIL;
  877. *pcReturned = 0;
  878. *ppBuffer = NULL;
  879. LPBYTE pNetBuf = NULL;
  880. DWORD dwRead, dwTemp;
  881. CDllLoader dll(TEXT("netapi32.dll"));
  882. if( dll )
  883. {
  884. // netapi32.dll loaded here...
  885. type_NetAPI_NetShareEnum *pfnNetShareEnum = (type_NetAPI_NetShareEnum *)dll.GetProcAddress("NetShareEnum");
  886. type_NetAPI_NetApiBufferSize *pfnNetApiBufferSize = (type_NetAPI_NetApiBufferSize *)dll.GetProcAddress("NetApiBufferSize");
  887. type_NetAPI_NetApiBufferFree *pfnNetApiBufferFree = (type_NetAPI_NetApiBufferFree *)dll.GetProcAddress("NetApiBufferFree");
  888. if( pfnNetShareEnum && pfnNetApiBufferSize && pfnNetApiBufferFree &&
  889. NERR_Success == pfnNetShareEnum(const_cast<LPTSTR>(pszServer), dwLevel,
  890. &pNetBuf, MAX_PREFERRED_LENGTH, &dwRead, &dwTemp, NULL) &&
  891. dwRead && pNetBuf &&
  892. NERR_Success == pfnNetApiBufferSize(pNetBuf, &dwTemp) )
  893. {
  894. *ppBuffer = new BYTE[dwTemp];
  895. if( *ppBuffer )
  896. {
  897. // copy the bits first
  898. memcpy(*ppBuffer, pNetBuf, dwTemp);
  899. // adjust the pointers here - a little bit ugly, but works
  900. for( DWORD dw = 0; dw < dwRead; dw++ )
  901. {
  902. // adjust shi1_netname
  903. reinterpret_cast<SHARE_INFO_1*>(*ppBuffer)[dw].shi1_netname =
  904. reinterpret_cast<LPWSTR>(
  905. (*ppBuffer) +
  906. (reinterpret_cast<BYTE*>(
  907. reinterpret_cast<SHARE_INFO_1*>(pNetBuf)[dw].shi1_netname) -
  908. pNetBuf));
  909. // adjust shi1_remark
  910. reinterpret_cast<SHARE_INFO_1*>(*ppBuffer)[dw].shi1_remark =
  911. reinterpret_cast<LPWSTR>(
  912. (*ppBuffer) +
  913. (reinterpret_cast<BYTE*>(
  914. reinterpret_cast<SHARE_INFO_1*>(pNetBuf)[dw].shi1_remark) -
  915. pNetBuf));
  916. }
  917. // number of structures returned
  918. *pcReturned = dwRead;
  919. }
  920. hr = ((*ppBuffer) ? S_OK : E_OUTOFMEMORY);
  921. CHECK(NERR_Success == pfnNetApiBufferFree(pNetBuf));
  922. }
  923. }
  924. }
  925. if( E_FAIL == hr && ERROR_SUCCESS != GetLastError() )
  926. {
  927. // if failed, let's be more spcific about what the error is...
  928. hr = HRESULT_FROM_WIN32(GetLastError());
  929. }
  930. return hr;
  931. }
  932. } // namespace ShellServices
  933. // utility functions
  934. HRESULT LoadXMLDOMDoc(LPCTSTR pszURL, IXMLDOMDocument **ppXMLDoc)
  935. {
  936. HRESULT hr = E_INVALIDARG;
  937. CRefPtrCOM<IXMLDOMDocument> spXMLDoc;
  938. if( pszURL && ppXMLDoc )
  939. {
  940. *ppXMLDoc = NULL;
  941. // create an instance of XMLDOM
  942. hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void **)&spXMLDoc);
  943. if( SUCCEEDED(hr) )
  944. {
  945. CComVariant xmlSource(pszURL);
  946. if( VT_BSTR == xmlSource.vt )
  947. {
  948. // just load the XML document here
  949. VARIANT_BOOL fIsSuccessful = VARIANT_TRUE;
  950. hr = spXMLDoc->load(xmlSource, &fIsSuccessful);
  951. if( S_FALSE == hr || VARIANT_FALSE == fIsSuccessful )
  952. {
  953. // this isn't a valid XML file.
  954. hr = E_FAIL;
  955. }
  956. else
  957. {
  958. // everything looks successful here - just return the XML document
  959. *ppXMLDoc = spXMLDoc.Detach();
  960. }
  961. }
  962. else
  963. {
  964. // xmlSource failed to allocate the string
  965. hr = E_OUTOFMEMORY;
  966. }
  967. }
  968. }
  969. return hr;
  970. }
  971. HRESULT CreateStreamFromURL(LPCTSTR pszURL, IStream **pps)
  972. {
  973. HRESULT hr = E_INVALIDARG;
  974. if( pszURL && pps )
  975. {
  976. *pps = NULL;
  977. TCHAR szBuf[INTERNET_MAX_SCHEME_LENGTH];
  978. DWORD cch = ARRAYSIZE(szBuf);
  979. if( SUCCEEDED(hr = CoInternetParseUrl(pszURL, PARSE_SCHEMA, 0, szBuf, cch, &cch, 0)) &&
  980. 0 == lstrcmp(szBuf, TEXT("res")) )
  981. {
  982. // check if this is a res:// URL to handle explicitly since
  983. // this protocol doesn't report filename and therefore can't
  984. // be used in conditions where caching is required - we can't
  985. // call URLOpenBlockingStream - use alternatives.
  986. // not impl. yet...
  987. ASSERT(FALSE);
  988. }
  989. hr = URLOpenBlockingStream(NULL, pszURL, pps, 0, NULL);
  990. }
  991. return hr;
  992. }
  993. HRESULT CreateStreamFromResource(LPCTSTR pszModule, LPCTSTR pszResType, LPCTSTR pszResName, IStream **pps)
  994. {
  995. HRESULT hr = E_INVALIDARG;
  996. if( pszResType && pszResName )
  997. {
  998. hr = E_FAIL;
  999. *pps = NULL;
  1000. HINSTANCE hModule = NULL;
  1001. if( (NULL == pszModule) || (hModule = LoadLibrary(pszModule)) )
  1002. {
  1003. HRSRC hHint = NULL;
  1004. ULONG uSize = 0;
  1005. if( (hHint = FindResource(hModule, pszResName, pszResType)) &&
  1006. (uSize = SizeofResource(hModule, hHint)) )
  1007. {
  1008. HGLOBAL hResData = LoadResource(hModule, hHint);
  1009. if( hResData )
  1010. {
  1011. LPVOID lpResData = LockResource(hResData);
  1012. if( lpResData )
  1013. {
  1014. if( (*pps = SHCreateMemStream(reinterpret_cast<LPBYTE>(lpResData), uSize)) )
  1015. {
  1016. hr = S_OK;
  1017. }
  1018. UnlockResource(lpResData);
  1019. }
  1020. FreeResource(hResData);
  1021. }
  1022. }
  1023. }
  1024. if( hModule )
  1025. {
  1026. FreeLibrary(hModule);
  1027. }
  1028. }
  1029. return hr;
  1030. }
  1031. HRESULT Gdiplus2HRESULT(Gdiplus::Status status)
  1032. {
  1033. // can't think of a better way to do this now
  1034. HRESULT hr = E_FAIL;
  1035. switch( status )
  1036. {
  1037. case Gdiplus::Ok:
  1038. hr = S_OK;
  1039. break;
  1040. case Gdiplus::InvalidParameter:
  1041. hr = E_INVALIDARG;
  1042. break;
  1043. case Gdiplus::OutOfMemory:
  1044. hr = E_OUTOFMEMORY;
  1045. break;
  1046. case Gdiplus::InsufficientBuffer:
  1047. hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
  1048. break;
  1049. case Gdiplus::Aborted:
  1050. hr = E_ABORT;
  1051. break;
  1052. case Gdiplus::ObjectBusy:
  1053. hr = E_PENDING;
  1054. break;
  1055. case Gdiplus::FileNotFound:
  1056. hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  1057. break;
  1058. case Gdiplus::AccessDenied:
  1059. hr = E_ACCESSDENIED;
  1060. break;
  1061. case Gdiplus::UnknownImageFormat:
  1062. hr = HRESULT_FROM_WIN32(ERROR_INVALID_PIXEL_FORMAT);
  1063. break;
  1064. case Gdiplus::NotImplemented:
  1065. hr = E_NOTIMPL;
  1066. break;
  1067. case Gdiplus::Win32Error:
  1068. hr = HRESULT_FROM_WIN32(GetLastError());
  1069. break;
  1070. case Gdiplus::ValueOverflow:
  1071. case Gdiplus::FontFamilyNotFound:
  1072. case Gdiplus::FontStyleNotFound:
  1073. case Gdiplus::NotTrueTypeFont:
  1074. case Gdiplus::UnsupportedGdiplusVersion:
  1075. case Gdiplus::GdiplusNotInitialized:
  1076. case Gdiplus::WrongState:
  1077. break;
  1078. }
  1079. return hr;
  1080. }
  1081. HRESULT LoadAndScaleBmp(LPCTSTR pszURL, UINT nWidth, UINT nHeight, Gdiplus::Bitmap **ppBmp)
  1082. {
  1083. CRefPtrCOM<IStream> spStream;
  1084. HRESULT hr = CreateStreamFromURL(pszURL, &spStream);
  1085. if( SUCCEEDED(hr) )
  1086. {
  1087. hr = LoadAndScaleBmp(spStream, nWidth, nHeight, ppBmp);
  1088. }
  1089. return hr;
  1090. }
  1091. HRESULT LoadAndScaleBmp(IStream *pStream, UINT nWidth, UINT nHeight, Gdiplus::Bitmap **ppBmp)
  1092. {
  1093. HRESULT hr = E_INVALIDARG;
  1094. if( pStream && nWidth && nHeight && ppBmp )
  1095. {
  1096. hr = E_FAIL;
  1097. *ppBmp = NULL;
  1098. Gdiplus::Bitmap bmp(pStream);
  1099. if( SUCCEEDED(hr = Gdiplus2HRESULT(bmp.GetLastStatus())) )
  1100. {
  1101. hr = E_OUTOFMEMORY;
  1102. CAutoPtr<Gdiplus::Bitmap> spBmpNew = new Gdiplus::Bitmap(nWidth, nHeight);
  1103. if( spBmpNew && SUCCEEDED(hr = Gdiplus2HRESULT(spBmpNew->GetLastStatus())) )
  1104. {
  1105. Gdiplus::Graphics g(spBmpNew);
  1106. if( SUCCEEDED(hr = Gdiplus2HRESULT(g.GetLastStatus())) )
  1107. {
  1108. if( SUCCEEDED(hr = g.DrawImage(&bmp, 0, 0, nWidth, nHeight)) )
  1109. {
  1110. *ppBmp = spBmpNew.Detach();
  1111. hr = S_OK;
  1112. }
  1113. }
  1114. }
  1115. }
  1116. }
  1117. return hr;
  1118. }
  1119. //
  1120. // This function is trying to get the last active popup of the top
  1121. // level owner of the current thread active window.
  1122. //
  1123. HRESULT GetCurrentThreadLastPopup(HWND *phwnd)
  1124. {
  1125. HRESULT hr = E_INVALIDARG;
  1126. if( phwnd )
  1127. {
  1128. hr = E_FAIL;
  1129. if( NULL == *phwnd )
  1130. {
  1131. // if *phwnd is NULL then get the current thread active window
  1132. GUITHREADINFO ti = {0};
  1133. ti.cbSize = sizeof(ti);
  1134. if( GetGUIThreadInfo(0, &ti) && ti.hwndActive )
  1135. {
  1136. *phwnd = ti.hwndActive;
  1137. }
  1138. }
  1139. if( *phwnd )
  1140. {
  1141. HWND hwndOwner, hwndParent;
  1142. // climb up to the top parent in case it's a child window...
  1143. while( hwndParent = GetParent(*phwnd) )
  1144. {
  1145. *phwnd = hwndParent;
  1146. }
  1147. // get the owner in case the top parent is owned
  1148. hwndOwner = GetWindow(*phwnd, GW_OWNER);
  1149. if( hwndOwner )
  1150. {
  1151. *phwnd = hwndOwner;
  1152. }
  1153. // get the last popup of the owner of the top level parent window
  1154. *phwnd = GetLastActivePopup(*phwnd);
  1155. hr = (*phwnd) ? S_OK : E_FAIL;
  1156. }
  1157. }
  1158. return hr;
  1159. }