// 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 */