402 lines
12 KiB
Plaintext
402 lines
12 KiB
Plaintext
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.
|