unit VA508ScreenReaderDLLLinker; interface { TODO -oJeremy Merrill -c508 :Add ability to handle multiple instances / multiple appliations to JAWS at the same time - will need to use Application.MainForm handle approach, probably need to use different registry keys with handle in registry key name. JAWS has a GetAppMainWindow command to get the handle. Will need a cleanup command in delphi to make sure we don't leave junk in the registry - probably search running apps, and if the main form's handle isn't in the registry, delete entries. } uses Windows, SysUtils, Forms, Classes, VA508AccessibilityConst; {$I 'VA508ScreenReaderDLLStandard.inc'} // Returns true if a link to a screen reader was successful. The first link that // is established causes searching to stop. // Searches for .SR files in this order: // 1) Current machine's Program Files directory // 2) \Program Files directory on drive where app resides, // if it's different than the current machine's program files directory // 3) The directory the application was run from. function ScreenReaderDLLsExist: boolean; function IsScreenReaderSupported(Unload: Boolean): boolean; function InitializeScreenReaderLink: boolean; procedure CloseScreenReaderLink; type TVA508RegisterCustomBehaviorProc = procedure(BehaviorType: integer; Before, After: PChar); stdcall; TVA508SpeakTextProc = procedure(Text: PChar); stdcall; TVA508IsRunningFunc = function(HighVersion, LowVersion: Word): BOOL; stdcall; TVA508ConfigChangePending = function: boolean; stdcall; TVA508ComponentDataProc = procedure (WindowHandle: HWND; DataStatus: LongInt = DATA_NONE; Caption: PChar = nil; Value: PChar = nil; Data: PChar = nil; ControlType: PChar = nil; State: PChar = nil; Instructions: PChar = nil; ItemInstructions: PChar = nil); stdcall; var SRSpeakText: TVA508SpeakTextProc = nil; SRIsRunning: TVA508IsRunningFunc = nil; SRRegisterCustomBehavior: TVA508RegisterCustomBehaviorProc = nil; SRComponentData: TVA508ComponentDataProc = nil; SRConfigChangePending: TVA508ConfigChangePending = nil; ValidSRFiles: TStringList = nil; ExecuteFind: boolean = TRUE; DoInitialize: boolean = TRUE; InitializeResult: boolean = FALSE; implementation uses VAUtils, VA508AccessibilityRouter, VA508AccessibilityManager; const ScreenReaderFileExtension = '.SR'; ScreenReaderCommonFilesDir = 'VistA\Common Files\'; ScreenReaderSearchSpec = '*' + ScreenReaderFileExtension; {$WARNINGS OFF} // Ignore platform specific code warning BadFile = faHidden or faSysFile or faDirectory or faSymLink; {$WARNINGS ON} {$REGION 'Initialize Proc Definition'} type TVA508InitializeProc = function(CallBackProc: TComponentDataRequestProc): BOOL; stdcall; const TVA508InitializeProcName = 'Initialize'; var SRInitialize: TVA508InitializeProc = nil; function Initialize(ComponentCallBackProc: TComponentDataRequestProc): BOOL; stdcall; {$HINTS OFF} // Ignore unused variable hint var CompileVerification: TVA508InitializeProc; begin CompileVerification := Initialize; Result := FALSE; end; {$HINTS ON} {$ENDREGION} {$REGION 'ShutDown Proc Definition'} type TVA508ShutDownProc = procedure; stdcall; const TVA508ShutDownProcName = 'ShutDown'; var SRShutDown: TVA508ShutDownProc = nil; procedure ShutDown; stdcall; {$HINTS OFF} // Ignore unused variable hint var CompileVerification: TVA508ShutDownProc; begin CompileVerification := ShutDown; end; {$HINTS ON} {$ENDREGION} {$REGION 'RegisterCustomBehavior Proc Definition'} const TVA508RegisterCustomBehaviorProcName = 'RegisterCustomBehavior'; procedure RegisterCustomBehavior(BehaviorType: integer; Before, After: PChar); stdcall; {$HINTS OFF} // Ignore unused variable hint var CompileVerification: TVA508RegisterCustomBehaviorProc; begin CompileVerification := RegisterCustomBehavior; end; {$HINTS ON} {$ENDREGION} {$REGION 'ComponentData Proc Definition'} const TVA508ComponentDataProcName = 'ComponentData'; procedure ComponentData(WindowHandle: HWND; DataStatus: LongInt = DATA_NONE; Caption: PChar = nil; Value: PChar = nil; Data: PChar = nil; ControlType: PChar = nil; State: PChar = nil; Instructions: PChar = nil; ItemInstructions: PChar = nil); stdcall; {$HINTS OFF} // Ignore unused variable hint var CompileVerification: TVA508ComponentDataProc; begin CompileVerification := ComponentData; end; {$HINTS ON} {$ENDREGION} {$REGION 'SpeakText Proc Definition'} const TVA508SpeakTextProcName = 'SpeakText'; procedure SpeakText(Text: PChar); stdcall; {$HINTS OFF} // Ignore unused variable hint var CompileVerification: TVA508SpeakTextProc; begin CompileVerification := SpeakText; end; {$HINTS ON} {$ENDREGION} {$REGION 'IsRunning Proc Definition'} const TVA508IsRunningFuncName = 'IsRunning'; function IsRunning(HighVersion, LowVersion: Word): BOOL; stdcall; {$HINTS OFF} // Ignore unused variable hint var CompileVerification: TVA508IsRunningFunc; begin CompileVerification := IsRunning; Result := FALSE; // avoid compiler warning... end; {$HINTS ON} {$ENDREGION} {$REGION 'ConfigChangePending Proc Definition'} const TVA508ConfigChangePendingName = 'ConfigChangePending'; function ConfigChangePending: boolean; stdcall; {$HINTS OFF} // Ignore unused variable hint var CompileVerification: TVA508ConfigChangePending; begin CompileVerification := ConfigChangePending; Result := FALSE; // avoid compiler warning... end; {$HINTS ON} {$ENDREGION} var DLLHandle: THandle = 0; procedure ClearProcPointers; begin SRInitialize := nil; SRShutDown := nil; SRRegisterCustomBehavior := nil; SRSpeakText := nil; SRIsRunning := nil; SRComponentData := nil; SRConfigChangePending := nil; DoInitialize := FALSE; InitializeResult := FALSE; end; function InitializeScreenReaderLink: boolean; begin if DoInitialize then begin InitializeResult := SRInitialize(ComponentDataRequested); DoInitialize := FALSE; if not InitializeResult then CloseScreenReaderLink; end; Result := InitializeResult; end; procedure CloseScreenReaderLink; begin if DLLHandle > HINSTANCE_ERROR then begin SRShutDown; FreeLibrary(DLLHandle); DLLHandle := 0; ClearProcPointers; end; end; procedure LoadScreenReader(index: integer); var FileName: string; begin FileName := ValidSRFiles[index]; DLLHandle := LoadLibrary(PChar(FileName)); if DLLHandle > HINSTANCE_ERROR then begin SRInitialize := GetProcAddress(DLLHandle, TVA508InitializeProcName); SRShutDown := GetProcAddress(DLLHandle, TVA508ShutDownProcName); SRRegisterCustomBehavior := GetProcAddress(DLLHandle, TVA508RegisterCustomBehaviorProcName); SRSpeakText := GetProcAddress(DLLHandle, TVA508SpeakTextProcName); SRIsRunning := GetProcAddress(DLLHandle, TVA508IsRunningFuncName); SRComponentData := GetProcAddress(DLLHandle, TVA508ComponentDataProcName); SRConfigChangePending := GetProcAddress(DLLHandle, TVA508ConfigChangePendingName); DoInitialize := TRUE; end; end; function CheckRunning(Unload: boolean; HighVersion, LowVersion: integer): boolean; begin // Calling IsRunning this way, instead of setting ok to it's result, // prevents ok from begin converted to a LongBool at compile time if assigned(SRIsRunning) and SRIsRunning(HighVersion, LowVersion) then Result := TRUE else Result := FALSE; if Unload and (DLLHandle > HINSTANCE_ERROR)then begin FreeLibrary(DLLHandle); DLLHandle := 0; end; end; procedure FindScreenReaders; var ok: boolean; procedure CheckProcs; begin SRInitialize := GetProcAddress(DLLHandle, TVA508InitializeProcName); ok := assigned(SRInitialize); if ok then begin SRShutDown := GetProcAddress(DLLHandle, TVA508ShutDownProcName); ok := assigned(SRShutDown); if ok then begin SRRegisterCustomBehavior := GetProcAddress(DLLHandle, TVA508RegisterCustomBehaviorProcName); ok := assigned(SRRegisterCustomBehavior); if ok then begin SRSpeakText := GetProcAddress(DLLHandle, TVA508SpeakTextProcName); ok := assigned(SRSpeakText); if ok then begin SRIsRunning := GetProcAddress(DLLHandle, TVA508IsRunningFuncName); ok := assigned(SRIsRunning); if ok then begin SRComponentData := GetProcAddress(DLLHandle, TVA508ComponentDataProcName); ok := assigned(SRComponentData); if ok then begin SRConfigChangePending := GetProcAddress(DLLHandle, TVA508ConfigChangePendingName); ok := assigned(SRConfigChangePending); end; end; end; end; end; end; ClearProcPointers; end; procedure CheckFile(FileName: string); var idx: integer; begin DLLHandle := 0; ok := FileExists(FileName); if ok then begin ok := FALSE; idx := ValidSRFiles.IndexOf(FileName); if idx < 0 then begin DLLHandle := LoadLibrary(PChar(FileName)); if DLLHandle > HINSTANCE_ERROR then begin try CheckProcs; if ok then ValidSRFiles.Add(FileName) finally FreeLibrary(DLLHandle); DLLHandle := 0; end; end; end; end end; procedure ScanScreenReaders(dir: string; addCommonFilesPath: boolean = true); var SR: TSearchRec; Done: integer; RootDir: string; begin if dir = '' then exit; RootDir := AppendBackSlash(dir); if addCommonFilesPath then RootDir := RootDir + ScreenReaderCommonFilesDir; Done := FindFirst(RootDir + ScreenReaderSearchSpec, faAnyFile, SR); try while Done = 0 do begin if((SR.Attr and BadFile) = 0) and (CompareText(ExtractFileExt(SR.Name), ScreenReaderFileExtension) = 0) then begin CheckFile(RootDir + SR.Name); end; Done := FindNext(SR); end; finally FindClose(SR); end; end; begin if ExecuteFind then begin if not assigned(ValidSRFiles) then ValidSRFiles := TStringList.Create; ScanScreenReaders(GetProgramFilesPath); if not ok then ScanScreenReaders(GetAlternateProgramFilesPath); if not ok then ScanScreenReaders(ExtractFilePath(Application.ExeName), FALSE); ExecuteFind := FALSE; end; end; function ScreenReaderDLLsExist: boolean; begin FindScreenReaders; Result := (ValidSRFiles.Count > 0); end; function IsScreenReaderSupported(Unload: Boolean): boolean; var i: integer; HighVersion, LowVersion: integer; begin Result := FALSE; FindScreenReaders; VersionStringSplit(VA508AccessibilityManagerVersion, HighVersion, LowVersion); for I := 0 to ValidSRFiles.Count - 1 do begin LoadScreenReader(i); Result := CheckRunning(Unload, HighVersion, LowVersion); if Result then exit; if not Unload then begin FreeLibrary(DLLHandle); DLLHandle := 0; end; end; end; initialization finalization CloseScreenReaderLink; if assigned(ValidSRFiles) then FreeAndNil(ValidSRFiles); end.