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.

660 lines
20 KiB

  1. // SpeechCpl.cpp : Implementation of DLL Exports.
  2. // Note: Proxy/Stub Information
  3. // To build a separate proxy/stub DLL,
  4. // run nmake -f SpeechCplps.mk in the project directory.
  5. #include "stdafx.h"
  6. #include <initguid.h>
  7. #include <assertwithstack.cpp>
  8. #include "resource.h"
  9. #include "stuff.h"
  10. #include "sapiver.h"
  11. #include <SpSatellite.h>
  12. #define SAPI4CPL L"speech.cpl"
  13. #define SHLWAPIDLL "shlwapi.dll"
  14. const CLSID LIBID_SPEECHCPLLib = { /* ae9b6e4a-dc9a-41cd-8d53-dcbc3673d5e2 */
  15. 0xae9b6e4a,
  16. 0xdc9a,
  17. 0x41cd,
  18. {0x8d, 0x53, 0xdc, 0xbc, 0x36, 0x73, 0xd5, 0xe2}
  19. };
  20. CComModule _Module;
  21. BOOL IsIECurrentEnough();
  22. BOOL g_fIEVersionGoodEnough = IsIECurrentEnough();
  23. HINSTANCE g_hInstance;
  24. CSpSatelliteDLL g_SatelliteDLL;
  25. BEGIN_OBJECT_MAP(ObjectMap)
  26. END_OBJECT_MAP()
  27. // Forward definition of About dlgproc
  28. INT_PTR CALLBACK AboutDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  29. bool IsSAPI4Installed();
  30. /*****************************************************************************
  31. * DllMain *
  32. *---------*
  33. * Description:
  34. * DLL Entry Point
  35. ****************************************************************** MIKEAR ***/
  36. #ifdef _WIN32_WCE
  37. extern "C"
  38. BOOL WINAPI DllMain(HANDLE hInst, DWORD dwReason, LPVOID /*lpReserved*/)
  39. HINSTANCE hInstance = (HINSTANCE)hInst;
  40. #else
  41. extern "C"
  42. BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
  43. #endif
  44. {
  45. if (dwReason == DLL_PROCESS_ATTACH)
  46. {
  47. g_hInstance = hInstance;
  48. _Module.Init(ObjectMap, g_hInstance, &LIBID_SPEECHCPLLib);
  49. SHFusionInitializeFromModuleID(hInstance, 124);
  50. }
  51. else if (dwReason == DLL_PROCESS_DETACH)
  52. {
  53. _Module.Term();
  54. SHFusionUninitialize();
  55. }
  56. return TRUE; // ok
  57. } /* DllMain */
  58. /////////////////////////////////////////////////////////////////////////////
  59. // Used to determine whether the DLL can be unloaded by OLE
  60. STDAPI DllCanUnloadNow(void)
  61. {
  62. return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
  63. }
  64. /////////////////////////////////////////////////////////////////////////////
  65. // Returns a class factory to create an object of the requested type
  66. STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
  67. {
  68. return _Module.GetClassObject(rclsid, riid, ppv);
  69. }
  70. /////////////////////////////////////////////////////////////////////////////
  71. // DllRegisterServer - Adds entries to the system registry
  72. STDAPI DllRegisterServer(void)
  73. {
  74. // registers object, typelib and all interfaces in typelib
  75. return _Module.RegisterServer(TRUE);
  76. }
  77. /////////////////////////////////////////////////////////////////////////////
  78. // DllUnregisterServer - Removes entries from the system registry
  79. STDAPI DllUnregisterServer(void)
  80. {
  81. return _Module.UnregisterServer(TRUE);
  82. }
  83. // Error Messages
  84. #if 0
  85. void Error(HWND hWnd, HRESULT hRes, UINT uResID)
  86. {
  87. SPDBG_FUNC( "Error" );
  88. WCHAR szErrorTemplate[256];
  89. WCHAR szError[288];
  90. WCHAR szCaption[128];
  91. HMODULE hMod;
  92. UINT uFlags;
  93. DWORD dwRes;
  94. LPVOID pvMsg;
  95. int iLen;
  96. // Load the caption for the error message box
  97. iLen = LoadString(_Module.GetResourceInstance(), IDS_CAPTION, szCaption, 128);
  98. SPDBG_ASSERT(iLen != 0);
  99. // Was a resource ID specified?
  100. if (uResID == 0xFFFFFFFF) {
  101. // Nope. Use the HRESULT.
  102. // Is it a Speech error? NOTE: we have to check this before
  103. // system error messages because there are conflicts between
  104. // some speech errors (e.g. 0x80040202) and system errors.
  105. // NOTE NOTE NOTE!!! This is NOT perfect. Since we don't know
  106. // the context of the error here we won't be able to distinguish
  107. // whether the error is really a speech error or a system error.
  108. // Since we use speech heavily and the system errors that conflict
  109. // are unlikely to occur in here, we'll check the speech errors
  110. // first.
  111. uFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM;
  112. if ((hRes >= 0x80040200) && (hRes <= 0x80040502)) {
  113. WCHAR szSpeechDll[_MAX_PATH];
  114. WCHAR *pchWindows;
  115. // NOTE NOTE NOTE: use GetSystemDirectory instead of
  116. // GetWindowsDirectory. GetWindowsDirectory doesn't
  117. // work without registry manipulation under Hydra.
  118. GetSystemDirectory(szSpeechDll, _MAX_PATH);
  119. pchWindows = wcsrchr(szSpeechDll, '\\');
  120. if (pchWindows)
  121. *pchWindows = 0;
  122. wcscat(szSpeechDll, kpszSpeechDllPath);
  123. // Load speech.dll
  124. CSpUnicodeSupport unicode;
  125. hMod = unicode.LoadLibrary(szSpeechDll);
  126. if (hMod)
  127. uFlags |= FORMAT_MESSAGE_FROM_HMODULE;
  128. }
  129. // Get the error string
  130. dwRes = FormatMessage(uFlags, hMod, hRes, 0, (LPWSTR)&pvMsg, 0, NULL);
  131. // Unload speech.dll (if necessary)
  132. if (hMod)
  133. FreeLibrary(hMod);
  134. // Did we get the error message?
  135. if (dwRes != 0)
  136. {
  137. MessageBox(hWnd, (LPTSTR)pvMsg, szCaption, MB_OK|MB_TOPMOST|g_dwIsRTLLayout);
  138. LocalFree(pvMsg);
  139. return;
  140. }
  141. }
  142. // If this is an unknown error just put up a generic
  143. // message.
  144. if (uResID == 0xFFFFFFFF)
  145. uResID = IDS_E_INSTALL;
  146. // We don't want to show the user the IDS_E_INSTALL message more
  147. // than once.
  148. if ((uResID == IDS_E_INSTALL) && g_bNoInstallError)
  149. return;
  150. // Load the string resource
  151. iLen = LoadString(_Module.GetResourceInstance(), uResID, szErrorTemplate, 256);
  152. // It better be there
  153. SPDBG_ASSERT(iLen != 0);
  154. // Format and show the error
  155. wsprintf(szError, szErrorTemplate, hRes);
  156. MessageBox(hWnd, szError, szCaption, MB_OK|MB_TOPMOST|g_dwIsRTLLayout);
  157. }
  158. #endif
  159. /*****************************************************************************
  160. * RunControlPanel *
  161. *-----------------*
  162. * Description:
  163. * Perform Control Panel initialization and display property sheet
  164. ****************************************************************** MIKEAR ***/
  165. void RunControlPanel(HWND hWndCpl)
  166. {
  167. SPDBG_FUNC( "RunControlPanel" );
  168. PROPSHEETHEADER psh;
  169. PROPSHEETPAGE rgpsp[kcMaxPages];
  170. HPROPSHEETPAGE rPages[kcMaxPages];
  171. UINT kcPages = 0;
  172. // Set up the property sheet header. NOTE: the
  173. // resources for the control panel applet are in
  174. // this module. For NT5, the resource loader handles
  175. // the multi-lingual UI by searching for a speech.cpl.mui
  176. // resource only DLL in the %system%\mui\XXXX directory
  177. // where XXXX is the hex langid.
  178. ZeroMemory(rgpsp, sizeof(PROPSHEETPAGE) * kcMaxPages);
  179. ZeroMemory(&psh, sizeof(PROPSHEETHEADER));
  180. ZeroMemory(rPages, sizeof(HPROPSHEETPAGE) * kcMaxPages);
  181. psh.dwSize = sizeof(PROPSHEETHEADER);
  182. psh.dwFlags = PSH_DEFAULT;
  183. psh.hwndParent = hWndCpl;
  184. psh.hInstance = _Module.GetResourceInstance();
  185. psh.pszCaption = MAKEINTRESOURCE(IDS_CAPTION);
  186. psh.phpage = rPages;
  187. psh.nPages = 0; // make sure psh.nPages gets an initial value
  188. // CComPtr<ISpEnumAudioInstance> cpEnumDevice;
  189. BOOL fHaveVoices = FALSE;
  190. BOOL fHaveRecognizers = FALSE;
  191. ULONG ulNumInputDevices = 1;
  192. ULONG ulNumOutputDevices = 1;
  193. // Get the voice and recognizer count
  194. CComPtr<ISpObjectToken> cpDefVoice;
  195. fHaveVoices = SUCCEEDED(SpGetDefaultTokenFromCategoryId(SPCAT_VOICES, &cpDefVoice));
  196. CComPtr<ISpObjectToken> cpDefReco;
  197. fHaveRecognizers = SUCCEEDED(SpGetDefaultTokenFromCategoryId(SPCAT_RECOGNIZERS, &cpDefReco));
  198. // Set up the PROPSHEETPAGE structure(s). If there are no voices
  199. // or no recognizers, then don't show the corresponding pages. Also
  200. // don't show the page if there is only 1 voice or recognizer and
  201. // one device.
  202. if( fHaveRecognizers )
  203. {
  204. rgpsp[kcPages].dwSize = IsOS(OS_WHISTLERORGREATER)? sizeof(PROPSHEETPAGE) : PROPSHEETPAGE_V2_SIZE;
  205. rgpsp[kcPages].dwFlags = PSP_DEFAULT;
  206. rgpsp[kcPages].hInstance = _Module.GetResourceInstance();
  207. rgpsp[kcPages].pszTemplate = MAKEINTRESOURCE(IDD_SR);
  208. rgpsp[kcPages].pfnDlgProc = SRDlgProc;
  209. rPages[kcPages] = CreatePropertySheetPage(rgpsp+kcPages);
  210. kcPages++;
  211. }
  212. else
  213. {
  214. if ( g_pSRDlg )
  215. {
  216. delete g_pSRDlg;
  217. g_pSRDlg = NULL;
  218. }
  219. }
  220. if( fHaveVoices )
  221. {
  222. rgpsp[kcPages].dwSize = IsOS(OS_WHISTLERORGREATER)? sizeof(PROPSHEETPAGE) : PROPSHEETPAGE_V2_SIZE;
  223. rgpsp[kcPages].dwFlags = PSP_DEFAULT;
  224. rgpsp[kcPages].hInstance = _Module.GetResourceInstance();
  225. rgpsp[kcPages].pszTemplate = MAKEINTRESOURCE(IDD_TTS);
  226. rgpsp[kcPages].pfnDlgProc = TTSDlgProc;
  227. rPages[kcPages] = CreatePropertySheetPage(rgpsp+kcPages);
  228. kcPages++;
  229. }
  230. else
  231. {
  232. if ( g_pTTSDlg )
  233. {
  234. delete g_pTTSDlg;
  235. g_pTTSDlg = NULL;
  236. }
  237. }
  238. // Always display the "Other" (formerly "About") pane on OS <=Win2000 or
  239. // on Whister and beyond if Sapi4 is present
  240. if ( !IsOS(OS_WHISTLERORGREATER) || IsSAPI4Installed() )
  241. {
  242. rgpsp[kcPages].dwSize = IsOS(OS_WHISTLERORGREATER)? sizeof(PROPSHEETPAGE) : PROPSHEETPAGE_V2_SIZE;
  243. rgpsp[kcPages].dwFlags = PSP_DEFAULT ;
  244. rgpsp[kcPages].hInstance = _Module.GetResourceInstance();
  245. rgpsp[kcPages].pszTemplate = MAKEINTRESOURCE(IDD_ABOUT);
  246. rgpsp[kcPages].pfnDlgProc = AboutDlgProc;
  247. rPages[kcPages] = CreatePropertySheetPage(rgpsp+kcPages);
  248. kcPages++;
  249. }
  250. psh.nPages = kcPages;
  251. // Is the current default working language a
  252. // RTL reading language?
  253. if (GetSystemMetrics(SM_MIDEASTENABLED))
  254. {
  255. psh.dwFlags |= PSH_RTLREADING;
  256. rgpsp[0].dwFlags |= PSP_RTLREADING;
  257. g_dwIsRTLLayout = MB_RTLREADING;
  258. }
  259. // Show the property sheet
  260. ::PropertySheet(&psh);
  261. } /* RunControlPanel */
  262. /*****************************************************************************
  263. * IsSAPI4Installed *
  264. *------------------*
  265. * Description:
  266. * Returns true iff speech.cpl is found in the system directory
  267. ****************************************************************** BeckyW ***/
  268. bool IsSAPI4Installed()
  269. {
  270. WCHAR wszSystemDir[ MAX_PATH ];
  271. if ( ::GetSystemDirectory( wszSystemDir, sp_countof( wszSystemDir ) ) )
  272. {
  273. WCHAR wszFoundPath[ MAX_PATH ];
  274. WCHAR *pwchFile = NULL;
  275. wszFoundPath[0] = 0;
  276. return (0 != ::SearchPath( wszSystemDir, SAPI4CPL, NULL,
  277. sp_countof( wszFoundPath ), wszFoundPath, &pwchFile ));
  278. }
  279. return false;
  280. } /* IsSAPI4Installed */
  281. /*****************************************************************************
  282. * RunSAPI4CPL *
  283. *-------------*
  284. * Description:
  285. * Runs speech.cpl and waits for it to exit
  286. ****************************************************************** BeckyW ***/
  287. void RunSAPI4CPL()
  288. {
  289. // Different OS's keep rundll32.exe in different directories,
  290. // so we'll just find it here
  291. WCHAR szStartProg[MAX_PATH];
  292. WCHAR *pchFilePart;
  293. ::SearchPath( NULL, _T("rundll32.exe"), NULL, MAX_PATH,
  294. szStartProg, &pchFilePart );
  295. STARTUPINFO si;
  296. ZeroMemory( &si, sizeof(si) );
  297. PROCESS_INFORMATION pi;
  298. si.cb = sizeof(STARTUPINFO);
  299. ::CreateProcess( szStartProg, L"rundll32 shell32.dll,Control_RunDLL speech.cpl",
  300. NULL, NULL, false, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );
  301. // Wait for it to exit
  302. ::WaitForSingleObject( pi.hProcess, INFINITE );
  303. } /* RunSAPI4CPL */
  304. /*****************************************************************************
  305. * IsIECurrentEnough *
  306. *-------------------*
  307. * Description:
  308. * Returns true iff the version of IE installed meets our requirements
  309. * (IE5 and higher)
  310. ****************************************************************** BeckyW ***/
  311. BOOL IsIECurrentEnough()
  312. {
  313. BOOL fCurrentEnough = false;
  314. DWORD dwDummy = 0;
  315. BYTE *pbBlock = NULL;
  316. DWORD dwSize = ::GetFileVersionInfoSizeA( SHLWAPIDLL, &dwDummy );
  317. if ( dwSize )
  318. {
  319. pbBlock = new BYTE[ dwSize ];
  320. }
  321. BOOL fSuccess = FALSE;
  322. if ( pbBlock )
  323. {
  324. fSuccess = ::GetFileVersionInfoA( SHLWAPIDLL, 0, dwSize, pbBlock );
  325. }
  326. LPVOID pvInfo = NULL;
  327. if ( fSuccess )
  328. {
  329. UINT uiLen = 0;
  330. fSuccess = ::VerQueryValueA( pbBlock, "\\", &pvInfo, &uiLen );
  331. }
  332. if ( fSuccess )
  333. {
  334. VS_FIXEDFILEINFO *pvffi = (VS_FIXEDFILEINFO *) pvInfo;
  335. WORD wVersion = HIWORD(pvffi->dwFileVersionMS);
  336. fCurrentEnough = HIWORD(pvffi->dwFileVersionMS) >= 5;
  337. }
  338. delete[] pbBlock;
  339. return fCurrentEnough;
  340. } /* IsIECurrentEnough */
  341. /*****************************************************************************
  342. * CPlApplet *
  343. *-----------*
  344. * Description:
  345. * Required export for Control Panel applets
  346. ****************************************************************** MIKEAR ***/
  347. LONG APIENTRY CPlApplet(HWND hWndCpl, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
  348. {
  349. SPDBG_FUNC( "CPlApplet" );
  350. // Standard CPL
  351. LPNEWCPLINFO lpNewCPlInfo;
  352. int tmpFlag;
  353. HRESULT hr = S_OK;
  354. switch (uMsg)
  355. {
  356. case CPL_INIT:
  357. if (g_fIEVersionGoodEnough)
  358. {
  359. _Module.m_hInstResource = g_SatelliteDLL.Load(g_hInstance, TEXT("spcplui.dll"));
  360. }
  361. #ifdef _DEBUG
  362. // Turn on memory leak checking
  363. tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
  364. tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
  365. _CrtSetDbgFlag( tmpFlag );
  366. #endif
  367. if ( FAILED( hr ) )
  368. {
  369. // CoInitialize failed, we can't run the CPL
  370. return 0;
  371. }
  372. return TRUE;
  373. case CPL_EXIT:
  374. // These were new'ed right before RunControlPanel() was called
  375. if ( g_pSRDlg )
  376. {
  377. delete g_pSRDlg;
  378. }
  379. if ( g_pTTSDlg )
  380. {
  381. delete g_pTTSDlg;
  382. }
  383. return TRUE;
  384. case CPL_GETCOUNT:
  385. {
  386. return g_fIEVersionGoodEnough ? 1 : 0;
  387. }
  388. case CPL_INQUIRE:
  389. LPCPLINFO lpCPLInfo;
  390. lpCPLInfo = (LPCPLINFO)lParam2;
  391. lpCPLInfo->lData = 0;
  392. lpCPLInfo->idIcon = IDI_SAPICPL;
  393. lpCPLInfo->idName = IDS_NAME;
  394. lpCPLInfo->idInfo = IDS_DESCRIPTION;
  395. break;
  396. case CPL_NEWINQUIRE:
  397. LPNEWCPLINFO lpNewCPLInfo;
  398. lpNewCPLInfo = (LPNEWCPLINFO) lParam2;
  399. lpNewCPLInfo->dwSize = sizeof( NEWCPLINFO );
  400. lpNewCPLInfo->hIcon = ::LoadIcon( _Module.GetResourceInstance(), MAKEINTRESOURCE( IDI_SAPICPL ) );
  401. ::LoadString( _Module.GetResourceInstance(), IDS_NAME, lpNewCPLInfo->szName, 32 );
  402. ::LoadString( _Module.GetResourceInstance(), IDS_DESCRIPTION, lpNewCPLInfo->szInfo, 64 );
  403. break;
  404. case CPL_DBLCLK:
  405. {
  406. // Construct dialog pages and display property sheet
  407. if ( !g_fIEVersionGoodEnough )
  408. {
  409. // No can do: Can't run this guy since there isn't enough IE love
  410. WCHAR szError[ 256 ];
  411. szError[0] = 0;
  412. ::LoadString( _Module.GetResourceInstance(), IDS_NO_IE5, szError, sp_countof( szError ) );
  413. ::MessageBox( NULL, szError, NULL, MB_ICONEXCLAMATION | g_dwIsRTLLayout );
  414. }
  415. else
  416. {
  417. // setup TTS dialog
  418. g_pTTSDlg = new CTTSDlg();
  419. // setup SR dialog
  420. g_pSRDlg = new CSRDlg();
  421. if ( g_pTTSDlg && g_pSRDlg )
  422. {
  423. RunControlPanel(hWndCpl);
  424. }
  425. else
  426. {
  427. WCHAR szError[256];
  428. szError[0] = '\0';
  429. ::LoadString(_Module.GetResourceInstance(), IDS_OUTOFMEMORY, szError, sp_countof(szError));
  430. ::MessageBox(NULL, szError, NULL, MB_ICONWARNING|g_dwIsRTLLayout);
  431. }
  432. }
  433. }
  434. break;
  435. }
  436. return 0;
  437. } /* CPlApplet */
  438. /*****************************************************************************
  439. * AboutDlgProc *
  440. *--------------*
  441. * Description:
  442. * Dialog proc for the about propsheet
  443. ****************************************************************** BECKYW ****/
  444. INT_PTR CALLBACK AboutDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  445. {
  446. SPDBG_FUNC( "AboutDlgProc" );
  447. USES_CONVERSION;
  448. static bool fSAPI4 = false;
  449. static WCHAR szHelpFile[ MAX_PATH ];
  450. switch (uMsg)
  451. {
  452. case WM_INITDIALOG:
  453. {
  454. WCHAR szVerString[ MAX_LOADSTRING ];
  455. ::LoadString( _Module.GetResourceInstance(),
  456. IDS_SAPI_VERSION, szVerString, sp_countof( szVerString ) );
  457. wcscat( szVerString, _T(VER_PRODUCTVERSION_STR) );
  458. ::SetDlgItemText( hWnd, IDC_STATIC_SAPIVER, szVerString );
  459. // Don't display help or file versioning on Whistler and beyond
  460. if ( IsOS(OS_WHISTLERORGREATER) )
  461. {
  462. ::ShowWindow( ::GetDlgItem( hWnd, IDC_ABOUT_HELP ), SW_HIDE );
  463. ::ShowWindow( ::GetDlgItem( hWnd, IDC_VERSION_STATIC ), SW_HIDE );
  464. ::ShowWindow( ::GetDlgItem( hWnd, IDC_STATIC_SAPIVER ), SW_HIDE );
  465. }
  466. else
  467. {
  468. // Display help only if it's there
  469. WCHAR szHelpDir[ MAX_PATH ];
  470. UINT uiRet = ::GetWindowsDirectory( szHelpDir, sp_countof( szHelpDir ) );
  471. DWORD dwRet = 0;
  472. if ( uiRet > 0 )
  473. {
  474. wcscat( szHelpDir, L"\\Help" );
  475. WCHAR *pchFilePart = NULL;
  476. dwRet = ::SearchPath( szHelpDir, L"speech.chm", NULL,
  477. sp_countof( szHelpFile ), szHelpFile, &pchFilePart );
  478. }
  479. if ( 0 == dwRet )
  480. {
  481. szHelpFile[0] = 0;
  482. ::ShowWindow( ::GetDlgItem( hWnd, IDC_ABOUT_HELP ), SW_HIDE );
  483. }
  484. }
  485. // Display the link to SAPI4 only if SAPI4 is installed
  486. fSAPI4 = IsSAPI4Installed();
  487. if ( !fSAPI4 )
  488. {
  489. ::ShowWindow( ::GetDlgItem( hWnd, IDC_GROUP_SAPI4 ), SW_HIDE );
  490. ::ShowWindow( ::GetDlgItem( hWnd, IDC_STATIC_SAPI4 ), SW_HIDE );
  491. ::ShowWindow( ::GetDlgItem( hWnd, IDC_CPL_SAPI4 ), SW_HIDE );
  492. }
  493. break;
  494. }
  495. case WM_COMMAND:
  496. switch( LOWORD(wParam) )
  497. {
  498. case IDC_ABOUT_HELP:
  499. {
  500. if ( *szHelpFile )
  501. {
  502. CSpUnicodeSupport unicode;
  503. unicode.HtmlHelp( NULL, szHelpFile, 0, 0 );
  504. }
  505. break;
  506. }
  507. case IDC_CPL_SAPI4:
  508. {
  509. // Run SAPI4's control panel after exiting ours with a "Cancel"
  510. HWND hwndParent = ::GetParent( hWnd );
  511. PSHNOTIFY pshnot;
  512. pshnot.lParam = 0;
  513. pshnot.hdr.hwndFrom = hwndParent;
  514. pshnot.hdr.code = PSN_QUERYCANCEL;
  515. pshnot.hdr.idFrom = 0;
  516. if ( g_pSRDlg )
  517. {
  518. ::SendMessage( g_pSRDlg->GetHDlg(), WM_NOTIFY, (WPARAM) hwndParent, (LPARAM) &pshnot );
  519. }
  520. if ( g_pTTSDlg )
  521. {
  522. ::SendMessage( g_pTTSDlg->GetHDlg(), WM_NOTIFY, (WPARAM) hwndParent, (LPARAM) &pshnot );
  523. }
  524. ::DestroyWindow( hwndParent );
  525. RunSAPI4CPL();
  526. break;
  527. }
  528. break;
  529. }
  530. }
  531. return FALSE;
  532. } /* AboutDlgProc */