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.
 
 
 
 
 
 

3501 lines
109 KiB

/*************************************************************************
Project: Narrator
Module: keys.cpp
Author: Paul Blenkhorn
Date: April 1997
Notes: Credit to be given to MSAA team - bits of code have been
lifted from:
Babble, Inspect, and Snapshot.
Copyright (C) 1997-1998 by Microsoft Corporation. All rights reserved.
See bottom of file for disclaimer
History: Add features, Bug fixes : 1999 Anil Kumar
*************************************************************************/
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <ole2.h>
#include <ole2ver.h>
#include <commctrl.h>
#include "commdlg.h"
#include <string.h>
#include <initguid.h>
#include <oleacc.h>
#include <TCHAR.H>
#include "..\Narrator\Narrator.h"
#include "keys.h"
#include "w95trace.c"
#include "getprop.h"
#include "..\Narrator\resource.h"
#include "resource.h"
#include "list.h" // include list.h before helpthd.h, GINFO needs CList
#include "HelpThd.h"
#include <stdio.h>
#include "mappedfile.cpp"
template<class _Ty> class CAutoArray
{
public:
explicit CAutoArray(_Ty *_P = 0) : _Ptr(_P) {}
~CAutoArray()
{
if ( _Ptr )
delete [] _Ptr;
}
_Ty *Get()
{
return _Ptr;
}
private:
_Ty *_Ptr;
};
#define ARRAYSIZE(x) (sizeof(x) / sizeof(*x))
// ROBSI: 99-10-09
#define MAX_NAME 4196 // 4K (beyond Max of MSAA)
#define MAX_VALUE 512
// When building with VC5, we need winable.h since the active
// accessibility structures are not in VC5's winuser.h. winable.h can
// be found in the active accessibility SDK
#ifdef VC5_BUILD___NOT_NT_BUILD_ENVIRONMENT
#include <winable.h>
#endif
#define STATE_MASK (STATE_SYSTEM_CHECKED | STATE_SYSTEM_MIXED | STATE_SYSTEM_READONLY | STATE_SYSTEM_BUSY | STATE_SYSTEM_MARQUEED | STATE_SYSTEM_ANIMATED | STATE_SYSTEM_INVISIBLE | STATE_SYSTEM_UNAVAILABLE)
// Local functions
void Home(int x);
void MoveToEnd(int x);
void SpeakKeyboard(int nOption);
void SpeakWindow(int nOption);
void SpeakRepeat(int nOption);
void SpeakItem(int nOption);
void SpeakMainItems(int nOption);
void SpeakMute(int nOption);
void GetObjectProperty(IAccessible*, long, int, LPTSTR, UINT);
void AddAccessibleObjects(IAccessible*, const VARIANT &);
BOOL AddItem(IAccessible*, const VARIANT &);
void SpeakObjectInfo(LPOBJINFO poiObj, BOOL SpeakExtra);
BOOL Object_Normalize( IAccessible * pAcc,
VARIANT * pvarChild,
IAccessible ** ppAccOut,
VARIANT * pvarChildOut);
_inline void InitChildSelf(VARIANT *pvar)
{
pvar->vt = VT_I4;
pvar->lVal = CHILDID_SELF;
}
// MSAA event handlers
BOOL OnFocusChangedEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild,
DWORD dwmsTimeStamp);
BOOL OnValueChangedEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild,
DWORD dwmsTimeStamp);
BOOL OnSelectChangedEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild,
DWORD dwmsTimeStamp);
BOOL OnLocationChangedEvent(DWORD event, HWND hwnd, LONG idObject,
LONG idChild, DWORD dwmsTimeStamp);
BOOL OnStateChangedEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild,
DWORD dwmsTimeStamp);
BOOL OnObjectShowEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild,
DWORD dwmsTimeStamp);
// More local routines
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK MouseProc(int code, WPARAM wParam, LPARAM lParam);
BOOL IsFocussedItem( HWND hWnd, IAccessible * pAcc, VARIANT varChild );
void FilterGUID(TCHAR* szSpeak);
// Hot keys
HOTK rgHotKeys[] =
{ //Key SHIFT Function Parameter
{ VK_F12, MSR_CTRL | MSR_SHIFT, SpeakKeyboard, 0},
{ VK_SPACE, MSR_CTRL | MSR_SHIFT, SpeakWindow, 1},
{ VK_RETURN,MSR_CTRL | MSR_SHIFT, SpeakMainItems, 0},
{ VK_INSERT,MSR_CTRL | MSR_SHIFT, SpeakItem, 0},
{ VK_HOME, MSR_ALT, Home, 0},
{ VK_END, MSR_ALT, MoveToEnd, 0},
};
// a-anilk: this is better than defining as a constant - you don't have to worry
// about making the table and the count match up.
#define CKEYS_HOT (sizeof(rgHotKeys)/sizeof(HOTK))
#define MSR_DONOWT 0
#define MSR_DOCHAR 1
#define MSR_DOWORD 2
#define MSR_DOLINE 3
#define MSR_DOLINED 4
#define MSR_DOCHARR 5
#define MSR_DOWORDR 6
#define MSR_DOOBJECT 4
#define MSR_DOWINDOW 5
#define MAX_TEXT_ROLE 128
HHOOK g_hhookKey = 0;
HHOOK g_hhookMouse = 0;
HWINEVENTHOOK g_hEventHook = 0;
POINT g_ptMoveCursor = {0,0};
UINT_PTR g_uTimer = 0;
// Global Variables stored in memory mapped file
struct GLOBALDATA
{
int nAutoRead; // Did we get to ReadWindow through focus change or CTRL_ALT_SPACE flag
int nSpeakWindowSoon; // Flag to indicate the we have a new window ... speak it when sensible
int nLeftHandSide; // Store left hand side of HTML window we want to read
BOOL fDoingPassword;
int nMsrDoNext; // keyboard flag set when curor keys are used ... let us know what to read when caret has moved
HWND hwndMSR;
// Global variables to control events and speech
BOOL fInternetExplorer;
BOOL fHTML_Help;
UINT uMSG_MSR_Cursor;
POINT ptCurrentMouse;
BOOL fMouseUp; // flag for mouse up/down
HWND hwndHelp;
BOOL fJustHadSpace;
BOOL fJustHadShiftKeys;
BOOL fListFocus; // To avoid double speaking of list items...
BOOL fStartPressed;
TCHAR pszTextLocal[2000]; // PB: 22 Nov 1998. Make it work!!! Make this Global and Shared!
// Global data that used to be exported from the DLL
TCHAR szCurrentText[MAX_TEXT];
int fTrackSecondary;
int fTrackCaret;
int fTrackInputFocus;
int nEchoChars;
int fAnnounceWindow;
int fAnnounceMenu;
int fAnnouncePopup;
int fAnnounceToolTips;
int fReviewStyle;
int nReviewLevel;
};
// pointer to shared global data
GLOBALDATA *g_pGlobalData = 0;
// pointer to mem mapped file handle
CMemMappedFile *g_CMappedFile = 0;
// the number of times to try to create mem mapped file must be < 10
const int c_cMappedFileTries = 3;
// name of memory mapped file
TCHAR g_szMappedFileName[] = TEXT("NarratorShared0");
// mutex to access mem mapped file and wait time
TCHAR g_szMutexNarrator[] = TEXT("NarratorMutex0");
const int c_nMutexWait = 5000;
void InitGlobalData()
{
CScopeMutex csMutex;
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait) && g_pGlobalData)
{
DBPRINTF(TEXT("InitGlobalData\r\n"));
g_pGlobalData->nMsrDoNext = MSR_DONOWT; // keyboard flag set when curor keys are used
g_pGlobalData->ptCurrentMouse.x = -1;
g_pGlobalData->ptCurrentMouse.y = -1;
g_pGlobalData->fMouseUp = TRUE; // flag for mouse up/down
g_pGlobalData->fTrackSecondary = TRUE;
g_pGlobalData->fTrackCaret = TRUE;
g_pGlobalData->fTrackInputFocus = FALSE;
g_pGlobalData->nEchoChars = MSR_ECHOALNUM | MSR_ECHOSPACE | MSR_ECHODELETE | MSR_ECHOMODIFIERS | MSR_ECHOENTER | MSR_ECHOBACK | MSR_ECHOTAB;
g_pGlobalData->fAnnounceWindow = TRUE;
g_pGlobalData->fAnnounceMenu = TRUE;
g_pGlobalData->fAnnouncePopup = TRUE;
g_pGlobalData->fAnnounceToolTips = FALSE; // this ain't working properly - taken out!
g_pGlobalData->fReviewStyle = TRUE;
g_pGlobalData->nReviewLevel = 0;
}
}
BOOL CreateMappedFile()
{
g_CMappedFile = new CMemMappedFile;
if (g_CMappedFile)
{
// Append a number thus avoiding restart timing issue
// on desktop switches but only try 3 times
// ISSUE (micw 08/22/00)
// - this code has potential problem of ending up with two or more mapped
// files open. A mapped file for narrator and one for each hook. Hooks will
// cause the DLL to get loaded for that process. If narrator has NarratorShared1
// opened and the hook loads this DLL then the hooked process will have
// NarratorShared0 open. Could use narrator's hwnd as the thing to append to the
// file and mutex name. This code could find the narrator hwnd and use that to
// open. Letting this go for now since the above hasn't been observed in testing.
int iPos1 = lstrlen(g_szMappedFileName) - 1;
int iPos2 = lstrlen(g_szMutexNarrator) - 1;
for (int i=0;i<c_cMappedFileTries;i++)
{
if (TRUE == g_CMappedFile->Open(g_szMappedFileName, sizeof(GLOBALDATA)))
{
CScopeMutex csMutex;
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait))
{
g_CMappedFile->AccessMem((void **)&g_pGlobalData);
if (g_CMappedFile->FirstOpen())
InitGlobalData();
DBPRINTF(TEXT("CreateMappedFile: Succeeded %d try!\r\n"), i);
return TRUE;
}
g_CMappedFile->Close();
break; // fail if get to here
}
Sleep(500);
g_szMappedFileName[iPos1] = '1'+i;
g_szMutexNarrator[iPos2] = '1'+i;
}
}
DBPRINTF(TEXT("CreateMappedFile: Unable to create the mapped file!\r\n"));
return FALSE;
}
void CloseMappedFile()
{
if (g_CMappedFile)
{
g_CMappedFile->Close();
delete g_CMappedFile;
g_CMappedFile = 0;
}
}
//
// Accessor functions for what used to be exported, shared, variables
//
#define SIMPLE_FUNC_IMPL(type, prefix, name, error) \
type Get ## name() \
{ \
CScopeMutex csMutex; \
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait)) \
{ \
return g_pGlobalData->prefix ## name; \
} else \
{ \
return error; \
} \
} \
void Set ## name(type value) \
{ \
CScopeMutex csMutex; \
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait)) \
{ \
g_pGlobalData->prefix ## name = value; \
} \
}
SIMPLE_FUNC_IMPL(BOOL, f, TrackSecondary, FALSE)
SIMPLE_FUNC_IMPL(BOOL, f, TrackCaret, FALSE)
SIMPLE_FUNC_IMPL(BOOL, f, TrackInputFocus, FALSE)
SIMPLE_FUNC_IMPL(int, n, EchoChars, 0)
SIMPLE_FUNC_IMPL(BOOL, f, AnnounceWindow, FALSE)
SIMPLE_FUNC_IMPL(BOOL, f, AnnounceMenu, FALSE)
SIMPLE_FUNC_IMPL(BOOL, f, AnnouncePopup, FALSE)
SIMPLE_FUNC_IMPL(BOOL, f, AnnounceToolTips, FALSE)
SIMPLE_FUNC_IMPL(BOOL, f, ReviewStyle, FALSE)
SIMPLE_FUNC_IMPL(int, n, ReviewLevel, 0)
void GetCurrentText(LPTSTR psz, int cch)
{
CScopeMutex csMutex;
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait))
{
lstrcpyn(psz, g_pGlobalData->szCurrentText, cch);
psz[cch-1] = TEXT('\0');
}
}
void SetCurrentText(LPCTSTR psz)
{
CScopeMutex csMutex;
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait))
{
lstrcpyn(g_pGlobalData->szCurrentText, psz, MAX_TEXT);
g_pGlobalData->szCurrentText[MAX_TEXT-1] = TEXT('\0');
}
}
HINSTANCE g_Hinst = NULL;
DWORD g_tidMain=0; // ROBSI: 10-10-99
// These are class names, This could change from one OS to another and in
// different OS releases.I have grouped them here : Anil.
// These names may have to changed for Win9x and other releases
#define CLASS_WINSWITCH TEXT("#32771") // This is WinSwitch class. Disguises itself :-)AK
#define CLASS_HTMLHELP_IE TEXT("HTML_Internet Explorer")
#define CLASS_IE_FRAME TEXT("IEFrame")
#define CLASS_IE_MAINWND TEXT("Internet Explorer_Server")
#define CLASS_LISTVIEW TEXT("SysListView32")
#define CLASS_HTMLHELP TEXT("HH Parent")
#define CLASS_TOOLBAR TEXT("ToolbarWindow32")
#define CLASS_MS_WINNOTE TEXT("MS_WINNOTE")
#define CLASS_HH_POPUP TEXT("hh_popup")
BOOL IsTridentWindow( LPCTSTR szClass )
{
return lstrcmpi(szClass, CLASS_HTMLHELP_IE) == 0
|| lstrcmpi(szClass, CLASS_IE_FRAME) == 0
|| lstrcmpi(szClass, CLASS_IE_MAINWND) == 0
|| lstrcmpi(szClass, TEXT("PCHShell Window")) == 0 // Help & Support
|| lstrcmpi(szClass, TEXT("Internet Explorer_TridentDlgFrame")) == 0; // Trident popup windows
}
// Check if the pAcc, varChild refer to a balloon tip. If so, it places the corresponding
// IAccessible and childID in the out ppAcc/pvarChild params.
// The in pAcc/varChild params are always consumed, so should not be freed by caller.
BOOL CheckIsBalloonTipElseRelease( IAccessible * pAcc, VARIANT varChild, IAccessible ** ppAcc, VARIANT * pvarChild )
{
VARIANT varRole;
HRESULT hr = pAcc->get_accRole( varChild, &varRole );
if ( hr == S_OK && varRole.vt == VT_I4 &&
( varRole.lVal == ROLE_SYSTEM_TOOLTIP || varRole.lVal == ROLE_SYSTEM_HELPBALLOON ) )
{
// Got it...
*ppAcc = pAcc;
pvarChild->vt = VT_I4;
pvarChild->lVal = CHILDID_SELF;
return TRUE;
}
pAcc->Release();
return FALSE;
}
IAccessible * GetFocusedIAccessibile( HWND hwndFocus, VARIANT * varChild )
{
IAccessible *pIAcc = NULL;
HRESULT hr = AccessibleObjectFromWindow(hwndFocus, OBJID_CLIENT,
IID_IAccessible, (void**)&pIAcc);
InitChildSelf(varChild);
if (S_OK == hr)
{
while ( pIAcc )
{
HRESULT hr = pIAcc->get_accFocus(varChild);
switch ( varChild->vt )
{
case VT_I4:
return pIAcc;
break;
case VT_DISPATCH:
{
IAccessible * pAccTemp = NULL;
hr = varChild->pdispVal->QueryInterface( IID_IAccessible, (void**) &pAccTemp );
VariantClear( varChild );
pIAcc->Release();
if ( hr != S_OK )
{
pIAcc = NULL;
break;
}
pIAcc = pAccTemp;
break;
}
default:
pIAcc->Release();
pIAcc = NULL;
break;
}
}
}
return NULL;
}
/*************************************************************************
Function: SpeakString
Purpose: Send speak string message back to original application
Inputs: TCHAR *str
Returns: void
History:
Uses sendmessage to avoid other messages firing and overwriting this one.
*************************************************************************/
void SpeakString(TCHAR * str)
{
DBPRINTF(TEXT("SpeakString '%s'\r\n"), str);
lstrcpyn(g_pGlobalData->szCurrentText,str,MAX_TEXT);
g_pGlobalData->szCurrentText[MAX_TEXT-1] = TEXT('\0');
SendMessage(g_pGlobalData->hwndMSR, WM_MSRSPEAK, 0, 0);
}
/*************************************************************************
Function: SpeakStr
Purpose: Send speak string message back to original application
Inputs: TCHAR *str
Returns: void
History:
This one uses Postmessage to make focus change work for ALT-TAB???????
*************************************************************************/
void SpeakStr(TCHAR * str)
{
lstrcpyn(g_pGlobalData->szCurrentText,str,MAX_TEXT);
g_pGlobalData->szCurrentText[MAX_TEXT-1] = TEXT('\0');
PostMessage(g_pGlobalData->hwndMSR, WM_MSRSPEAK, 0, 0);
}
/*************************************************************************
Function: SpeakStringAll
Purpose: Speak the string, but put out a space first to make sure the
string is fresh - i.e. stop duplicate string pruning from
occuring
Inputs: TCHAR *str
Returns: void
History:
*************************************************************************/
void SpeakStringAll(TCHAR * str)
{
SpeakString(TEXT(" ")); // stop speech filter losing duplicates
SpeakString(str);
}
/*************************************************************************
Function: SpeakStringId
Purpose: Speak a string loaded as a resource ID
Inputs: UINT id
Returns: void
History:
*************************************************************************/
void SpeakStringId(UINT id)
{
if (LoadString(g_Hinst, id, g_pGlobalData->szCurrentText, 256) == 0)
{
DBPRINTF (TEXT("LoadString failed on hinst %lX id %u\r\n"),g_Hinst,id);
SpeakString(TEXT("TEXT NOT FOUND!"));
}
else
{
SendMessage(g_pGlobalData->hwndMSR, WM_MSRSPEAK, 0, 0);
SpeakString(TEXT(" ")); // stop speech filter losing duplicates
}
}
/*************************************************************************
Function: SetSecondary
Purpose: Set secondary focus position & posibly move mouse pointer
Inputs: Position: x & y
Whether to move cursor: MoveCursor
Returns: void
History:
*************************************************************************/
void SetSecondary(long x, long y, int MoveCursor)
{
g_pGlobalData->ptCurrentMouse.x = x;
g_pGlobalData->ptCurrentMouse.y = y;
if (MoveCursor)
{
// Check if co-ordinates are valid, At many places causes
// the cursor to vanish...
if ( x > 0 && y > 0 )
SetCursorPos(x,y);
}
// Tell everyone where the cursor is.
// g_pGlobalData->uMSG_MSR_Cursor set using RegisterWindowMessage below in InitMSAA
SendMessage(HWND_BROADCAST,g_pGlobalData->uMSG_MSR_Cursor,x,y);
}
/*************************************************************************
Function: TrackCursor
Purpose: This is a callback in responce to a SetTimer it calls SetSecondary
then kills the timer and resets the global timer flag.
Returns: void
History:
*************************************************************************/
VOID CALLBACK TrackCursor(HWND hwnd, // handle to window
UINT uMsg, // WM_TIMER message
UINT_PTR idEvent, // timer identifier
DWORD dwTime ) // current system time
{
KillTimer( NULL, g_uTimer );
g_uTimer = 0;
SetSecondary(g_ptMoveCursor.x, g_ptMoveCursor.y, TRUE);
return;
}
VOID GetStateString(LONG lState,
LONG lStateMask,
LPTSTR szState,
UINT cchState )
{
int iStateBit;
DWORD lStateBits;
LPTSTR lpszT;
UINT cchT;
bool fFirstTime = true;
cchState -= 1; // leave room for the null
if ( !szState )
return;
*szState = TEXT('\0');
for ( iStateBit = 0, lStateBits = 1; iStateBit < 32; iStateBit++, (lStateBits <<= 1) )
{
if ( !fFirstTime && cchState > 2)
{
*szState++ = TEXT(',');
*szState++ = TEXT(' ');
cchState -= 2;
}
*szState = TEXT('\0'); // make sure we are always null terminated
if (lState & lStateBits & lStateMask)
{
cchT = GetStateText(lStateBits, szState, cchState);
szState += cchT;
cchState -= cchT;
fFirstTime = false;
}
}
}
/*************************************************************************
Function: BackToApplication
Purpose: Set the focus back to the application that we came from with F12
Inputs: void
Returns: void
History:
*************************************************************************/
void BackToApplication(void)
{
CScopeMutex csMutex;
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait))
SetForegroundWindow(g_pGlobalData->hwndHelp);
}
/*************************************************************************
Function: InitKeys
Purpose: Set up processing for global hot keys
Inputs: HWND hwnd
Returns: BOOL - TRUE if successful
History:
*************************************************************************/
BOOL InitKeys(HWND hwnd)
{
HMODULE hModSelf;
CScopeMutex csMutex;
if (!csMutex.Create(g_szMutexNarrator, c_nMutexWait))
return FALSE;
// If someone else has a hook installed, fail.
if (g_pGlobalData->hwndMSR)
return FALSE;
// Save off the hwnd to send messages to
g_pGlobalData->hwndMSR = hwnd;
DBPRINTF(TEXT("InitKeys: hwndMSR = 0x%x hwnd = 0x%x\r\n"), g_pGlobalData->hwndMSR, hwnd);
// Get the module handle for this DLL
hModSelf = GetModuleHandle(TEXT("NarrHook.dll"));
if(!hModSelf)
return FALSE;
// Set up the global keyboard hook
g_hhookKey = SetWindowsHookEx(WH_KEYBOARD, // What kind of hook
KeyboardProc,// Proc to send to
hModSelf, // Our Module
0); // For all threads
// and set up the global mouse hook
g_hhookMouse = SetWindowsHookEx(WH_MOUSE, // What kind of hook
MouseProc, // Proc to send to
hModSelf, // Our Module
0); // For all threads
// Return TRUE|FALSE based on result
return g_hhookKey != NULL && g_hhookMouse != NULL;
}
/*************************************************************************
Function: UninitKeys
Purpose: Deinstall the hooks
Inputs: void
Returns: BOOL - TRUE if successful
History:
*************************************************************************/
BOOL UninitKeys(void)
{
CScopeMutex csMutex;
if (!csMutex.Create(g_szMutexNarrator, c_nMutexWait))
return FALSE;
// Reset
DBPRINTF(TEXT("UninitKeys setting hwndMSR NULL\r\n"));
g_pGlobalData->hwndMSR = NULL;
// Unhook keyboard if that was hooked
if (g_hhookKey)
{
UnhookWindowsHookEx(g_hhookKey);
g_hhookKey = NULL;
}
// Unhook mouse if that was hooked
if (g_hhookMouse)
{
UnhookWindowsHookEx(g_hhookMouse);
g_hhookMouse = NULL;
}
return TRUE;
}
/*************************************************************************
Function: KeyboardProc
Purpose: Gets called for keys hit
Inputs: void
Returns: BOOL - TRUE if successful
History:
*************************************************************************/
LRESULT CALLBACK KeyboardProc(int code, // hook code
WPARAM wParam, // virtual-key code
LPARAM lParam) // keystroke-message information
{
int state = 0;
int ihotk;
g_pGlobalData->fDoingPassword = FALSE;
if (code == HC_ACTION)
{
// If this is a key up, bail out now.
if (!(lParam & 0x80000000))
{
g_pGlobalData->fMouseUp = TRUE;
g_pGlobalData->nSpeakWindowSoon = FALSE;
g_pGlobalData->fJustHadSpace = FALSE;
if (lParam & 0x20000000)
{ // get ALT state
state = MSR_ALT;
SpeakMute(0);
}
if (GetKeyState(VK_SHIFT) < 0)
state |= MSR_SHIFT;
if (GetKeyState(VK_CONTROL) < 0)
state |= MSR_CTRL;
for (ihotk = 0; ihotk < CKEYS_HOT; ihotk++)
{
if ((rgHotKeys[ihotk].keyVal == wParam) &&
(state == rgHotKeys[ihotk].status))
{
// Call the function
SpeakMute(0);
(*rgHotKeys[ihotk].lFunction)(rgHotKeys[ihotk].nOption);
return(1);
}
}
// ROBSI: 10-11-99 -- Work Item: Should be able to use the code in OnFocusChangedEvent
// that sets the fDoingPassword flag, but that means
// changing the handling of StateChangeEvents to prevent
// calling OnFocusChangedEvent. For now, we'll just use
// call GetGUIThreadInfo to determine the focused window
// and then rely on OLEACC to tell us if it is a PWD field.
// ROBSI <begin>
HWND hwndFocus = NULL;
GUITHREADINFO gui;
// Use the foreground thread. If nobody is the foreground, nobody has
// the focus either.
gui.cbSize = sizeof(GUITHREADINFO);
if ( GetGUIThreadInfo(0, &gui) )
{
hwndFocus = gui.hwndFocus;
}
if (hwndFocus != NULL)
{
// Use OLEACC to detect password fields. It turns out to be more
// reliable than SendMessage(GetFocus(), EM_GETPASSWORDCHAR, 0, 0L).
VARIANT varChild;
IAccessible *pIAcc = GetFocusedIAccessibile( hwndFocus, &varChild );
if ( pIAcc )
{
// Test the password bit...
VARIANT varState;
VariantInit(&varState);
HRESULT hr = pIAcc->get_accState(varChild, &varState);
if ((S_OK == hr) && (varState.vt == VT_I4) && (varState.lVal & STATE_SYSTEM_PROTECTED))
{
g_pGlobalData->fDoingPassword = TRUE;
}
pIAcc->Release();
}
// ROBSI: OLEACC does not always properly detect password fields.
// Therefore, we use Win32 as a backup.
if (!g_pGlobalData->fDoingPassword)
{
TCHAR szClassName[256];
// Verify this control is an Edit or RichEdit control to avoid
// sending EM_ messages to random controls.
// POTENTIAL BUG? If login dialog changes to another class, we'll break.
if ( RealGetWindowClass( hwndFocus, szClassName, ARRAYSIZE(szClassName)) )
{
if ((0 == lstrcmp(szClassName, TEXT("Edit"))) ||
(0 == lstrcmp(szClassName, TEXT("RICHEDIT"))) ||
(0 == lstrcmp(szClassName, TEXT("RichEdit20A"))) ||
(0 == lstrcmp(szClassName, TEXT("RichEdit20W")))
)
{
g_pGlobalData->fDoingPassword = (SendMessage(hwndFocus, EM_GETPASSWORDCHAR, 0, 0L) != NULL);
}
}
}
}
// ROBSI <end>
if (g_pGlobalData->fDoingPassword)
{
// ROBSI: 10-11-99
// Go ahead and speak keys that are not printable but will
// help the user understand what state they are in.
switch (wParam)
{
case VK_CAPITAL:
if (g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS)
{
SpeakMute(0);
if ( GetKeyState(VK_CAPITAL) & 0x0F )
SpeakStringId(IDS_CAPS_ON);
else
SpeakStringId(IDS_CAPS_OFF);
}
break;
case VK_NUMLOCK:
if (g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS)
{
SpeakMute(0);
if ( GetKeyState(VK_NUMLOCK) & 0x0F )
SpeakStringId(IDS_NUM_ON);
else
SpeakStringId(IDS_NUM_OFF);
}
break;
case VK_DELETE:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_DELETE);
}
break;
case VK_INSERT:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_INSERT);
}
break;
case VK_BACK:
if (g_pGlobalData->nEchoChars & MSR_ECHOBACK)
{
SpeakMute(0);
SpeakStringId(IDS_BACKSPACE);
}
break;
case VK_TAB:
SpeakMute(0);
if (g_pGlobalData->nEchoChars & MSR_ECHOTAB)
SpeakStringId(IDS_TAB);
break;
case VK_CONTROL:
SpeakMute(0); // always mute when control held down!
if ((g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS) && !(g_pGlobalData->fJustHadShiftKeys & MSR_CTRL))
{
SpeakStringId(IDS_CONTROL);
// ROBSI: Commenting out to avoid modifying Global State
// g_pGlobalData->fJustHadShiftKeys |= MSR_CTRL;
}
break;
default:
SpeakMute(0);
SpeakStringId(IDS_PASS);
break;
}
return (CallNextHookEx(g_hhookKey, code, wParam, lParam));
}
TCHAR buff[20];
BYTE KeyState[256];
UINT ScanCode;
GetKeyboardState(KeyState);
if ((g_pGlobalData->nEchoChars & MSR_ECHOALNUM) &&
(ScanCode = MapVirtualKeyEx((UINT)wParam, 2,GetKeyboardLayout(0))))
{
#ifdef UNICODE
ToUnicode((UINT)wParam,ScanCode,KeyState, buff,10,0);
#else
ToAscii((UINT)wParam,ScanCode,KeyState,(unsigned short *)buff,0);
#endif
// Use 'GetStringTypeEx()' instead of _istprint()
buff[1] = 0;
WORD wCharType;
WORD fPrintable = C1_UPPER|C1_LOWER|C1_DIGIT|C1_SPACE|C1_PUNCT|C1_BLANK|C1_XDIGIT|C1_ALPHA;
GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE1, buff, 1, &wCharType);
if (wCharType & fPrintable)
{
SpeakMute(0);
SpeakStringAll(buff);
}
}
// All new: Add speech for all keys...AK
switch (wParam) {
case VK_SPACE:
g_pGlobalData->fJustHadSpace = TRUE;
if (g_pGlobalData->nEchoChars & MSR_ECHOSPACE)
{
SpeakMute(0);
SpeakStringId(IDS_SPACE);
}
break;
case VK_LWIN:
case VK_RWIN:
if (g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS)
{
SpeakMute(0);
g_pGlobalData->fStartPressed = TRUE;
SpeakStringId(IDS_WINKEY);
}
break;
case VK_CAPITAL:
if (g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS)
{
SpeakMute(0);
if ( GetKeyState(VK_CAPITAL) & 0x0F )
SpeakStringId(IDS_CAPS_ON);
else
SpeakStringId(IDS_CAPS_OFF);
}
break;
case VK_SNAPSHOT:
if (g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS)
{
SpeakMute(0);
SpeakStringId(IDS_PRINT);
}
break;
case VK_ESCAPE:
if (g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS)
{
SpeakMute(0);
SpeakStringId(IDS_ESC);
}
break;
case VK_NUMLOCK:
if (g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS)
{
SpeakMute(0);
if ( GetKeyState(VK_NUMLOCK) & 0x0F )
SpeakStringId(IDS_NUM_ON);
else
SpeakStringId(IDS_NUM_OFF);
}
break;
case VK_DELETE:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_DELETE);
}
break;
case VK_INSERT:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_INSERT);
}
break;
case VK_HOME:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_HOME);
}
break;
case VK_END:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_END);
}
break;
case VK_PRIOR:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_PAGEUP);
}
break;
case VK_NEXT:
if (g_pGlobalData->nEchoChars & MSR_ECHODELETE)
{
SpeakMute(0);
SpeakStringId(IDS_PAGEDOWN);
}
break;
case VK_BACK:
if (g_pGlobalData->nEchoChars & MSR_ECHOBACK)
{
SpeakMute(0);
SpeakStringId(IDS_BACKSPACE);
}
break;
case VK_TAB:
SpeakMute(0);
if (g_pGlobalData->nEchoChars & MSR_ECHOTAB)
SpeakStringId(IDS_TAB);
break;
case VK_CONTROL:
SpeakMute(0); // always mute when control held down!
if ((g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS) && !(g_pGlobalData->fJustHadShiftKeys & MSR_CTRL))
{
SpeakStringId(IDS_CONTROL);
g_pGlobalData->fJustHadShiftKeys |= MSR_CTRL;
}
break;
case VK_MENU:
if ((g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS) && !(g_pGlobalData->fJustHadShiftKeys & MSR_ALT))
{
SpeakMute(0);
SpeakStringId(IDS_ALT);
}
break;
case VK_SHIFT:
if ((g_pGlobalData->nEchoChars & MSR_ECHOMODIFIERS) && !(g_pGlobalData->fJustHadShiftKeys & MSR_SHIFT))
{
SpeakMute(0);
SpeakStringId(IDS_SHIFT);
g_pGlobalData->fJustHadShiftKeys |= MSR_SHIFT;
}
break;
case VK_RETURN:
if (g_pGlobalData->nEchoChars & MSR_ECHOENTER)
{
SpeakMute(0);
SpeakStringId(IDS_RETURN);
}
break;
}
// set flags for moving around edit controls
g_pGlobalData->nMsrDoNext = MSR_DONOWT;
if (state == MSR_CTRL && (wParam == VK_LEFT || wParam == VK_RIGHT))
{
SpeakMute(0);
g_pGlobalData->nMsrDoNext = MSR_DOWORD;
}
else if ((state & MSR_CTRL) && (state & MSR_SHIFT) && (wParam == VK_LEFT))
g_pGlobalData->nMsrDoNext = MSR_DOWORD;
else if ((state & MSR_CTRL) && (state & MSR_SHIFT) && (wParam == VK_RIGHT))
g_pGlobalData->nMsrDoNext = MSR_DOWORDR;
else if ((state & MSR_SHIFT) && (wParam == VK_LEFT))
g_pGlobalData->nMsrDoNext = MSR_DOCHAR;
else if ((state & MSR_SHIFT) && (wParam == VK_RIGHT))
g_pGlobalData->nMsrDoNext = MSR_DOCHARR;
else if ((state & MSR_CTRL) && (wParam == VK_UP || wParam == VK_DOWN))
g_pGlobalData->nMsrDoNext = MSR_DOLINE;
else if ((state & MSR_SHIFT) && (wParam == VK_UP))
g_pGlobalData->nMsrDoNext = MSR_DOLINE;
else if ((state & MSR_SHIFT) && (wParam == VK_DOWN))
g_pGlobalData->nMsrDoNext = MSR_DOLINED;
else if (state == 0)
{ // i.e. no shift keys
switch (wParam)
{
case VK_LEFT:
case VK_RIGHT:
g_pGlobalData->nMsrDoNext = MSR_DOCHAR;
SpeakMute(0);
break;
case VK_DOWN:
case VK_UP:
g_pGlobalData->nMsrDoNext = MSR_DOLINE;
SpeakMute(0);
break;
case VK_F3:
if (GetForegroundWindow() == g_pGlobalData->hwndMSR)
{
PostMessage(g_pGlobalData->hwndMSR, WM_MSRCONFIGURE, 0, 0);
return(1);
}
break;
case VK_F9:
if (GetForegroundWindow() == g_pGlobalData->hwndMSR)
{
PostMessage(g_pGlobalData->hwndMSR, WM_MSRQUIT, 0, 0);
return(1);
}
break;
} // end switch wParam (keycode)
} // end if no shift keys pressed
} // end if key down
} // end if code == HC_ACTION
g_pGlobalData->fJustHadShiftKeys = state;
return (CallNextHookEx(g_hhookKey, code, wParam, lParam));
}
/*************************************************************************
Function: MouseProc
Purpose: Gets called for mouse eventshit
Inputs: void
Returns: BOOL - TRUE if successful
History:
*************************************************************************/
LRESULT CALLBACK MouseProc(int code, // hook code
WPARAM wParam, // virtual-key code
LPARAM lParam) // keystroke-message information
{
CScopeMutex csMutex;
if (!csMutex.Create(g_szMutexNarrator, c_nMutexWait))
return 1; // TODO not sure what to do here; MSDN is unclear about retval
LRESULT retVal = CallNextHookEx(g_hhookMouse, code, wParam, lParam);
if (code == HC_ACTION)
{
switch (wParam)
{ // want to know if mouse is down
case WM_NCLBUTTONDOWN:
case WM_LBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_RBUTTONDOWN:
// to keep sighted people happy when using mouse shut up
// the speech on mouse down
// SpeakMute(0);
// Chnage to PostMessage works for now: a-anilk
PostMessage(g_pGlobalData->hwndMSR, WM_MUTE, 0, 0);
// If it is then don't move mouse pointer when focus set!
g_pGlobalData->fMouseUp = FALSE;
break;
case WM_NCLBUTTONUP:
case WM_LBUTTONUP:
case WM_NCRBUTTONUP:
case WM_RBUTTONUP:
// g_pGlobalData->fMouseUp = TRUE; Don't clear flag here - wait until key pressed before enabling auto mouse movemens again
break;
}
}
return(retVal);
}
// --------------------------------------------------------------------------
//
// Entry point: DllMain()
//
// Some stuff only needs to be done the first time the DLL is loaded, and the
// last time it is unloaded, which is to set up the values for things in the
// shared data segment, including SharedMemory support.
//
// InterlockedIncrement() and Decrement() return 1 if the result is
// positive, 0 if zero, and -1 if negative. Therefore, the only
// way to use them practically is to start with a counter at -1.
// Then incrementing from -1 to 0, the first time, will give you
// a unique value of 0. And decrementing the last time from 0 to -1
// will give you a unique value of -1.
//
// --------------------------------------------------------------------------
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID pvReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
g_Hinst = hinst;
// Create the memory mapped file for shared global data
CreateMappedFile();
break;
case DLL_PROCESS_DETACH:
// Close the memory mapped file for shared global data
CloseMappedFile();
break;
}
return(TRUE);
}
/*************************************************************************
Function: WinEventProc
Purpose: Callback function handles events
Inputs: HWINEVENTHOOK hEvent - Handle of the instance of the event proc
DWORD event - Event type constant
HWND hwndMsg - HWND of window generating event
LONG idObject - ID of object generating event
LONG idChild - ID of child generating event (0 if object)
DWORD idThread - ID of thread generating event
DWORD dwmsEventTime - Timestamp of event
Returns:
History:
*************************************************************************/
void CALLBACK WinEventProc(HWINEVENTHOOK hEvent, DWORD event, HWND hwndMsg,
LONG idObject, LONG idChild, DWORD idThread,
DWORD dwmsEventTime)
{
// NOTE: If any more events are handled by ProcessWinEvent, they must be
// added to this switch statement.
// no longer will we get an IAccessible here - the helper thread will
// get the info from the Stack and get and use the IAccessible there.
switch (event)
{
case EVENT_OBJECT_STATECHANGE:
case EVENT_OBJECT_VALUECHANGE:
case EVENT_OBJECT_SELECTION:
case EVENT_OBJECT_FOCUS:
case EVENT_OBJECT_LOCATIONCHANGE:
case EVENT_SYSTEM_MENUSTART:
case EVENT_SYSTEM_MENUEND:
case EVENT_SYSTEM_MENUPOPUPSTART:
case EVENT_SYSTEM_MENUPOPUPEND:
case EVENT_SYSTEM_SWITCHSTART:
case EVENT_SYSTEM_FOREGROUND:
case EVENT_OBJECT_SHOW:
AddEventInfoToStack(event, hwndMsg, idObject, idChild,
idThread, dwmsEventTime);
break;
} // end switch (event)
}
/*************************************************************************
Function:
Purpose:
Inputs:
Returns:
History:
*************************************************************************/
void ProcessWinEvent(DWORD event, HWND hwndMsg, LONG idObject, LONG
idChild, DWORD idThread,DWORD dwmsEventTime)
{
TCHAR szName[256];
// What type of event is coming through?
// bring secondary focus here: Get from Object inspector
// bring mouse pointer here if flag set.
if (g_pGlobalData->nReviewLevel != 2)
{
switch (event)
{
case EVENT_SYSTEM_SWITCHSTART:
SpeakMute(0);
SpeakString(TEXT("ALT TAB"));
break;
case EVENT_SYSTEM_MENUSTART:
break;
case EVENT_SYSTEM_MENUEND:
SpeakMute(0);
if (g_pGlobalData->fAnnounceMenu)
SpeakStringId(IDS_MENUEND);
break;
case EVENT_SYSTEM_MENUPOPUPSTART:
if (g_pGlobalData->fAnnouncePopup)
{
SpeakMute(0);
SpeakStringId(IDS_POPUP);
}
break;
case EVENT_SYSTEM_MENUPOPUPEND:
SpeakMute(0);
if (g_pGlobalData->fAnnouncePopup)
SpeakStringId(IDS_POPUPEND);
break;
case EVENT_OBJECT_STATECHANGE :
DBPRINTF(TEXT("EVENT_OBJECT_STATECHANGE\r\n"));
// want to catch state changes on spacebar pressed
switch (g_pGlobalData->fJustHadSpace)
{
case 0 : // get out - only do this code if space just been pressed
break;
case 1 :
case 2 : // ignore the first and second time round!
g_pGlobalData->fJustHadSpace++;
break;
case 3 : // second time around speak the item
OnFocusChangedEvent(event, hwndMsg, idObject, idChild, dwmsEventTime);
g_pGlobalData->fJustHadSpace = 0;
break;
}
OnStateChangedEvent(event, hwndMsg, idObject, idChild, dwmsEventTime);
break;
case EVENT_OBJECT_VALUECHANGE :
OnValueChangedEvent(event, hwndMsg, idObject, idChild, dwmsEventTime);
break;
case EVENT_OBJECT_SELECTION :
if (GetParent(hwndMsg) == g_pGlobalData->hwndMSR)
{
// don't do this for our own or list box throws a wobbler!
break;
}
// this comes in for list items a second time after the focus
// changes BUT that gets filtered by the double speak check.
// What this catches is list item changes when cursor down in
// combo boxes!
// Make it just works for them.
OnSelectChangedEvent(event, hwndMsg, idObject, idChild, dwmsEventTime);
break;
case EVENT_OBJECT_FOCUS:
DBPRINTF(TEXT("EVENT_OBJECT_FOCUS\r\n"));
OnFocusChangedEvent(event, hwndMsg, idObject, idChild, dwmsEventTime);
break;
case EVENT_SYSTEM_FOREGROUND: // Window comes to front - speak its name!
SpeakMute(0);
SpeakStringId(IDS_FOREGROUND);
TCHAR szClassName[100];
// if the class name is CLASS_MS_WINNOTE or CLASS_HH_POPUP it's context senceitive help
// and the text will be read in OnFocusChangeEvent by SpeakObjectInfo. So we don't need to
// read the same text here and in SpeakWindow
GetClassName( hwndMsg, szClassName, ARRAYSIZE(szClassName) );
if ( (lstrcmpi(szClassName, CLASS_MS_WINNOTE ) == 0) || (lstrcmpi(szClassName, CLASS_HH_POPUP ) == 0) )
break;
GetWindowText(hwndMsg, szName, sizeof(szName)/sizeof(TCHAR)); // raid #113789
SpeakString(szName);
if (g_pGlobalData->fAnnounceWindow)
{
g_pGlobalData->nSpeakWindowSoon = TRUE; // read window when next focus set
}
break;
case EVENT_OBJECT_LOCATIONCHANGE:
// Only the caret
if (idObject != OBJID_CARET)
return;
OnLocationChangedEvent(event, hwndMsg, idObject, idChild, dwmsEventTime);
break;
case EVENT_OBJECT_SHOW:
OnObjectShowEvent(event, hwndMsg, idObject, idChild, dwmsEventTime);
break;
} // end switch (event)
} // end if review level != 2
return;
}
/*************************************************************************
Function: OnValueChangedEvent
Purpose: Receives value events
Inputs: DWORD event - What event are we processing
HWND hwnd - HWND of window generating event
LONG idObject - ID of object generating event
LONG idChild - ID of child generating event (0 if object)
DWORD idThread - ID of thread generating event
DWORD dwmsEventTime - Timestamp of event
Returns: BOOL - TRUE if succeeded
*************************************************************************/
BOOL OnValueChangedEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild,
DWORD dwmsTimeStamp)
{
HRESULT hr;
OBJINFO objCurrent;
VARIANT varRole;
IAccessible* pIAcc;
VARIANT varChild;
TCHAR szName[200];
hr = AccessibleObjectFromEvent (hwnd, idObject, idChild, &pIAcc, &varChild);
if (SUCCEEDED(hr))
{
objCurrent.hwnd = hwnd;
objCurrent.plObj = (long*)pIAcc;
objCurrent.varChild = varChild;
VariantInit(&varRole);
hr = pIAcc->get_accRole(varChild, &varRole);
if( FAILED(hr) ||
varRole.lVal != ROLE_SYSTEM_SPINBUTTON && g_pGlobalData->nMsrDoNext == MSR_DONOWT)
{
pIAcc->Release();
return(FALSE);
}
g_pGlobalData->nMsrDoNext = MSR_DONOWT; // PB 22 Nov 1998 stop this firing more than once (probably)
if (varRole.vt == VT_I4 && (
(varRole.lVal == ROLE_SYSTEM_TEXT && g_pGlobalData->nMsrDoNext != MSR_DOLINE) ||
varRole.lVal == ROLE_SYSTEM_PUSHBUTTON ||
varRole.lVal == ROLE_SYSTEM_SCROLLBAR))
{
DBPRINTF (TEXT("Don't Speak <%s>\r\n"), szName);
// don't speak 'cos it's an edit box (or others) changing value!
}
else if (!g_pGlobalData->fInternetExplorer) // don't do this for IE .. it speaks edit box too much.
{
DBPRINTF (TEXT("Now Speak!\r\n"));
SpeakMute(0);
SpeakObjectInfo(&objCurrent, FALSE);
}
else
DBPRINTF (TEXT("Do nowt!\r\n"));
pIAcc->Release();
}
return(TRUE);
}
/*************************************************************************
Function: OnSelectChangedEvent
Purpose: Receives selection change events - not from MSR though
Inputs: DWORD event - What event are we processing
HWND hwnd - HWND of window generating event
LONG idObject - ID of object generating event
LONG idChild - ID of child generating event (0 if object)
DWORD idThread - ID of thread generating event
DWORD dwmsEventTime - Timestamp of event
Returns: BOOL - TRUE if succeeded
Notes: Maybe change this to only take combo-boxes?
*************************************************************************/
BOOL OnSelectChangedEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild,
DWORD dwmsTimeStamp)
{
HRESULT hr;
IAccessible* pIAcc;
OBJINFO objCurrent;
VARIANT varRole;
VARIANT varChild;
// if we've not had a cursor style movement then sack this as it could be
// scroll bar chaging or slider moving etc to reflect rapidy moving events
hr = AccessibleObjectFromEvent (hwnd, idObject, idChild, &pIAcc, &varChild);
if (SUCCEEDED(hr))
{
objCurrent.hwnd = hwnd;
objCurrent.plObj = (long*)pIAcc;
objCurrent.varChild = varChild;
VariantInit(&varRole); // heuristic!
hr = pIAcc->get_accRole(varChild, &varRole);
if ( FAILED(hr) )
{
pIAcc->Release();
return FALSE;
}
if (varRole.vt == VT_I4 &&
varRole.lVal == ROLE_SYSTEM_LISTITEM)
{
TCHAR buffer[100];
GetClassName(hwnd,buffer,100); // Is it sysListView32
// "Don't mute here ... we lose the previous speech message which will
// have spoken the list item IF we were cursoring to list item.
// SpeakMute(0);
// don't speak unless it's a listitem
// e.g. Current Selection for Joystick from Joystick setup.
// this does mean that some list items get spoken twice!:AK
// if ( lstrcmpi(buffer, CLASS_LISTVIEW) != 0)
if ( !g_pGlobalData->fListFocus )
SpeakObjectInfo(&objCurrent,FALSE);
g_pGlobalData->fListFocus = FALSE;
}
pIAcc->Release();
}
return(TRUE);
}
/*************************************************************************
Function: OnFocusChangedEvent
Purpose: Receives focus events
Inputs: DWORD event - What event are we processing
HWND hwnd - HWND of window generating event
LONG idObject - ID of object generating event
LONG idChild - ID of child generating event (0 if object)
DWORD idThread - ID of thread generating event
DWORD dwmsEventTime - Timestamp of event
Returns: BOOL - TRUE if succeeded
*************************************************************************/
BOOL OnFocusChangedEvent(DWORD event, HWND hwnd, LONG idObject,
LONG idChild, DWORD dwmsTimeStamp)
{
HRESULT hr;
TCHAR szName[256];
TCHAR buffer[100];
IAccessible* pIAcc;
VARIANT varChild;
VARIANT varRole;
VARIANT varState;
BOOL switchWnd = FALSE;
hr = AccessibleObjectFromEvent (hwnd, idObject, idChild, &pIAcc, &varChild);
if (FAILED(hr))
return FALSE;
// Check for Bogus events...
if( !IsFocussedItem(hwnd, pIAcc, varChild) )
{
pIAcc->Release();
return FALSE;
}
// Ignore the first Start pressed events...
if ( g_pGlobalData->fStartPressed )
{
g_pGlobalData->fStartPressed = FALSE;
pIAcc->Release();
return FALSE;
}
g_pGlobalData->fDoingPassword = FALSE;
// Have we got a password char in this one
// if so then tell them and get out
VariantInit(&varState);
hr = pIAcc->get_accState(varChild, &varState);
if ( FAILED(hr) )
{
pIAcc->Release();
return FALSE;
}
if ( varState.vt == VT_EMPTY )
varState.lVal = 0;
g_pGlobalData->fDoingPassword = (varState.lVal & STATE_SYSTEM_PROTECTED);
GetClassName(hwnd,buffer,100); // is it Internet Explorer in any of its many forms?
DBPRINTF(TEXT("OnFocusChangedEvent: class name = %s\r\n"), buffer);
g_pGlobalData->fInternetExplorer = IsTridentWindow(buffer);
g_pGlobalData->fHTML_Help = FALSE;
if (lstrcmpi(buffer, CLASS_WINSWITCH) == 0)
switchWnd = TRUE;
GetClassName(GetForegroundWindow(),buffer,100);
if ((lstrcmpi(buffer, CLASS_HTMLHELP) == 0)|| (lstrcmpi(buffer, CLASS_IE_FRAME) == 0) ) { // have we got HTML Help?
g_pGlobalData->fInternetExplorer = TRUE;
g_pGlobalData->fHTML_Help = TRUE;
}
// Check to see if we are getting rapid focus changes
// Consider using the Time stamp and saving away the last object
VariantInit(&varRole);
// If the focus is being set to a list, a combo, or a dialog,
// don't say anything. We'll say something when the focus gets
// set to one of the children.
hr = pIAcc->get_accRole(varChild, &varRole); // heuristic!
if ( FAILED(hr) )
{
pIAcc->Release();
return FALSE;
}
// Special casing stuff.. Avoid repeatation for list items...
// Required to correctly process Auto suggest list boxes.
// As list items also send SelectionChange : AK
if (varRole.vt == VT_I4 )
{
switch ( varRole.lVal )
{
case ROLE_SYSTEM_DIALOG:
pIAcc->Release();
return FALSE;
break;
case ROLE_SYSTEM_TITLEBAR:
g_pGlobalData->fMouseUp = FALSE;
break;
case ROLE_SYSTEM_LISTITEM:
g_pGlobalData->fListFocus = TRUE;
break;
default:
break;
}
}
if (idObject == OBJID_WINDOW)
{
SpeakMute(0);
SpeakStringId(IDS_WINDOW);
GetWindowText(hwnd, szName, sizeof(szName)/sizeof(TCHAR)); // raid #113789
SpeakString(szName);
}
RECT rcCursor;
if ( pIAcc->accLocation(&rcCursor.left, &rcCursor.top, &rcCursor.right, &rcCursor.bottom, varChild) == S_OK )
{
const POINT ptLoc = { rcCursor.left + (rcCursor.right/2), rcCursor.top + (rcCursor.bottom/2) };
if (g_pGlobalData->fTrackInputFocus && g_pGlobalData->fMouseUp)
{
POINT CursorPosition;
GetCursorPos(&CursorPosition);
// mouse to follow if it's not already in rectangle
// (e.g manually moving mouse in menu) and mouse button up
if (CursorPosition.x < rcCursor.left
|| CursorPosition.x > (rcCursor.left+rcCursor.right)
|| CursorPosition.y < rcCursor.top
|| CursorPosition.y > (rcCursor.top+rcCursor.bottom))
{
g_ptMoveCursor.x = ptLoc.x;
g_ptMoveCursor.y = ptLoc.y;
// If we set the cursor immediately extraneous events the
// hovering on menu items causes feed back which results
// in the cursor going back and forth between menu items.
// This code sets a timer so that the cursor is set after things settle down
if ( g_uTimer == 0 )
g_uTimer = SetTimer( NULL, 0, 100, TrackCursor );
// If the focus events are from cursor movement this will ignore the extra
// event that cause the feed back
if ( g_pGlobalData->nMsrDoNext != MSR_DONOWT )
g_pGlobalData->fMouseUp = FALSE;
}
}
else
{
SetSecondary(ptLoc.x, ptLoc.y, FALSE);
}
}
OBJINFO objCurrent;
objCurrent.hwnd = hwnd;
objCurrent.plObj = (long*)pIAcc;
objCurrent.varChild = varChild;
// If the event is from the switch window,
// Then mute the current speech before proceeding...AK
if ( switchWnd && g_pGlobalData->fListFocus )
SpeakMute(0);
DBPRINTF(TEXT("OnFocusChangedEvent: Calling SpeakObjectInfo...\r\n"));
SpeakObjectInfo(&objCurrent,TRUE);
if (g_pGlobalData->fDoingPassword)
{
pIAcc->Release();
return FALSE;
}
if (g_pGlobalData->nSpeakWindowSoon)
{
DBPRINTF(TEXT("OnFocusChangedEvent: Calling SpeakWindow\r\n"));
SpeakWindow(0);
g_pGlobalData->nSpeakWindowSoon = FALSE;
}
pIAcc->Release();
return TRUE;
}
/*************************************************************************
Function: OnStateChangedEvent
Purpose: Receives focus events
Inputs: DWORD event - What event are we processing
HWND hwnd - HWND of window generating event
LONG idObject - ID of object generating event
LONG idChild - ID of child generating event (0 if object)
DWORD idThread - ID of thread generating event
DWORD dwmsEventTime - Timestamp of event
Returns: BOOL - TRUE if succeeded
*************************************************************************/
BOOL OnStateChangedEvent(DWORD event, HWND hwnd, LONG idObject,
LONG idChild, DWORD dwmsTimeStamp)
{
HRESULT hr;
IAccessible* pIAcc;
VARIANT varChild;
VARIANT varRole;
hr = AccessibleObjectFromEvent (hwnd, idObject, idChild, &pIAcc, &varChild);
if (FAILED(hr))
return (FALSE);
// Check for Bogus events...
if( !IsFocussedItem(hwnd, pIAcc, varChild) )
{
pIAcc->Release();
return (FALSE);
}
VariantInit(&varRole);
hr = pIAcc->get_accRole(varChild, &varRole);
if ( FAILED(hr) )
{
pIAcc->Release();
return FALSE;
}
// Special casing stuff.. Handle State change for
// Outline items only for now
if (varRole.vt == VT_I4 )
{
switch ( varRole.lVal )
{
case ROLE_SYSTEM_OUTLINEITEM:
{
OBJINFO objCurrent;
objCurrent.hwnd = hwnd;
objCurrent.plObj = (long*)pIAcc;
objCurrent.varChild = varChild;
SpeakObjectInfo(&objCurrent,TRUE);
}
break;
default:
break;
}
}
pIAcc->Release();
return(TRUE);
}
/*************************************************************************
Function: OnLocationChangedEvent
Purpose: Receives location change events - for the caret
Inputs: DWORD event - What event are we processing
HWND hwnd - HWND of window generating event
LONG idObject - ID of object generating event
LONG idChild - ID of child generating event (0 if object)
DWORD idThread - ID of thread generating event
DWORD dwmsEventTime - Timestamp of event
Returns: BOOL - TRUE if succeeded
*************************************************************************/
BOOL OnLocationChangedEvent(DWORD event, HWND hwnd, LONG idObject,
LONG idChild, DWORD dwmsTimeStamp)
{
//
// Get the caret position and save it.
//
// flag set by key down code - here do appropriate action after
// caret has moved
if (g_pGlobalData->nMsrDoNext)
{ // read char, word etc.
WORD wLineNumber;
WORD wLineIndex;
WORD wLineLength;
DWORD dwGetSel;
DWORD wStart;
DWORD wEnd;
WORD wColNumber;
WORD wEndWord;
LPTSTR pszTextShared;
HANDLE hProcess;
int nSomeInt;
int *p; // PB 22 Nov 1998 Use this to get the size of the buffer in to array
DWORD LineStart;
// Send the EM_GETSEL message to the edit control.
// The low-order word of the return value is the character
// position of the caret relative to the first character in the
// edit control.
dwGetSel = (WORD)SendMessage(hwnd, EM_GETSEL, (WPARAM)(LPDWORD) &wStart, (LPARAM)(LPDWORD) &wEnd);
if (dwGetSel == -1)
{
return FALSE;
}
LineStart = wStart;
// New: Check for the selected text: AK
if ( g_pGlobalData->nMsrDoNext == MSR_DOCHARR )
LineStart = wEnd;
else if ( g_pGlobalData->nMsrDoNext == MSR_DOLINED )
LineStart = wEnd - 1;
else if ( g_pGlobalData->nMsrDoNext == MSR_DOWORDR )
LineStart = wEnd;
// SteveDon: get the line for the start of the selection
wLineNumber = (WORD)SendMessage(hwnd,EM_LINEFROMCHAR, LineStart, 0L);
// get the first character on that line that we're on.
wLineIndex = (WORD)SendMessage(hwnd,EM_LINEINDEX, wLineNumber, 0L);
// get the length of the line we're on
wLineLength = (WORD)SendMessage(hwnd,EM_LINELENGTH, LineStart, 0L);
// Subtract the LineIndex from the start of the selection,
// This result is the column number of the caret position.
wColNumber = LineStart - wLineIndex;
// if we can't hold the text we want, say nothing.
if (wLineLength > MAX_TEXT)
{
return FALSE;
}
// To get the text of a line, send the EM_GETLINE message. When
// the message is sent, wParam is the line number to get and lParam
// is a pointer to the buffer that will hold the text. When the message
// is sent, the first word of the buffer specifies the maximum number
// of characters that can be copied to the buffer.
// We'll allocate the memory for the buffer in "shared" space so
// we can all see it.
// Allocate a buffer to hold it
// PB 22 Nov 1998 Make it work!!! next 6 lines new. Use global shared memory to do this!!!
nSomeInt = wLineLength+1;
if (nSomeInt >= 2000)
nSomeInt = 1999;
p = (int *) g_pGlobalData->pszTextLocal;
*p = nSomeInt;
SendMessage(hwnd, EM_GETLINE, (WPARAM)wLineNumber, (LPARAM)g_pGlobalData->pszTextLocal);
g_pGlobalData->pszTextLocal[nSomeInt] = 0;
// At this stage, pszTextLocal points to a (possibly) empty string.
// We deal with that later...
switch (g_pGlobalData->nMsrDoNext)
{
case MSR_DOWORDR:
case MSR_DOWORD:
if (wColNumber >= wLineLength)
{
SpeakMute(0);
SpeakStringId(IDS_LINEEND);
break;
}
else
{
for (wEndWord = wColNumber; wEndWord < wLineLength; wEndWord++)
{
if (g_pGlobalData->pszTextLocal[wEndWord] <= ' ')
{
break;
}
}
wEndWord++;
if (wEndWord-wColNumber < ARRAYSIZE(g_pGlobalData->pszTextLocal))
{
lstrcpyn(g_pGlobalData->pszTextLocal,g_pGlobalData->pszTextLocal+wColNumber,wEndWord-wColNumber);
g_pGlobalData->pszTextLocal[wEndWord-wColNumber] = TEXT('\0');
}
SpeakMute(0);
SpeakStringAll(g_pGlobalData->pszTextLocal);
}
break;
case MSR_DOCHARR:
wColNumber = LineStart - wLineIndex - 1;
// Fall Through
case MSR_DOCHAR: // OK now read character to left and right
if (wColNumber >= wLineLength)
{
SpeakMute(0);
SpeakStringId(IDS_LINEEND);
}
else if (g_pGlobalData->pszTextLocal[wColNumber] == TEXT(' '))
{
SpeakMute(0);
SpeakStringId(IDS_SPACE);
}
else
{
g_pGlobalData->pszTextLocal[0] = g_pGlobalData->pszTextLocal[wColNumber];
g_pGlobalData->pszTextLocal[1] = 0;
SpeakMute(0);
SpeakStringAll(g_pGlobalData->pszTextLocal);
}
break;
case MSR_DOLINED:
// Fall through
case MSR_DOLINE:
g_pGlobalData->pszTextLocal[wLineLength] = 0; // add null
SpeakMute(0);
SpeakStringAll(g_pGlobalData->pszTextLocal);
break;
} // end switch (g_pGlobalData->nMsrDoNext)
} // end if (g_pGlobalData->nMsrDoNext)
RECT rcCursor;
IAccessible* pIAcc;
HRESULT hr;
VARIANT varChild;
SetRectEmpty(&rcCursor); // now sort out mouse position as apprpropriate
hr = AccessibleObjectFromEvent (hwnd, idObject, idChild, &pIAcc, &varChild);
if (SUCCEEDED(hr))
{
hr = pIAcc->accLocation(&rcCursor.left, &rcCursor.top,
&rcCursor.right, &rcCursor.bottom,
varChild);
// Move mouse cursor, Only when Track mouse option is selcted: AK
if (SUCCEEDED(hr) && g_pGlobalData->fTrackInputFocus && g_pGlobalData->fTrackCaret && g_pGlobalData->fMouseUp )
{
const POINT ptLoc = { rcCursor.left + (rcCursor.right/2), rcCursor.top + (rcCursor.bottom/2) };
g_ptMoveCursor.x = ptLoc.x;
g_ptMoveCursor.y = ptLoc.y;
if ( g_uTimer == 0 )
g_uTimer = SetTimer( NULL, 0, 100, TrackCursor );
else
SetSecondary( ptLoc.x, ptLoc.y, FALSE );
}
pIAcc->Release();
}
return TRUE;
}
/*************************************************************************
Function: OnObjectShowEvent
Purpose: Receives object show events - This is used for balloon tips
Inputs: DWORD event - What event are we processing
HWND hwnd - HWND of window generating event
LONG idObject - ID of object generating event
LONG idChild - ID of child generating event (0 if object)
DWORD idThread - ID of thread generating event
DWORD dwmsEventTime - Timestamp of event
Returns: BOOL - TRUE if succeeded
*************************************************************************/
BOOL OnObjectShowEvent(DWORD event, HWND hwnd, LONG idObject,
LONG idChild, DWORD dwmsTimeStamp)
{
IAccessible* pAcc = NULL;
HRESULT hr;
VARIANT varChild;
varChild.vt = VT_EMPTY;
IAccessible* pAccTemp = NULL;
VARIANT varChildTemp;
varChildTemp.vt = VT_I4;
varChildTemp.lVal = CHILDID_SELF;
if( idObject == OBJID_WINDOW )
{
// Most common case - get the client object, check if role is balloon tip...
hr = AccessibleObjectFromWindow( hwnd, OBJID_CLIENT, IID_IAccessible, (void **) &pAccTemp );
if( hr == S_OK && pAccTemp )
{
if( !CheckIsBalloonTipElseRelease( pAccTemp, varChild, &pAcc, &varChild ) )
return FALSE;
}
}
// if we didn't find a balloon tip try and get it from the event instead
if ( !pAcc && varChild.vt != VT_I4 )
{
hr = AccessibleObjectFromEvent( hwnd, idObject, idChild, &pAccTemp, &varChildTemp );
if( hr == S_OK && pAccTemp )
{
if( !CheckIsBalloonTipElseRelease( pAccTemp, varChildTemp, &pAcc, &varChild ) )
return FALSE;
}
else
{
return FALSE;
}
}
TCHAR szRole[ 128 ] = TEXT("");
VARIANT varRole;
hr = pAcc->get_accRole( varChild, & varRole );
if( hr == S_OK && varRole.vt == VT_I4 )
GetRoleText( varRole.lVal, szRole, ARRAYSIZE( szRole ) );
BSTR bstrName = NULL;
TCHAR szName [ 1025 ] = TEXT("");
TCHAR * pszName;
hr = pAcc->get_accName( varChild, & bstrName );
if( hr == S_OK && bstrName != NULL && bstrName[ 0 ] != '\0' )
{
#ifdef UNICODE
pszName = bstrName;
#else
WideCharToMultiByte( CP_ACP, 0, bstrName, -1, szName, ARRAYSIZE( szName ), NULL, NULL );
pszName = szName;
#endif
}
TCHAR szText[ 1025 ];
lstrcpyn(szText, szRole, ARRAYSIZE(szText));
lstrcatn(szText, TEXT(": "), ARRAYSIZE(szText));
lstrcatn(szText, pszName, ARRAYSIZE(szText));
szText[ARRAYSIZE(szText)-1] = TEXT('\0');
SpeakString(szText);
SysFreeString(bstrName);
return TRUE;
}
/*************************************************************************
Function: InitMSAA
Purpose: Initalize the Active Accessibility subsystem, including
initializing the helper thread, installing the WinEvent
hook, and registering custom messages.
Inputs: none
Returns: BOOL - TRUE if successful
History:
*************************************************************************/
BOOL InitMSAA(void)
{
CScopeMutex csMutex;
if (!csMutex.Create(g_szMutexNarrator, c_nMutexWait))
return FALSE;
// Call this FIRST to initialize the helper thread
InitHelperThread();
// Set up event call back
g_hEventHook = SetWinEventHook(EVENT_MIN, // We want all events
EVENT_MAX,
GetModuleHandle(TEXT("NarrHook.dll")), // Use our own module
WinEventProc, // Our callback function
0, // All processes
0, // All threads
WINEVENT_OUTOFCONTEXT /* WINEVENT_INCONTEXT */);
// Receive async events
// JMC: For Safety, lets always be 'out of context'. Who cares if there is a
// performance penalty.
// By being out of context, we guarantee the we won't bring down other apps if
// there is a bug in our event hook.
// Did we install correctly?
if (g_hEventHook)
{
//
// register own own message for giving the cursor position
//
g_pGlobalData->uMSG_MSR_Cursor = RegisterWindowMessage(TEXT("MSR cursor"));
return TRUE;
}
// Did not install properly - clean up and fail
UnInitHelperThread();
return FALSE;
}
/*************************************************************************
Function: UnInitMSAA
Purpose: Shuts down the Active Accessibility subsystem
Inputs: none
Returns: BOOL - TRUE if successful
History:
*************************************************************************/
BOOL UnInitMSAA(void)
{
CScopeMutex csMutex;
if (csMutex.Create(g_szMutexNarrator, c_nMutexWait))
{
// Remove the WinEvent hook
UnhookWinEvent(g_hEventHook);
// Call this LAST so that the helper thread can finish up.
UnInitHelperThread();
}
// return true; we're exiting and there's not much that can be done
return TRUE;
}
// --------------------------------------------------------------------------
//
// GetObjectAtCursor()
//
// Gets the object the cursor is over.
//
// --------------------------------------------------------------------------
IAccessible * GetObjectAtCursor(VARIANT * pvarChild,HRESULT* pResult)
{
POINT pt;
IAccessible * pIAcc;
HRESULT hr;
//
// Get cursor object & position
//
if (g_pGlobalData->ptCurrentMouse.x < 0)
GetCursorPos(&pt);
else
pt = g_pGlobalData->ptCurrentMouse;
//
// Get object here.
//
VariantInit(pvarChild);
hr = AccessibleObjectFromPoint(pt, &pIAcc, pvarChild);
*pResult = hr;
if (!SUCCEEDED(hr)) {
return NULL;
}
return pIAcc;
}
/*************************************************************************
Function: SpeakItem
Purpose:
Inputs:
Returns:
History:
*************************************************************************/
void SpeakItem(int nOption)
{
TCHAR tszDesc[256];
VARIANT varChild;
IAccessible* pIAcc;
HRESULT hr;
POINT ptMouse;
BSTR bstr;
SpeakString(TEXT(" ")); // reset last utterence
// Important to init variants
VariantInit(&varChild);
//
// Get cursor object & position
//
if (g_pGlobalData->ptCurrentMouse.x < 0)
GetCursorPos(&ptMouse);
else
ptMouse = g_pGlobalData->ptCurrentMouse;
hr = AccessibleObjectFromPoint(ptMouse, &pIAcc, &varChild);
// Check to see if we got a valid pointer
if (SUCCEEDED(hr))
{
hr = pIAcc->get_accDescription(varChild, &bstr);
if ( FAILED(hr) )
bstr = NULL;
if (bstr)
{
#ifdef UNICODE
lstrcpyn(tszDesc,bstr,ARRAYSIZE(tszDesc));
tszDesc[ARRAYSIZE(tszDesc)-1] = TEXT('\0');
#else
// If we got back a string, use that instead.
WideCharToMultiByte(CP_ACP, 0, bstr, -1, tszDesc, sizeof(tszDesc), NULL, NULL);
#endif
SysFreeString(bstr);
SpeakStringAll(tszDesc);
}
if (pIAcc)
pIAcc->Release();
}
return;
}
/*************************************************************************
Function: SpeakMute
Purpose: causes the system to shut up.
Inputs:
Returns:
History:
*************************************************************************/
void SpeakMute(int nOption)
{
SendMessage(g_pGlobalData->hwndMSR, WM_MUTE, 0, 0);
}
/*************************************************************************
Function: SpeakObjectInfo
Purpose:
Inputs:
Returns:
History:
*************************************************************************/
void SpeakObjectInfo(LPOBJINFO poiObj, BOOL ReadExtra)
{
BSTR bstrName;
IAccessible* pIAcc;
long* pl;
HRESULT hr;
CAutoArray<TCHAR> aaName( new TCHAR[MAX_TEXT] );
TCHAR * szName = aaName.Get();
CAutoArray<TCHAR> aaSpeak( new TCHAR[MAX_TEXT] );
TCHAR * szSpeak = aaSpeak.Get();
if ( !szName || !szSpeak )
return; // no memory
TCHAR szRole[MAX_TEXT_ROLE];
TCHAR szState[MAX_TEXT_ROLE];
TCHAR szValue[MAX_TEXT_ROLE];
VARIANT varRole;
VARIANT varState;
BOOL bSayValue = TRUE;
BOOL bReadHTMLEdit = FALSE;
DWORD Role = 0;
bstrName = NULL;
// Truncate them
szName[0] = TEXT('\0');
szSpeak[0] = TEXT('\0');
szRole[0] = TEXT('\0');
szState[0] = TEXT('\0');
szValue[0] = TEXT('\0');
// Get the object out of the struct
pl = poiObj->plObj;
pIAcc =(IAccessible*)pl;
GetObjectProperty(pIAcc, poiObj->varChild.lVal, ID_NAME, szName, MAX_NAME);
if (szName[0] == -1) // name going to be garbage
{
LoadString(g_Hinst, IDS_NAMELESS, szSpeak, MAX_TEXT); // For now change "IDS_NAMELESS" in Resources to be just space!
}
else
{
lstrcpyn(szSpeak, szName, MAX_TEXT);
szSpeak[MAX_TEXT-1] = TEXT('\0');
}
szName[0] = TEXT('\0');
VariantInit(&varRole);
hr = pIAcc->get_accRole(poiObj->varChild, &varRole);
if (FAILED(hr))
{
DBPRINTF (TEXT("Failed role!\r\n"));
MessageBeep(MB_OK);
return;
}
if (varRole.vt == VT_I4)
{
Role = varRole.lVal; // save for use below (if ReadExtra)
GetRoleText(varRole.lVal,szRole, ARRAYSIZE(szRole));
// Special casing stuff:
// Outline Items give out their level No. in the tree in the Value
// field, So Don't speak it.
switch(varRole.lVal)
{
case ROLE_SYSTEM_STATICTEXT:
case ROLE_SYSTEM_OUTLINEITEM:
{
bSayValue = FALSE; // don't speak value for text - it may be HTML link
}
break;
// If the text is from combo -box then speak up
case ROLE_SYSTEM_TEXT:
bReadHTMLEdit = TRUE;
bSayValue = TRUE; // Speak text in combo box
break;
case ROLE_SYSTEM_LISTITEM:
{
FilterGUID(szSpeak);
}
break;
case ROLE_SYSTEM_SPINBUTTON:
// Remove the Wizard97 spin box utterances....AK
{
HWND hWnd, hWndP;
WindowFromAccessibleObject(pIAcc, &hWnd);
if ( hWnd != NULL)
{
hWndP = GetParent(hWnd);
LONG_PTR style = GetWindowLongPtr(hWndP, GWL_STYLE);
if ( style & WS_DISABLED)
return;
}
}
break;
default:
break;
}
}
if (g_pGlobalData->fDoingPassword)
LoadString(g_Hinst, IDS_PASSWORD, szRole, 128);
// This will free a BSTR, etc.
VariantClear(&varRole);
if ( (lstrlen(szRole) > 0) &&
(varRole.lVal != ROLE_SYSTEM_CLIENT) )
{
lstrcatn(szSpeak, TEXT(", "),MAX_TEXT);
lstrcatn(szSpeak, szRole, MAX_TEXT);
szRole[0] = TEXT('\0');
}
//
// add value string if there is one
//
hr = pIAcc->get_accValue(poiObj->varChild, &bstrName);
if ( FAILED(hr) )
bstrName = NULL;
if (bstrName)
{
#ifdef UNICODE
lstrcpyn(szName, bstrName, MAX_TEXT);
szName[MAX_TEXT-1] = TEXT('\0');
#else
// If we got back a string, use that instead.
WideCharToMultiByte(CP_ACP, 0, bstrName,-1, szName, MAX_TEXT, NULL, NULL);
#endif
SysFreeString(bstrName);
}
// ROBSI: 10-10-99, Bug?
// We are not properly testing bSayValue here. Therefore, outline items are
// speaking their indentation level -- their accValue. According to comments
// above, this should be skipped. However, below we are explicitly loading
// IDS_TREELEVEL and using this level. Which is correct?
// If not IE, read values for combo box, Edit etc.., For IE, read only for edit boxes
if ( ((!g_pGlobalData->fInternetExplorer && bSayValue )
|| ( g_pGlobalData->fInternetExplorer && bReadHTMLEdit ) )
&& lstrlen(szName) > 0)
{ // i.e. got a value
lstrcatn(szSpeak,TEXT(", "),MAX_TEXT);
lstrcatn(szSpeak,szName,MAX_TEXT);
szName[0] = TEXT('\0');
}
hr = pIAcc->get_accState(poiObj->varChild, &varState);
if (FAILED(hr))
{
MessageBeep(MB_OK);
return;
}
if (varState.vt == VT_I4)
{
GetStateString(varState.lVal, STATE_MASK, szState, ARRAYSIZE(szState) );
}
if (lstrlen(szState) > 0)
{
lstrcatn(szSpeak, TEXT(", "), MAX_TEXT);
lstrcatn(szSpeak, szState, MAX_TEXT);
szState[0] = TEXT('\0');
}
if (ReadExtra && ( // Speak extra information if just got focus on this item
Role == ROLE_SYSTEM_CHECKBUTTON ||
Role == ROLE_SYSTEM_PUSHBUTTON ||
Role == ROLE_SYSTEM_RADIOBUTTON ||
Role == ROLE_SYSTEM_MENUITEM ||
Role == ROLE_SYSTEM_OUTLINEITEM ||
Role == ROLE_SYSTEM_LISTITEM ||
Role == ROLE_SYSTEM_OUTLINEBUTTON)
) {
switch (Role) {
case ROLE_SYSTEM_CHECKBUTTON:
{
// Change due to localization issues:a-anilk
TCHAR szTemp[MAX_TEXT_ROLE];
if (varState.lVal & STATE_SYSTEM_CHECKED)
LoadString(g_Hinst, IDS_TO_UNCHECK, szTemp, MAX_TEXT_ROLE);
else
LoadString(g_Hinst, IDS_TO_CHECK, szTemp, MAX_TEXT_ROLE);
// GetObjectProperty(pIAcc, poiObj->varChild.lVal, ID_DEFAULT, szName, 256);
// wsprintf(szTemp, szTempLate, szName);
lstrcatn(szSpeak, szTemp, MAX_TEXT);
}
break;
case ROLE_SYSTEM_PUSHBUTTON:
{
if ( !(varState.lVal & STATE_SYSTEM_UNAVAILABLE) )
{
LoadString(g_Hinst, IDS_TOPRESS, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
}
}
break;
case ROLE_SYSTEM_RADIOBUTTON:
LoadString(g_Hinst, IDS_TOSELECT, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
break;
// To distinguish between menu items with sub-menu and without one.
// For submenus, It speaks - ', Has a sub-menu': a-anilk
case ROLE_SYSTEM_MENUITEM:
{
long count = 0;
pIAcc->get_accChildCount(&count);
// count = 1 for all menu items with sub menus
if ( count == 1 || varState.lVal & STATE_SYSTEM_HASPOPUP )
{
LoadString(g_Hinst, IDS_SUBMENU, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
}
}
break;
case ROLE_SYSTEM_OUTLINEITEM:
{
// Read out the level in the tree....
// And also the status as Expanded or Collapsed....:AK
TCHAR buffer[64];
if ( varState.lVal & STATE_SYSTEM_COLLAPSED )
{
LoadString(g_Hinst, IDS_TEXPAND, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
}
else if ( varState.lVal & STATE_SYSTEM_EXPANDED )
{
LoadString(g_Hinst, IDS_TCOLLAP, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
}
hr = pIAcc->get_accValue(poiObj->varChild, &bstrName);
LoadString(g_Hinst, IDS_TREELEVEL, szName, 256);
wsprintf(buffer, szName, bstrName);
lstrcatn(szSpeak, buffer, MAX_TEXT);
SysFreeString(bstrName);
}
break;
case ROLE_SYSTEM_LISTITEM:
{
// The list item is selectable, But not selected...:a-anilk
if ( (varState.lVal & STATE_SYSTEM_SELECTABLE ) &&
(!(varState.lVal & STATE_SYSTEM_SELECTED)) )
{
LoadString(g_Hinst, IDS_NOTSEL, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
}
}
break;
case ROLE_SYSTEM_OUTLINEBUTTON:
{
if ( varState.lVal & STATE_SYSTEM_COLLAPSED )
{
LoadString(g_Hinst, IDS_OB_EXPAND, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
}
else if ( varState.lVal & STATE_SYSTEM_EXPANDED )
{
LoadString(g_Hinst, IDS_OB_COLLAPSE, szName, 256);
lstrcatn(szSpeak, szName, MAX_TEXT);
}
}
}
}
SpeakString(szSpeak);
return;
}
/*************************************************************************
Function: SpeakMainItems
Purpose:
Inputs:
Returns:
History:
*************************************************************************/
void SpeakMainItems(int nOption)
{
VARIANT varChild;
IAccessible* pIAcc=NULL;
HRESULT hr;
POINT ptMouse;
SpeakString(TEXT(" "));
//
// Get cursor object & position
//
if (g_pGlobalData->ptCurrentMouse.x < 0)
GetCursorPos(&ptMouse);
else
ptMouse = g_pGlobalData->ptCurrentMouse;
// Important to init variants
VariantInit(&varChild);
hr = AccessibleObjectFromPoint(ptMouse, &pIAcc, &varChild);
// Check to see if we got a valid pointer
if (SUCCEEDED(hr))
{
OBJINFO objCurrent;
objCurrent.hwnd = WindowFromPoint(ptMouse);
objCurrent.plObj = (long*)pIAcc;
objCurrent.varChild = varChild;
SpeakObjectInfo(&objCurrent,FALSE);
pIAcc->Release();
}
return;
}
/*************************************************************************
Function: SpeakKeyboard
Purpose:
Inputs:
Returns:
History:
*************************************************************************/
void SpeakKeyboard(int nOption)
{
TCHAR szName[128];
VARIANT varChild;
IAccessible* pIAcc;
HRESULT hr;
POINT ptMouse;
//
// Get cursor object & position
//
if (g_pGlobalData->ptCurrentMouse.x < 0)
GetCursorPos(&ptMouse);
else
ptMouse = g_pGlobalData->ptCurrentMouse;
// Important to init variants
VariantInit(&varChild);
hr = AccessibleObjectFromPoint(ptMouse, &pIAcc, &varChild);
// Check to see if we got a valid pointer
if (SUCCEEDED(hr))
{
SpeakStringId(IDS_KEYBOARD);
GetObjectProperty(pIAcc, varChild.lVal, ID_SHORTCUT, szName, ARRAYSIZE(szName));
SpeakString(szName);
if (pIAcc)
pIAcc->Release();
}
return;
}
/*************************************************************************
Function: Home
Purpose:
Inputs:
Returns:
History:
ALT_HOME to take secondary cursor to top of this window
*************************************************************************/
void Home(int x)
{
RECT rect;
GetWindowRect(GetForegroundWindow(),&rect);
// Set it to show the title bar 48, max system icon size
SetSecondary(rect.left + 48/*(rect.right - rect.left)/2*/, rect.top + 5,g_pGlobalData->fTrackSecondary);
SpeakMainItems(0);
}
/*************************************************************************
Function: MoveToEnd
Purpose:
Inputs:
Returns:
History:
ALT_END to take secondary cursor to top of this window
*************************************************************************/
void MoveToEnd(int x)
{
RECT rect;
GetWindowRect(GetForegroundWindow(),&rect);
SetSecondary(rect.left+ 48 /*(rect.right - rect.left)/2*/,rect.bottom - 8,g_pGlobalData->fTrackSecondary);
SpeakMainItems(0);
}
#define LEFT_ID 0
#define RIGHT_ID 1
#define TOP_ID 2
#define BOTTOM_ID 3
#define SPOKEN_ID 4
#define SPATIAL_SIZE 2500
long ObjLocation[5][SPATIAL_SIZE];
int ObjIndex;
#define MAX_SPEAK 8192
/*************************************************************************
Function:
Purpose:
Inputs:
Returns:
History:
*************************************************************************/
void SpatialRead(RECT rc)
{
int left_min, top_min, width_min, height_min, index_min; // current minimum object
int i, j; // loop vars
for (i = 0; i < ObjIndex; i++)
{
left_min = 20000;
top_min = 20000;
index_min = -1;
if (g_pGlobalData->fInternetExplorer)
{
for (j = 0; j < ObjIndex; j++)
{
// Skip items that have been spoken before...
if (ObjLocation[SPOKEN_ID][j] != 0)
continue;
// if this is the first non-spoken object, just use it
if( index_min == -1 )
{
index_min = j;
top_min = ObjLocation[TOP_ID][j];
left_min = ObjLocation[LEFT_ID][j];
width_min = ObjLocation[RIGHT_ID][j];
height_min = ObjLocation[BOTTOM_ID][j];
}
else
{
// If same top, different heights, and overlapping widths, then give smaller one priority
if( ObjLocation[TOP_ID][j] == top_min
&& ObjLocation[BOTTOM_ID][j] != height_min
&& ObjLocation[LEFT_ID][j] < left_min + width_min
&& ObjLocation[LEFT_ID][j] + ObjLocation[RIGHT_ID][j] > left_min )
{
if( ObjLocation[BOTTOM_ID][j] < height_min )
{
index_min = j;
top_min = ObjLocation[TOP_ID][j];
left_min = ObjLocation[LEFT_ID][j];
width_min = ObjLocation[RIGHT_ID][j];
height_min = ObjLocation[BOTTOM_ID][j];
}
}
else if ( (ObjLocation[TOP_ID][j] < top_min || // check if more top-left than previous ones - give or take on height (i.e. 5 pixels)
(ObjLocation[TOP_ID][j] == top_min && ObjLocation[LEFT_ID][j] < left_min ) ) ) // more left on this line
{
// OK got a candidate
index_min = j;
top_min = ObjLocation[TOP_ID][j];
left_min = ObjLocation[LEFT_ID][j];
width_min = ObjLocation[RIGHT_ID][j];
height_min = ObjLocation[BOTTOM_ID][j];
}
}
} // for j
} // end if Internet Explorer
else
{
for (j = 0; j < ObjIndex; j++)
{
if (ObjLocation[SPOKEN_ID][j] == 0 && // not been spoken before
// check if enclosed by current rectangle (semi-hierarcical - with recursion!)
(ObjLocation[LEFT_ID][j] >= rc.left && ObjLocation[LEFT_ID][j] <= rc.left + rc.right &&
ObjLocation[TOP_ID][j] >= rc.top && ObjLocation[TOP_ID][j] <= rc.top + rc.bottom
) &&
// also check if more top-left than previous ones - give or take on height (i.e. 10 pixels)
( (ObjLocation[TOP_ID][j] < top_min + 10 && ObjLocation[LEFT_ID][j] < left_min)
// or just higher up
|| (ObjLocation[TOP_ID][j] < top_min)
)
)
{ // OK got a candidate
index_min = j;
top_min = ObjLocation[TOP_ID][j];
left_min = ObjLocation[LEFT_ID][j];
}
} // for j
} // end not Internet Explorer
if (index_min >= 0)
{ // got one!
HWND hwndList;
CAutoArray<TCHAR> aaText( new TCHAR[MAX_SPEAK] );
TCHAR * szText = aaText.Get();
RECT rect;
ObjLocation[SPOKEN_ID][index_min] = 1; // don't do this one again
hwndList = GetDlgItem(g_pGlobalData->hwndMSR, IDC_WINDOWINFO);
// if the data does not fit don't say anything.
if (SendMessage(hwndList, LB_GETTEXTLEN, index_min, NULL) <= MAX_SPEAK)
{
SendMessage(hwndList, LB_GETTEXT, index_min, (LPARAM) szText);
SpeakString(szText);
}
if (g_pGlobalData->fInternetExplorer) // no recursion for IE
continue;
rect.left = ObjLocation[LEFT_ID][index_min];
rect.right = ObjLocation[RIGHT_ID][index_min];
rect.top = ObjLocation[TOP_ID][index_min];
rect.bottom = ObjLocation[BOTTOM_ID][index_min];
SpatialRead(rect);
}
} // for i
}
//--------------------------------------------------------------------------
//
// SpeakWindow()
//
// Fills in a tree view with the descendants of the given top level window.
// If hwnd is 0, use the previously saved hwnd to build the tree.
//
//--------------------------------------------------------------------------
void SpeakWindow(int nOption)
{
IAccessible* pacc;
RECT rect;
TCHAR szName[128];
VARIANT varT;
HWND ForeWnd;
TCHAR buffer[100];
szName[0] = NULL;
buffer[0] = NULL;
g_pGlobalData->nAutoRead = nOption; // set global flag to tell code in AddItem if we're to read edit box contents (don't do it if just got focus as the edit box has probably been spoken already
ForeWnd = GetForegroundWindow(); // Check if we're in HTML Help
GetClassName(ForeWnd,buffer,100);
g_pGlobalData->fHTML_Help = 0;
if ((lstrcmpi(buffer, CLASS_HTMLHELP) == 0) )
{
g_pGlobalData->fInternetExplorer = TRUE;
g_pGlobalData->fHTML_Help = TRUE;
GetWindowRect(ForeWnd, &rect); // get the left hand side of our window to use later
g_pGlobalData->nLeftHandSide = rect.left;
}
else if ( IsTridentWindow(buffer) )
{
g_pGlobalData->fInternetExplorer = TRUE;
g_pGlobalData->fHTML_Help = FALSE;
GetWindowRect(ForeWnd, &rect); // get the left hand side of our window to use later
g_pGlobalData->nLeftHandSide = rect.left;
}
// Inititalise stack for tree information
ObjIndex = 0;
//
// Get the object for the root.
//
pacc = NULL;
AccessibleObjectFromWindow(GetForegroundWindow(), OBJID_WINDOW, IID_IAccessible, (void**)&pacc);
if (nOption == 1)
{ // if it was a keyboard press then speak the window's name
SpeakStringId(IDS_WINDOW);
GetWindowText(GetForegroundWindow(), szName, sizeof(szName)/sizeof(TCHAR)); // raid #113789
SpeakString(szName);
}
if (pacc)
{
HWND hwndList; // first clear the list box used to store the window info
hwndList = GetDlgItem(g_pGlobalData->hwndMSR, IDC_WINDOWINFO);
SendMessage(hwndList, LB_RESETCONTENT, 0, 0);
// AddAccessibleObjects changes this - so need to save and restore it
// so that it is correct when we call SpatialRead...
BOOL fIsInternetExplorer = g_pGlobalData->fInternetExplorer;
InitChildSelf(&varT);
AddAccessibleObjects(pacc, varT); // recursively go off and get the information
pacc->Release();
GetWindowRect(GetForegroundWindow(),&rect);
if (g_pGlobalData->fReviewStyle)
{
g_pGlobalData->fInternetExplorer = fIsInternetExplorer;
SpatialRead(rect);
}
}
}
//--------------------------------------------------------------------------
//
// AddItem()
//
// Parameters: pacc - the IAccessible object to [maybe] add
// varChild - if pacc is parent, the child id
// Return Values: Returns TRUE if caller should continue to navigate the
// UI tree or FALSE if it should stop.
//
//--------------------------------------------------------------------------
BOOL AddItem(IAccessible* pacc, const VARIANT &varChild)
{
TCHAR szName[MAX_NAME] = TEXT(" ");
TCHAR szRole[128] = TEXT(" ");
TCHAR szState[128] = TEXT(" ");
TCHAR szValue[MAX_VALUE] = TEXT(" ");
TCHAR szLink[32];
VARIANT varT;
BSTR bszT;
BOOL DoMore = TRUE;
BOOL GotStaticText = FALSE;
BOOL GotGraphic = FALSE;
BOOL GotText = FALSE;
BOOL GotNameless = FALSE;
BOOL GotInvisible = FALSE;
BOOL GotOffScreen = FALSE;
BOOL GotLink = FALSE;
int lastRole = 0;
static TCHAR szLastName[MAX_NAME] = TEXT(" ");
BOOL fInternetExplorer = g_pGlobalData->fInternetExplorer;
int nAutoRead = g_pGlobalData->nAutoRead;
BOOL fHTMLHelp = g_pGlobalData->fHTML_Help;
int nLeftHandSide = g_pGlobalData->nLeftHandSide;
HWND hwndMSR = g_pGlobalData->hwndMSR;
HRESULT hr;
//
// Get object state first. If we are skipping invisible dudes, we want
// to bail out now.
//
VariantInit(&varT);
hr = pacc->get_accState(varChild, &varT);
if ( FAILED(hr) )
{
DBPRINTF( TEXT("AddItem get_accState returned 0x%x\r\n"), hr );
return FALSE;
}
DWORD dwState = 0;
if (varT.vt == VT_I4)
{
LONG lStateMask = STATE_SYSTEM_UNAVAILABLE | STATE_SYSTEM_INVISIBLE | STATE_SYSTEM_CHECKED;
GetStateString(varT.lVal, lStateMask, szState, ARRAYSIZE(szState) );
dwState = varT.lVal;
GotInvisible = varT.lVal & STATE_SYSTEM_INVISIBLE;
GotOffScreen = varT.lVal & STATE_SYSTEM_OFFSCREEN;
// Bail out if not shown. If it's not IE, ignore both invisible and scrolled-off...
if (!fInternetExplorer && GotInvisible)
return FALSE;
// ...but if it's IE, only ignore 'really' invisible (display:none), and allow
// scrolled-off (which has the offscreen bit set) to be read...
if (fInternetExplorer && GotInvisible && ! GotOffScreen )
return FALSE;
}
VariantClear(&varT);
//
// Get object role.
//
VariantInit(&varT);
hr = pacc->get_accRole(varChild, &varT);
if ( FAILED(hr) )
{
DBPRINTF( TEXT("AddItem get_accRole returned 0x%x\r\n"), hr );
return FALSE;
}
LONG lRole = varT.lVal;
if (varT.vt == VT_I4)
{
switch (varT.lVal)
{
case ROLE_SYSTEM_WINDOW:
case ROLE_SYSTEM_TABLE :
case ROLE_SYSTEM_DOCUMENT:
{
// it's a window - don't read it - read its kids
return TRUE; // but carry on searching down
}
case ROLE_SYSTEM_LIST:
case ROLE_SYSTEM_SLIDER:
case ROLE_SYSTEM_STATUSBAR:
case ROLE_SYSTEM_BUTTONMENU:
case ROLE_SYSTEM_COMBOBOX:
case ROLE_SYSTEM_DROPLIST:
case ROLE_SYSTEM_OUTLINE:
case ROLE_SYSTEM_TOOLBAR:
DoMore = FALSE; // i.e. speak it but no more children
break;
case ROLE_SYSTEM_GROUPING:
if (fInternetExplorer)
{
return TRUE;
}
else
{
DoMore = FALSE; // speak it but no more children
}
break;
// Some of the CLIENT fields in office2000 are not spoken because
// we don't add. We may need to specail case for office :a-anilk
// Micw: Special case for IE and let the rest thru (Whistler raid #28777)
case ROLE_SYSTEM_CLIENT : // for now work with this for IE ...???
if (fInternetExplorer)
{
return TRUE;
}
break;
case ROLE_SYSTEM_PANE :
if ( fInternetExplorer )
{
LONG lLeft = 0, lTop = 0, lHeight = 0, lWidth = 0;
HRESULT hr = pacc->accLocation( &lLeft, &lTop, &lHeight, &lWidth, varChild );
// If they don't know where they are don't speak them
// We do not want to read zero width or height and the elements like in
// Remote Assistance or location of 0,0,0,0 like in oobe
if ( hr != S_OK || lHeight == 0 || lWidth == 0 )
{
return FALSE;
}
}
return TRUE;
case ROLE_SYSTEM_CELL: // New - works for HTML Help!
return TRUE;
case ROLE_SYSTEM_SEPARATOR:
case ROLE_SYSTEM_TITLEBAR:
case ROLE_SYSTEM_GRIP:
case ROLE_SYSTEM_MENUBAR:
case ROLE_SYSTEM_SCROLLBAR:
return FALSE; // don't speak it or it's children
case ROLE_SYSTEM_GRAPHIC: // this works for doing icons!
GotGraphic = TRUE;
break;
case ROLE_SYSTEM_LINK:
GotLink = TRUE;
break;
case ROLE_SYSTEM_TEXT:
GotText = TRUE;
break;
case ROLE_SYSTEM_SPINBUTTON:
// Remove the Wizard97 spin box utterances....
{
HWND hWnd, hWndP;
WindowFromAccessibleObject(pacc, &hWnd);
if ( hWnd != NULL)
{
hWndP = GetParent(hWnd);
LONG_PTR style = GetWindowLongPtr(hWndP, GWL_STYLE);
if ( style & WS_DISABLED)
{
return FALSE;
}
}
DoMore = FALSE; // i.e. speak it but no more children
}
case ROLE_SYSTEM_PAGETAB:
// Hack to not read them if they are disabled...
// Needed for WIZARD97 style :AK
{
HWND hWnd;
WindowFromAccessibleObject(pacc, &hWnd);
if ( hWnd != NULL)
{
LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE);
if ( style & WS_DISABLED)
{
return FALSE;
}
}
}
break;
} // switch
GetRoleText(varT.lVal, szRole, 128);
// ROBSI: 10-10-99, BUG? Why (Role == Static) or (IE)??
if (varT.lVal == ROLE_SYSTEM_STATICTEXT || fInternetExplorer)
{
// don't speak role for this
// speech is better without
szRole[0] = 0;
GotStaticText = TRUE;
}
}
else
{
szRole[0] = 0; // lstrcpy(szRole, TEXT("UNKNOWN"));
}
VariantClear(&varT);
//
// Get object name.
//
bszT = NULL;
hr = pacc->get_accName(varChild, &bszT);
if ( FAILED(hr) )
bszT = NULL;
if (bszT)
{
#ifdef UNICODE
lstrcpyn(szName, bszT, MAX_NAME);
szName[MAX_NAME-1] = TEXT('\0');
#else
WideCharToMultiByte(CP_ACP, 0, bszT, -1, szName, MAX_NAME, NULL, NULL);
#endif
SysFreeString(bszT);
if (szName[0] == -1)
{ // name going to be garbage
LoadString(g_Hinst, IDS_NAMELESS, szName, 256);
GotNameless = TRUE;
}
}
else
{
LoadString(g_Hinst, IDS_NAMELESS, szName, 256);
GotNameless = TRUE;
}
bszT = NULL;
hr = pacc->get_accValue(varChild, &bszT); // get value string if there is one
if ( FAILED(hr) )
bszT = NULL;
szValue[0] = 0;
if (bszT)
{
#ifdef UNICODE
lstrcpyn(szValue, bszT, MAX_VALUE);
szValue[MAX_VALUE-1] = TEXT('\0');
#else
WideCharToMultiByte(CP_ACP, 0, bszT, -1, szValue, MAX_VALUE, NULL, NULL);
#endif
SysFreeString(bszT);
}
// There is no reason for us to speak client client client all time
if ( GotNameless && lRole & ROLE_SYSTEM_CLIENT && szValue[0] == NULL )
return TRUE;
//
// make sure these are terminated for the compare
//
szLastName[MAX_NAME - 1]=TEXT('\0');
szName[MAX_NAME - 1]=TEXT('\0');
//
// don't want to repeat name that OLEACC got from static text
// so if this name is the same as the previous name - don't speak it.
//
if (lstrcmp(szName,szLastName) == 0)
szName[0] = 0;
if (GotStaticText)
{
lstrcpyn(szLastName, szName, MAX_NAME);
szLastName[MAX_NAME-1] = TEXT('\0');
}
else
{
szLastName[0] = 0;
}
CAutoArray<TCHAR> aaItemString( new TCHAR[MAX_TEXT] );
TCHAR * szItemString = aaItemString.Get();
if ( !szItemString )
return FALSE; // no memory
lstrcpyn(szItemString, szName, MAX_TEXT);
szItemString[MAX_TEXT-1] = TEXT('\0');
if (fInternetExplorer)
{
if (GotText && szName[0] == 0) // no real text
{
return FALSE;
}
if (GotNameless && szValue[0] == 0) // nameless with no link
{
return FALSE;
}
if (GotLink/*szValue[0]*/)
{
// got a link
// GotLink = TRUE;
LoadString(g_Hinst, IDS_LINK, szLink, 32);
lstrcatn(szItemString,szLink,MAX_TEXT);
}
}
else
{
// the focused item has already been read so if the item does not have
// focus it should be read in this case edit controls.
if (GotText && ( nAutoRead || !(dwState & STATE_SYSTEM_FOCUSED) ) )
lstrcatn(szItemString,szValue,MAX_TEXT);
}
if (!GotText && !GotLink && !GotGraphic)
{
if (lstrlen(szName) && lstrlen(szRole))
lstrcatn(szItemString,TEXT(", "),MAX_TEXT);
if (lstrlen(szRole))
{
lstrcatn(szItemString,szRole,MAX_TEXT);
if (lstrlen(szValue) || lstrlen(szState))
lstrcatn(szItemString, TEXT(", "),MAX_TEXT);
}
if (lstrlen(szValue))
{
lstrcatn(szItemString,szValue,MAX_TEXT);
if (lstrlen(szState))
lstrcatn(szItemString,TEXT(", "),MAX_TEXT);
}
if (lstrlen(szState))
lstrcatn(szItemString,szState,MAX_TEXT);
// Too much speech of period/comma. Just a space is fine...
lstrcatn(szItemString, TEXT(" "),MAX_TEXT);
}
if (g_pGlobalData->fReviewStyle)
{
HWND hwndList;
if (ObjIndex >= SPATIAL_SIZE) // only store so many
{
return DoMore;
}
pacc->accLocation(&ObjLocation[LEFT_ID][ObjIndex],
&ObjLocation[TOP_ID][ObjIndex],
&ObjLocation[RIGHT_ID][ObjIndex],
&ObjLocation[BOTTOM_ID][ObjIndex],
varChild);
// Dreadfull Hack/heuristic!
// bin information as it's the left hand side of the HTML help window
if (fHTMLHelp && (ObjLocation[LEFT_ID][ObjIndex] < nLeftHandSide + 220))
{
return DoMore;
}
hwndList = GetDlgItem(hwndMSR, IDC_WINDOWINFO);
SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM) szItemString);
ObjLocation[SPOKEN_ID][ObjIndex] = 0;
ObjIndex++;
}
else
SpeakString(szItemString);
return DoMore;
}
// --------------------------------------------------------------------------
//
// AddAccessibleObjects()
//
// This is a recursive function. It adds an item for the parent, then
// adds items for its children if it has any.
//
// Parameters:
// IAccessible* pacc Pointer to an IAccessible interface for the
// object being added. Start with the 'root' object.
// VARIANT varChild Variant that contains the ID of the child object
// to retrieve.
//
// The first call, pIAcc points to the top level window object,
// varChild is a variant that is VT_I4, CHILDID_SELF
//
// ISSUE: The calling code isn't prepared to deal with errors so I'm making
// the return void. A re-engineered version should do better error handling.
//
void AddAccessibleObjects(IAccessible* pIAcc, const VARIANT &varChild)
{
if (varChild.vt != VT_I4)
{
DBPRINTF(TEXT("BUG??: Got child ID other than VT_I4 (%d)\r\n"), varChild.vt);
return;
}
// Find the window class so we can find embedded trdent windows
HWND hwndCurrentObj;
TCHAR szClassName[64];
if ( WindowFromAccessibleObject( pIAcc, &hwndCurrentObj ) == S_OK )
{
if ( GetClassName( hwndCurrentObj, szClassName, ARRAYSIZE(szClassName) ) )
{
// is it Internet Explorer in any of its many forms?
g_pGlobalData->fInternetExplorer = IsTridentWindow(szClassName);
}
}
// Add the object itself and, if AddItem determines it wants children, do those
if (AddItem(pIAcc, varChild))
{
// Traverse the children to see if any of them should be added
if (varChild.lVal != CHILDID_SELF)
return; // only do this for container objects
// Loop through pIAcc's children.
long cChildren = 0;
pIAcc->get_accChildCount(&cChildren);
if (!cChildren)
return; // no children
// Allocate memory for the array of child variants
CAutoArray<VARIANT> aaChildren( new VARIANT [cChildren] );
VARIANT * pavarChildren = aaChildren.Get();
if( ! pavarChildren )
{
DBPRINTF(TEXT("Error: E_OUTOFMEMORY allocating pavarChildren\r\n"));
return;
}
long cObtained = 0;
HRESULT hr = AccessibleChildren( pIAcc, 0L, cChildren, pavarChildren, & cObtained );
if( hr != S_OK )
{
DBPRINTF(TEXT("Error: AccessibleChildren returns 0x%x\r\n"), hr);
return;
}
else if( cObtained != cChildren)
{
DBPRINTF(TEXT("Error: get_accChildCount returned %d but AccessibleChildren returned %d\r\n"), cChildren, cObtained);
return;
}
else
{
// Loop through VARIANTs in ARRAY. Object_Normalize returns a proper
// pAccChild and varAccChild pair regardless of whether the array
// element is VT_DISPATCH or VT_I4.
for( int i = 0 ; i < cChildren ; i++ )
{
IAccessible * pAccChild = NULL;
VARIANT varAccChild;
// Object_Normalize consumes the variant, so no VariantClear() needed.
if( Object_Normalize( pIAcc, & pavarChildren[ i ], & pAccChild, & varAccChild ) )
{
AddAccessibleObjects(pAccChild, varAccChild);
pAccChild->Release();
}
}
}
}
}
// --------------------------------------------------------------------------
// Helper method to filter out bogus focus events...
// Returns FALSE if the focus event is bogus, Otherwise returns TRUE
// a-anilk: 05-28-99
// --------------------------------------------------------------------------
BOOL IsFocussedItem( HWND hWnd, IAccessible * pAcc, VARIANT varChild )
{
TCHAR buffer[100];
GetClassName(hWnd,buffer,100);
// Is it toolbar, We cannot determine who had focus!!!
if ((lstrcmpi(buffer, CLASS_TOOLBAR) == 0) ||
(lstrcmpi(buffer, CLASS_IE_MAINWND) == 0))
return TRUE;
VARIANT varState;
HRESULT hr;
VariantInit(&varState);
hr = pAcc->get_accState(varChild, &varState);
if ( hr == S_OK)
{
if ( ! (varState.lVal & STATE_SYSTEM_FOCUSED) )
return FALSE;
}
else if (FAILED(hr)) // ROBSI: 10-11-99. If OLEACC returns an error, assume no focus.
{
return FALSE;
}
return TRUE;
}
#define TAB_KEY 0x09
#define CURLY_KEY 0x7B
// Helper method Filters GUID's that can appear in names: AK
void FilterGUID(TCHAR* szSpeak)
{
// the GUID's have a Tab followed by a {0087....
// If you find this pattern. Then donot speak that:AK
TCHAR *szSpeakBegin = szSpeak;
// make sure the we don't go over MAX_TEXT.
while(*szSpeak != NULL && (szSpeak-szSpeakBegin < MAX_TEXT-1))
{
if ( (*szSpeak == TAB_KEY) &&
(*(++szSpeak) == CURLY_KEY) )
{
*(--szSpeak) = NULL;
return;
}
szSpeak++;
}
}
///////////////////////////////////////////////////////////////////////////
// Helper functions
// Convert an IDispatch to an IAccessible/varChild pair (Releases the IDispatch)
BOOL Object_IDispatchToIAccessible( IDispatch * pdisp,
IAccessible ** ppAccOut,
VARIANT * pvarChildOut)
{
IAccessible * pAccTemp = NULL;
HRESULT hr = pdisp->QueryInterface( IID_IAccessible, (void**) & pAccTemp );
pdisp->Release();
if( hr != S_OK || ! pAccTemp )
{
return FALSE;
}
*ppAccOut = pAccTemp;
if( pvarChildOut )
{
InitChildSelf(pvarChildOut);
}
return TRUE;
}
// Given an IAccessible and a variant (may be I4 or DISP), returns a 'canonical'
// IAccessible/varChild, using getChild, etc.
// The variant is consumed.
BOOL Object_Normalize( IAccessible * pAcc,
VARIANT * pvarChild,
IAccessible ** ppAccOut,
VARIANT * pvarChildOut)
{
BOOL fRv = FALSE;
if( pvarChild->vt == VT_DISPATCH )
{
fRv = Object_IDispatchToIAccessible( pvarChild->pdispVal, ppAccOut, pvarChildOut );
}
else if( pvarChild->vt == VT_I4 )
{
if( pvarChild->lVal == CHILDID_SELF )
{
// No normalization necessary...
pAcc->AddRef();
*ppAccOut = pAcc;
pvarChildOut->vt = VT_I4;
pvarChildOut->lVal = pvarChild->lVal;
fRv = TRUE;
} else
{
// Might still be a full object - try get_accChild...
IDispatch * pdisp = NULL;
HRESULT hr = pAcc->get_accChild( *pvarChild, & pdisp );
if( hr == S_OK && pdisp )
{
// It's a full object...
fRv = Object_IDispatchToIAccessible( pdisp, ppAccOut, pvarChildOut );
}
else
{
// Just a regular leaf node...
pAcc->AddRef();
*ppAccOut = pAcc;
pvarChildOut->vt = VT_I4;
pvarChildOut->lVal = pvarChild->lVal;
fRv = TRUE;
}
}
}
else
{
DBPRINTF( TEXT("Object_Normalize unexpected error") );
*ppAccOut = NULL; // unexpected error...
VariantClear( pvarChild );
fRv = FALSE;
}
return fRv;
}
/*************************************************************************
THE INFORMATION AND CODE PROVIDED HEREUNDER (COLLECTIVELY REFERRED TO
AS "SOFTWARE") IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN
NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR
ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL,
CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF
MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR
LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE
FOREGOING LIMITATION MAY NOT APPLY.
*************************************************************************/