// SpeechCpl.cpp : Implementation of DLL Exports. // Note: Proxy/Stub Information // To build a separate proxy/stub DLL, // run nmake -f SpeechCplps.mk in the project directory. #include "stdafx.h" #include #include #include "resource.h" #include "stuff.h" #include "sapiver.h" #include #define SAPI4CPL L"speech.cpl" #define SHLWAPIDLL "shlwapi.dll" const CLSID LIBID_SPEECHCPLLib = { /* ae9b6e4a-dc9a-41cd-8d53-dcbc3673d5e2 */ 0xae9b6e4a, 0xdc9a, 0x41cd, {0x8d, 0x53, 0xdc, 0xbc, 0x36, 0x73, 0xd5, 0xe2} }; CComModule _Module; BOOL IsIECurrentEnough(); BOOL g_fIEVersionGoodEnough = IsIECurrentEnough(); HINSTANCE g_hInstance; CSpSatelliteDLL g_SatelliteDLL; BEGIN_OBJECT_MAP(ObjectMap) END_OBJECT_MAP() // Forward definition of About dlgproc INT_PTR CALLBACK AboutDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); bool IsSAPI4Installed(); /***************************************************************************** * DllMain * *---------* * Description: * DLL Entry Point ****************************************************************** MIKEAR ***/ #ifdef _WIN32_WCE extern "C" BOOL WINAPI DllMain(HANDLE hInst, DWORD dwReason, LPVOID /*lpReserved*/) HINSTANCE hInstance = (HINSTANCE)hInst; #else extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) #endif { if (dwReason == DLL_PROCESS_ATTACH) { g_hInstance = hInstance; _Module.Init(ObjectMap, g_hInstance, &LIBID_SPEECHCPLLib); SHFusionInitializeFromModuleID(hInstance, 124); } else if (dwReason == DLL_PROCESS_DETACH) { _Module.Term(); SHFusionUninitialize(); } return TRUE; // ok } /* DllMain */ ///////////////////////////////////////////////////////////////////////////// // Used to determine whether the DLL can be unloaded by OLE STDAPI DllCanUnloadNow(void) { return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; } ///////////////////////////////////////////////////////////////////////////// // Returns a class factory to create an object of the requested type STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return _Module.GetClassObject(rclsid, riid, ppv); } ///////////////////////////////////////////////////////////////////////////// // DllRegisterServer - Adds entries to the system registry STDAPI DllRegisterServer(void) { // registers object, typelib and all interfaces in typelib return _Module.RegisterServer(TRUE); } ///////////////////////////////////////////////////////////////////////////// // DllUnregisterServer - Removes entries from the system registry STDAPI DllUnregisterServer(void) { return _Module.UnregisterServer(TRUE); } // Error Messages #if 0 void Error(HWND hWnd, HRESULT hRes, UINT uResID) { SPDBG_FUNC( "Error" ); WCHAR szErrorTemplate[256]; WCHAR szError[288]; WCHAR szCaption[128]; HMODULE hMod; UINT uFlags; DWORD dwRes; LPVOID pvMsg; int iLen; // Load the caption for the error message box iLen = LoadString(_Module.GetResourceInstance(), IDS_CAPTION, szCaption, 128); SPDBG_ASSERT(iLen != 0); // Was a resource ID specified? if (uResID == 0xFFFFFFFF) { // Nope. Use the HRESULT. // Is it a Speech error? NOTE: we have to check this before // system error messages because there are conflicts between // some speech errors (e.g. 0x80040202) and system errors. // NOTE NOTE NOTE!!! This is NOT perfect. Since we don't know // the context of the error here we won't be able to distinguish // whether the error is really a speech error or a system error. // Since we use speech heavily and the system errors that conflict // are unlikely to occur in here, we'll check the speech errors // first. uFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM; if ((hRes >= 0x80040200) && (hRes <= 0x80040502)) { WCHAR szSpeechDll[_MAX_PATH]; WCHAR *pchWindows; // NOTE NOTE NOTE: use GetSystemDirectory instead of // GetWindowsDirectory. GetWindowsDirectory doesn't // work without registry manipulation under Hydra. GetSystemDirectory(szSpeechDll, _MAX_PATH); pchWindows = wcsrchr(szSpeechDll, '\\'); if (pchWindows) *pchWindows = 0; wcscat(szSpeechDll, kpszSpeechDllPath); // Load speech.dll CSpUnicodeSupport unicode; hMod = unicode.LoadLibrary(szSpeechDll); if (hMod) uFlags |= FORMAT_MESSAGE_FROM_HMODULE; } // Get the error string dwRes = FormatMessage(uFlags, hMod, hRes, 0, (LPWSTR)&pvMsg, 0, NULL); // Unload speech.dll (if necessary) if (hMod) FreeLibrary(hMod); // Did we get the error message? if (dwRes != 0) { MessageBox(hWnd, (LPTSTR)pvMsg, szCaption, MB_OK|MB_TOPMOST|g_dwIsRTLLayout); LocalFree(pvMsg); return; } } // If this is an unknown error just put up a generic // message. if (uResID == 0xFFFFFFFF) uResID = IDS_E_INSTALL; // We don't want to show the user the IDS_E_INSTALL message more // than once. if ((uResID == IDS_E_INSTALL) && g_bNoInstallError) return; // Load the string resource iLen = LoadString(_Module.GetResourceInstance(), uResID, szErrorTemplate, 256); // It better be there SPDBG_ASSERT(iLen != 0); // Format and show the error wsprintf(szError, szErrorTemplate, hRes); MessageBox(hWnd, szError, szCaption, MB_OK|MB_TOPMOST|g_dwIsRTLLayout); } #endif /***************************************************************************** * RunControlPanel * *-----------------* * Description: * Perform Control Panel initialization and display property sheet ****************************************************************** MIKEAR ***/ void RunControlPanel(HWND hWndCpl) { SPDBG_FUNC( "RunControlPanel" ); PROPSHEETHEADER psh; PROPSHEETPAGE rgpsp[kcMaxPages]; HPROPSHEETPAGE rPages[kcMaxPages]; UINT kcPages = 0; // Set up the property sheet header. NOTE: the // resources for the control panel applet are in // this module. For NT5, the resource loader handles // the multi-lingual UI by searching for a speech.cpl.mui // resource only DLL in the %system%\mui\XXXX directory // where XXXX is the hex langid. ZeroMemory(rgpsp, sizeof(PROPSHEETPAGE) * kcMaxPages); ZeroMemory(&psh, sizeof(PROPSHEETHEADER)); ZeroMemory(rPages, sizeof(HPROPSHEETPAGE) * kcMaxPages); psh.dwSize = sizeof(PROPSHEETHEADER); psh.dwFlags = PSH_DEFAULT; psh.hwndParent = hWndCpl; psh.hInstance = _Module.GetResourceInstance(); psh.pszCaption = MAKEINTRESOURCE(IDS_CAPTION); psh.phpage = rPages; psh.nPages = 0; // make sure psh.nPages gets an initial value // CComPtr cpEnumDevice; BOOL fHaveVoices = FALSE; BOOL fHaveRecognizers = FALSE; ULONG ulNumInputDevices = 1; ULONG ulNumOutputDevices = 1; // Get the voice and recognizer count CComPtr cpDefVoice; fHaveVoices = SUCCEEDED(SpGetDefaultTokenFromCategoryId(SPCAT_VOICES, &cpDefVoice)); CComPtr cpDefReco; fHaveRecognizers = SUCCEEDED(SpGetDefaultTokenFromCategoryId(SPCAT_RECOGNIZERS, &cpDefReco)); // Set up the PROPSHEETPAGE structure(s). If there are no voices // or no recognizers, then don't show the corresponding pages. Also // don't show the page if there is only 1 voice or recognizer and // one device. if( fHaveRecognizers ) { rgpsp[kcPages].dwSize = IsOS(OS_WHISTLERORGREATER)? sizeof(PROPSHEETPAGE) : PROPSHEETPAGE_V2_SIZE; rgpsp[kcPages].dwFlags = PSP_DEFAULT; rgpsp[kcPages].hInstance = _Module.GetResourceInstance(); rgpsp[kcPages].pszTemplate = MAKEINTRESOURCE(IDD_SR); rgpsp[kcPages].pfnDlgProc = SRDlgProc; rPages[kcPages] = CreatePropertySheetPage(rgpsp+kcPages); kcPages++; } else { if ( g_pSRDlg ) { delete g_pSRDlg; g_pSRDlg = NULL; } } if( fHaveVoices ) { rgpsp[kcPages].dwSize = IsOS(OS_WHISTLERORGREATER)? sizeof(PROPSHEETPAGE) : PROPSHEETPAGE_V2_SIZE; rgpsp[kcPages].dwFlags = PSP_DEFAULT; rgpsp[kcPages].hInstance = _Module.GetResourceInstance(); rgpsp[kcPages].pszTemplate = MAKEINTRESOURCE(IDD_TTS); rgpsp[kcPages].pfnDlgProc = TTSDlgProc; rPages[kcPages] = CreatePropertySheetPage(rgpsp+kcPages); kcPages++; } else { if ( g_pTTSDlg ) { delete g_pTTSDlg; g_pTTSDlg = NULL; } } // Always display the "Other" (formerly "About") pane on OS <=Win2000 or // on Whister and beyond if Sapi4 is present if ( !IsOS(OS_WHISTLERORGREATER) || IsSAPI4Installed() ) { rgpsp[kcPages].dwSize = IsOS(OS_WHISTLERORGREATER)? sizeof(PROPSHEETPAGE) : PROPSHEETPAGE_V2_SIZE; rgpsp[kcPages].dwFlags = PSP_DEFAULT ; rgpsp[kcPages].hInstance = _Module.GetResourceInstance(); rgpsp[kcPages].pszTemplate = MAKEINTRESOURCE(IDD_ABOUT); rgpsp[kcPages].pfnDlgProc = AboutDlgProc; rPages[kcPages] = CreatePropertySheetPage(rgpsp+kcPages); kcPages++; } psh.nPages = kcPages; // Is the current default working language a // RTL reading language? if (GetSystemMetrics(SM_MIDEASTENABLED)) { psh.dwFlags |= PSH_RTLREADING; rgpsp[0].dwFlags |= PSP_RTLREADING; g_dwIsRTLLayout = MB_RTLREADING; } // Show the property sheet ::PropertySheet(&psh); } /* RunControlPanel */ /***************************************************************************** * IsSAPI4Installed * *------------------* * Description: * Returns true iff speech.cpl is found in the system directory ****************************************************************** BeckyW ***/ bool IsSAPI4Installed() { WCHAR wszSystemDir[ MAX_PATH ]; if ( ::GetSystemDirectory( wszSystemDir, sp_countof( wszSystemDir ) ) ) { WCHAR wszFoundPath[ MAX_PATH ]; WCHAR *pwchFile = NULL; wszFoundPath[0] = 0; return (0 != ::SearchPath( wszSystemDir, SAPI4CPL, NULL, sp_countof( wszFoundPath ), wszFoundPath, &pwchFile )); } return false; } /* IsSAPI4Installed */ /***************************************************************************** * RunSAPI4CPL * *-------------* * Description: * Runs speech.cpl and waits for it to exit ****************************************************************** BeckyW ***/ void RunSAPI4CPL() { // Different OS's keep rundll32.exe in different directories, // so we'll just find it here WCHAR szStartProg[MAX_PATH]; WCHAR *pchFilePart; ::SearchPath( NULL, _T("rundll32.exe"), NULL, MAX_PATH, szStartProg, &pchFilePart ); STARTUPINFO si; ZeroMemory( &si, sizeof(si) ); PROCESS_INFORMATION pi; si.cb = sizeof(STARTUPINFO); ::CreateProcess( szStartProg, L"rundll32 shell32.dll,Control_RunDLL speech.cpl", NULL, NULL, false, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi ); // Wait for it to exit ::WaitForSingleObject( pi.hProcess, INFINITE ); } /* RunSAPI4CPL */ /***************************************************************************** * IsIECurrentEnough * *-------------------* * Description: * Returns true iff the version of IE installed meets our requirements * (IE5 and higher) ****************************************************************** BeckyW ***/ BOOL IsIECurrentEnough() { BOOL fCurrentEnough = false; DWORD dwDummy = 0; BYTE *pbBlock = NULL; DWORD dwSize = ::GetFileVersionInfoSizeA( SHLWAPIDLL, &dwDummy ); if ( dwSize ) { pbBlock = new BYTE[ dwSize ]; } BOOL fSuccess = FALSE; if ( pbBlock ) { fSuccess = ::GetFileVersionInfoA( SHLWAPIDLL, 0, dwSize, pbBlock ); } LPVOID pvInfo = NULL; if ( fSuccess ) { UINT uiLen = 0; fSuccess = ::VerQueryValueA( pbBlock, "\\", &pvInfo, &uiLen ); } if ( fSuccess ) { VS_FIXEDFILEINFO *pvffi = (VS_FIXEDFILEINFO *) pvInfo; WORD wVersion = HIWORD(pvffi->dwFileVersionMS); fCurrentEnough = HIWORD(pvffi->dwFileVersionMS) >= 5; } delete[] pbBlock; return fCurrentEnough; } /* IsIECurrentEnough */ /***************************************************************************** * CPlApplet * *-----------* * Description: * Required export for Control Panel applets ****************************************************************** MIKEAR ***/ LONG APIENTRY CPlApplet(HWND hWndCpl, UINT uMsg, LPARAM lParam1, LPARAM lParam2) { SPDBG_FUNC( "CPlApplet" ); // Standard CPL LPNEWCPLINFO lpNewCPlInfo; int tmpFlag; HRESULT hr = S_OK; switch (uMsg) { case CPL_INIT: if (g_fIEVersionGoodEnough) { _Module.m_hInstResource = g_SatelliteDLL.Load(g_hInstance, TEXT("spcplui.dll")); } #ifdef _DEBUG // Turn on memory leak checking tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ); tmpFlag |= _CRTDBG_LEAK_CHECK_DF; _CrtSetDbgFlag( tmpFlag ); #endif if ( FAILED( hr ) ) { // CoInitialize failed, we can't run the CPL return 0; } return TRUE; case CPL_EXIT: // These were new'ed right before RunControlPanel() was called if ( g_pSRDlg ) { delete g_pSRDlg; } if ( g_pTTSDlg ) { delete g_pTTSDlg; } return TRUE; case CPL_GETCOUNT: { return g_fIEVersionGoodEnough ? 1 : 0; } case CPL_INQUIRE: LPCPLINFO lpCPLInfo; lpCPLInfo = (LPCPLINFO)lParam2; lpCPLInfo->lData = 0; lpCPLInfo->idIcon = IDI_SAPICPL; lpCPLInfo->idName = IDS_NAME; lpCPLInfo->idInfo = IDS_DESCRIPTION; break; case CPL_NEWINQUIRE: LPNEWCPLINFO lpNewCPLInfo; lpNewCPLInfo = (LPNEWCPLINFO) lParam2; lpNewCPLInfo->dwSize = sizeof( NEWCPLINFO ); lpNewCPLInfo->hIcon = ::LoadIcon( _Module.GetResourceInstance(), MAKEINTRESOURCE( IDI_SAPICPL ) ); ::LoadString( _Module.GetResourceInstance(), IDS_NAME, lpNewCPLInfo->szName, 32 ); ::LoadString( _Module.GetResourceInstance(), IDS_DESCRIPTION, lpNewCPLInfo->szInfo, 64 ); break; case CPL_DBLCLK: { // Construct dialog pages and display property sheet if ( !g_fIEVersionGoodEnough ) { // No can do: Can't run this guy since there isn't enough IE love WCHAR szError[ 256 ]; szError[0] = 0; ::LoadString( _Module.GetResourceInstance(), IDS_NO_IE5, szError, sp_countof( szError ) ); ::MessageBox( NULL, szError, NULL, MB_ICONEXCLAMATION | g_dwIsRTLLayout ); } else { // setup TTS dialog g_pTTSDlg = new CTTSDlg(); // setup SR dialog g_pSRDlg = new CSRDlg(); if ( g_pTTSDlg && g_pSRDlg ) { RunControlPanel(hWndCpl); } else { WCHAR szError[256]; szError[0] = '\0'; ::LoadString(_Module.GetResourceInstance(), IDS_OUTOFMEMORY, szError, sp_countof(szError)); ::MessageBox(NULL, szError, NULL, MB_ICONWARNING|g_dwIsRTLLayout); } } } break; } return 0; } /* CPlApplet */ /***************************************************************************** * AboutDlgProc * *--------------* * Description: * Dialog proc for the about propsheet ****************************************************************** BECKYW ****/ INT_PTR CALLBACK AboutDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { SPDBG_FUNC( "AboutDlgProc" ); USES_CONVERSION; static bool fSAPI4 = false; static WCHAR szHelpFile[ MAX_PATH ]; switch (uMsg) { case WM_INITDIALOG: { WCHAR szVerString[ MAX_LOADSTRING ]; ::LoadString( _Module.GetResourceInstance(), IDS_SAPI_VERSION, szVerString, sp_countof( szVerString ) ); wcscat( szVerString, _T(VER_PRODUCTVERSION_STR) ); ::SetDlgItemText( hWnd, IDC_STATIC_SAPIVER, szVerString ); // Don't display help or file versioning on Whistler and beyond if ( IsOS(OS_WHISTLERORGREATER) ) { ::ShowWindow( ::GetDlgItem( hWnd, IDC_ABOUT_HELP ), SW_HIDE ); ::ShowWindow( ::GetDlgItem( hWnd, IDC_VERSION_STATIC ), SW_HIDE ); ::ShowWindow( ::GetDlgItem( hWnd, IDC_STATIC_SAPIVER ), SW_HIDE ); } else { // Display help only if it's there WCHAR szHelpDir[ MAX_PATH ]; UINT uiRet = ::GetWindowsDirectory( szHelpDir, sp_countof( szHelpDir ) ); DWORD dwRet = 0; if ( uiRet > 0 ) { wcscat( szHelpDir, L"\\Help" ); WCHAR *pchFilePart = NULL; dwRet = ::SearchPath( szHelpDir, L"speech.chm", NULL, sp_countof( szHelpFile ), szHelpFile, &pchFilePart ); } if ( 0 == dwRet ) { szHelpFile[0] = 0; ::ShowWindow( ::GetDlgItem( hWnd, IDC_ABOUT_HELP ), SW_HIDE ); } } // Display the link to SAPI4 only if SAPI4 is installed fSAPI4 = IsSAPI4Installed(); if ( !fSAPI4 ) { ::ShowWindow( ::GetDlgItem( hWnd, IDC_GROUP_SAPI4 ), SW_HIDE ); ::ShowWindow( ::GetDlgItem( hWnd, IDC_STATIC_SAPI4 ), SW_HIDE ); ::ShowWindow( ::GetDlgItem( hWnd, IDC_CPL_SAPI4 ), SW_HIDE ); } break; } case WM_COMMAND: switch( LOWORD(wParam) ) { case IDC_ABOUT_HELP: { if ( *szHelpFile ) { CSpUnicodeSupport unicode; unicode.HtmlHelp( NULL, szHelpFile, 0, 0 ); } break; } case IDC_CPL_SAPI4: { // Run SAPI4's control panel after exiting ours with a "Cancel" HWND hwndParent = ::GetParent( hWnd ); PSHNOTIFY pshnot; pshnot.lParam = 0; pshnot.hdr.hwndFrom = hwndParent; pshnot.hdr.code = PSN_QUERYCANCEL; pshnot.hdr.idFrom = 0; if ( g_pSRDlg ) { ::SendMessage( g_pSRDlg->GetHDlg(), WM_NOTIFY, (WPARAM) hwndParent, (LPARAM) &pshnot ); } if ( g_pTTSDlg ) { ::SendMessage( g_pTTSDlg->GetHDlg(), WM_NOTIFY, (WPARAM) hwndParent, (LPARAM) &pshnot ); } ::DestroyWindow( hwndParent ); RunSAPI4CPL(); break; } break; } } return FALSE; } /* AboutDlgProc */