windows-nt/Source/XPSP1/NT/enduser/speech/sapi/cpl/speechcpl.cpp
2020-09-26 16:20:57 +08:00

661 lines
19 KiB
C++

// 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 <initguid.h>
#include <assertwithstack.cpp>
#include "resource.h"
#include "stuff.h"
#include "sapiver.h"
#include <SpSatellite.h>
#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
BOOL 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<ISpEnumAudioInstance> cpEnumDevice;
BOOL fHaveVoices = FALSE;
BOOL fHaveRecognizers = FALSE;
ULONG ulNumInputDevices = 1;
ULONG ulNumOutputDevices = 1;
// Get the voice and recognizer count
CComPtr<ISpObjectToken> cpDefVoice;
fHaveVoices = SUCCEEDED(SpGetDefaultTokenFromCategoryId(SPCAT_VOICES, &cpDefVoice));
CComPtr<ISpObjectToken> 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 = (DLGPROC)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 = (DLGPROC)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 = (DLGPROC)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, sizeof(szError));
::MessageBox(NULL, szError, NULL, MB_ICONWARNING|g_dwIsRTLLayout);
}
}
}
break;
}
return 0;
} /* CPlApplet */
/*****************************************************************************
* AboutDlgProc *
*--------------*
* Description:
* Dialog proc for the about propsheet
****************************************************************** BECKYW ****/
BOOL 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 */