ZoomSoftware.jss

JAWS Script Language -- Complete ZoomSoftware.jss file

; Basic Scripts For the Zoom Conferencing Client.
; Prepared and updated by Brian Hartgen, Hartgen Consultancy, 27 January 2022. 
; Tested against Zoom Meetings Version 5, 9, 3, 3169
; JAWS version 2022.2201.54 
; New comments preceeded by a double at sign.

Include "hjconst.jsh" ; default constants, also includes HjIni.jsh For default section and key names
Include "hjglobal.jsh"
Include "MSAAConst.jsh"
Include "common.jsm"
Include "ZoomSoftware.jsm"

Import "Say.jsd"

Const
    ZoomNotificationWindowClass = "zoom_acc_notify_wnd",
    ZoomSchedulerComboBoxClass = "ZPWndSchedulerClass",
    ZoomSettingsComboBoxClass = "ZPSettingWndClass",
    ZoomChatClass = "ZPPTMainFrmWndClassEx"

Globals
    Int ZoomFirstTime, ; because we're Not storing settings any longer.
    Collection customBrailleComboBoxData,
    Int zUseTypeKey,
    String zRepeat,
    Int ZAlert,
    String zNotificationOutput,
    Int zChat,
    String SSpeakerName,
Int HasSpokenCategory,
Int Participants,
String ObjectName,
String PrevListOutput,
Int Line,
String ListOutput,
String zSText,
Int focusInChatEdit

; @@ For Notification Manager exclusions.

; For forcing refresh when MSAA alerts are received:
; We don't receive any events For some buttons where the name changes when the buttons is pressed To toggles something, one example is the Mute button in meetings.
; We will Use the MSAAAlertEvent as a trigger To attempt To detect a name change For these buttons by performing an MSAARefresh.
; But, performing an MSAARefresh now may cause both a focusChangedEvent and a NameChangedEvent, and this together with the MSAA alert announcement results in too many speech announcements.
; In cases where an alert is received immediately after a key press On a button, we do Not allow the MSAAAlertEvent code To continue processing but instead start watching For other events--focus change and name change.
; In the NameChangedEvent, If this event occurs within the time limit of the watch, and If the name change matches the button name,
; we tell the watcher that the NameChangedEvent code did the speaking;
; otherwise, the NameChangedEvent code will process as usual but will Not tell the event watcher that the desired speech output occurred.
; In FocusChangedEvent, If the watch For extra events is active and the focus did Not change,
; we assume that this focus change should be ignored and we do Not Handle speech output here.
; Finally, If the watch time expires and no events were detected To Handle the speech For the MSAAAlertEvent, we call the MSAAAlertEvent helper Function To process the alert which we would have ignored If some other event handled speaking it.
Const
    MSAAAlertSinceKeyPressMaxTime = 200 ;maximum number of ticks For associating an MSAA alert with being caused by a key press
Globals
    Int ConcludeWaitingForExtraEventsFromRefreshID, ;the schedule id of the Function ConcludeWaitingForExtraEventsFromRefreshID,
                ;which was set because we believe that an MSAA alert indicates that a button needs an MSAA refresh.
                ;The MSAARefresh will trigger other events, which we want To watch For and Handle For this special case.
                ; See functions: IsWaitingOnExtraEventsFromRefresh, MarkSpeechAsHandledInExtraEventFromRefresh, ConcludeWaitingForExtraEventsFromRefresh, MSAAAlertEvent, NameChangedEvent, FocusChangedEvent.
    Int CountOfExtraEventsSinceRefresh, ;Used To determine If events likely caused by MSAARefresh are fired.
    Collection ZoomDelayedMSAAAlertEventData ; Holds the params passed To MSAAAlertEvent, so that the event helper can be fired If needed when the watch For events expires.

Void Function AutoStartEvent ()
focusInChatEdit = False
Var String JAWSLanguage = stringLower (GetJFWLang ())
; preserve {} functionality For English users who potentially Use Dragon.
zUseTypeKey = (JAWSLanguage != "enu")
ShowSoundMixerDiscoveryDialog()
EndFunction

Void Function LoadNonJCFOptions ()
; Load preferences.
; @@ Older code commented out in the event there is a strategy change later down the road. But For now, we're Not retaining preferences.

; @@ When we restart JAWS, we must enable all alerts. Otherwise, keep the user's preference even when switching out of Zoom and back.
If ZoomFirstTime == 0 Then 
    Let ZoomFirstTime = 1
    ZAlert = 1 ; @@ We only want To set it To a value of 1 If JAWS is restarted.

    ; For now, we don't want this as it stores a preference.
    ; ZAlert = getNonJCFOption ("ZAlert")
    Let zChat = 0
    ; For now, we don't want this as it stores a preference.
    ; zChat = getNonJCFOption ("Chat")
EndIf ; End of Zoom first time run.
LoadNonJCFOptions () ; defaults
EndFunction

; To accompany Script To remind users as To what they've got.

Function SettingsReminder ()
If ZAlert== 0 Then 
    SayUsingVoice (VCTX_Message, MSGAlertsDisabled, ot_user_requested_information)
else
    SayUsingVoice (VCTX_Message, MSGAlertsEnabled, ot_user_requested_information)
EndIf
If zChat == 1 Then 
    SayUsingVoice (VCTX_Message, MSGExtendedChat, ot_user_requested_information)
else
    SayUsingVoice (VCTX_Message, MSGExtendedAlerts, ot_user_requested_information)
EndIf
EndFunction

Int Function IsWaitingOnExtraEventsFromRefresh()
Return ConcludeWaitingForExtraEventsFromRefreshID != 0
EndFunction

Void Function MarkSpeechAsHandledInExtraEventFromRefresh()
CountOfExtraEventsSinceRefresh = CountOfExtraEventsSinceRefresh + 1
EndFunction

Void Function CancelWaitingForExtraEventsFromRefresh()
If ConcludeWaitingForExtraEventsFromRefreshID
    UnScheduleFunction (ConcludeWaitingForExtraEventsFromRefreshID)
    ConcludeWaitingForExtraEventsFromRefreshID = 0
    CollectionRemoveAll(ZoomDelayedMSAAAlertEventData)
    CountOfExtraEventsSinceRefresh = 0
EndIf
EndFunction

Void Function ConcludeWaitingForExtraEventsFromRefresh()
ConcludeWaitingForExtraEventsFromRefreshID = 0
If !CountOfExtraEventsSinceRefresh
    ;We did Not detect any events where speech output was handled, so pass On the data recieved by MSAAAlertEvent To its handler:
    MSAAAlertEventHelper(ZoomDelayedMSAAAlertEventData.hwnd, ZoomDelayedMSAAAlertEventData.nTime, ZoomDelayedMSAAAlertEventData.SText, ZoomDelayedMSAAAlertEventData.nAlertLevel, ZoomDelayedMSAAAlertEventData.appName)
    CollectionRemoveAll(ZoomDelayedMSAAAlertEventData)
    Return
EndIf
CountOfExtraEventsSinceRefresh = 0
; We assume whatever desired announcement was handled in one of the extra events.
EndFunction

Void Function MSAAAlertEvent(Handle hwnd, Int nTime, String SText, Int nAlertLevel, String appName)
If getWindowClass (hwnd) != ZoomNotificationWindowClass Then; Not a Zoom Alert.
    Return MSAAAlertEvent (hwnd, nTime, SText, nAlertLevel, appName)
EndIf ; End of default Alert.
; See comment For forcing refresh when MSAA alerts are received:
If GetObjectRole() == role_system_PushButton
&& GetTickCount()-GetLastKeyPressTime() <= MSAAAlertSinceKeyPressMaxTime
    If !ZoomDelayedMSAAAlertEventData ZoomDelayedMSAAAlertEventData = New Collection EndIf
    ZoomDelayedMSAAAlertEventData.hwnd = hwnd
    ZoomDelayedMSAAAlertEventData.nTime = nTime
    ZoomDelayedMSAAAlertEventData.SText = SText
    ZoomDelayedMSAAAlertEventData.nAlertLevel = nAlertLevel
    ZoomDelayedMSAAAlertEventData.appName = appName
        ConcludeWaitingForExtraEventsFromRefreshID = ScheduleFunction("ConcludeWaitingForExtraEventsFromRefresh", 10)
    MSAARefresh()
    Return
EndIf
MSAAAlertEventHelper(hwnd, nTime, SText, nAlertLevel, appName)
EndFunction

Void Function MSAAAlertEventHelper(Handle hwnd, Int nTime, String SText, Int nAlertLevel, String appName)
Var
    String sMsgLong,
    String sMsgShort;
Let sMsgLong = SText
; Process the alerts and chats.
Let ZRepeat = SText ; Store the Alert For processing and output.
If StringContains (SText, MSGBandwidth) Then ; For when Zoom erroneously reports bandwidth is low
    Return
EndIf
; Are we recording or is recording paused? 
; Because synthetic speech announcing the recording status is now automatically transmitted To audience participants, we only need To say this If all alerts are enabled; If the user has specifically requested it, otherwise it is overbaring.
If ZAlert == 1 Then ; Alerts are enabled
    If StringContains (SText, MSGRecord) 
        || StringContains (SText, MSGPause) Then
 ; Change speech Messages. There is Braille output with this type.
 ; @@ For Notifications Manager.
 Var Collection NotificationRuleActions = ProcessNotification(sText, appName)
SayNotification (notificationRuleActions, ot_help)
If !notificationRuleActions.ExcludeFromNotificationHistory Then
    storeSpokenNotificationForRepeat (SText, appName) ; For New insert+Space&N keystroke.
EndIf
; @@ End of Notifications Manager
Return
EndIf ; Whether the user wants To hear this.
EndIf ; End of anything related To recording.

; Talking requires special handling.
If StringContains (SText, MSGTalking) Then ; Is the placeholder vacant or is someone actually talking.
    Let SSpeakerName = SText
    ProcessNameOfSpeaker () ; separate Function To ensure this is less cluttered.
Return
EndIf ; end of someone talking.
; User preference For incoming Messages.
If ZChat == 1  Then ; Chat Messages only are required.
    SpeakMessagesOnly ()
Return
EndIf ; end of user preference For chat Messages.

If StringIsBlank (zNotificationOutput) Then ; we have nothing.
    Let zNotificationOutput = ZRepeat
else ; add it To what we've got.
    Let zNotificationOutput = zNotificationOutput+"|"+ZRepeat
EndIf ; end of notification capture.
; If we've got this far and the user wants To hear Alerts Then send them To output.
If ZAlert == 1 Then 
        ; Change speech Messages. There is Braille output with this type.
        ; @@ For Notifications Manager
        notificationRuleActions = ProcessNotification(zRepeat, appName)
SayNotification (notificationRuleActions, ot_help)
; We don't want To save chat Messages, or alerts excluded from history by the user
If !NotificationRuleActions.ExcludeFromNotificationHistory && (!StringStartsWith(zRepeat, scChatMessageIndicator) && !StringStartsWith(zRepeat, scPersonalChatMessageIndicator)) Then
    StoreSpokenNotificationForRepeat(zRepeat, appName)
EndIf
; @@ End of Notifications Manager
EndIf ; end of Alert notifications If the user has selected them.

EndFunction

; special handling For speaker names.

Function ProcessNameOfSpeaker ()
Let SSpeakerName = StringTrimTrailingBlanks (SSpeakerName); avoid trailing space.
; StringsEqual is no longer necessary.
If SSpeakerName == MSGTalking Then ; Zoom has Not detected a speaker.
    SayUsingVoice (VCTX_Message, MSGSpeakerNotDetected, ot_user_requested_information)
else ; we have a speaker but the word Talking is unnecessary.
    Let SSpeakerName = StringReplaceSubstrings (SSpeakerName , MSGTalking, " ")
    Let SSpeakerName = StringTrimLeadingBlanks (SSpeakerName) ; clean output.
; Change speech Messages. There is Braille output with this type.
SayFormattedMessage (ot_help, SSpeakerName, SSpeakerName) 
EndIf ; end of whether we have legitimate name.

EndFunction

Void Function SpeakMessagesOnly ()
If StringStartsWith(zRepeat, scChatMessageIndicator) Then ; we want it If the user has selected chat only.
    If StringIsBlank (zNotificationOutput) Then ; we've got nothing so far.
        Let zNotificationOutput = zRepeat
    else ; add it To what we've got.
        Let zNotificationOutput = zNotificationOutput+"|"+zRepeat
    EndIf ; end of whether we have a chat message.
; Change speech Messages. There is Braille output with this type.
SayFormattedMessage (ot_help, ZRepeat, ZRepeat) 
EndIf ; whether the Alert contains a chat.
EndFunction

; older code For the time being.

Script SayAlert ()
If StringIsBlank (zRepeat) Then 
    sayMessage (OT_ERROR, MSGNoAlertsAvailable)
else
    SayMessage (OT_USER_REQUESTED_INFORMATION, zRepeat)
EndIf
EndScript

Script ToggleAlerts ()
; @@ New For January 2022.
If ZAlert== 0 Then 
    Let ZAlert= 1
    SayUsingVoice (VCTX_Message, MSGAlertsEnabled, OT_STATUS)
else
    Let ZAlert= 0
    SayUsingVoice (VCTX_Message, MSGAlertsDisabled, OT_STATUS)
EndIf
; @@ We're Not retaining this For now.
; WriteSettingInteger (Section_NonJCFOptions, "ZAlert", ZAlert, FT_CURRENT_JCF)
EndScript

Script ToggleChatMessages ()
If zChat == 0 Then 
    Let zChat = 1
    SayUsingVoice (VCTX_Message, MSGExtendedChat, ot_user_requested_information)
else
    Let zChat = 0
    SayUsingVoice (VCTX_Message, MSGExtendedAlerts, ot_user_requested_information)
EndIf
; @@ We're Not retaining this For now.
; WriteSettingInteger (Section_NonJCFOptions, "Chat", zChat, FT_CURRENT_JCF)
EndScript

 

Void Function SayAlertInfo ()
; A regular alert is produced here so If the user has disabled these we should ignore the request.
If ZAlert== 0 Then 
    Say (zRepeat, OT_USER_REQUESTED_INFORMATION)
EndIf
EndFunction

Script ScriptFileName()
ScriptAndAppNames (msgAppName)
EndScript

Script HotkeyHelp ()
If UserBufferIsActive () Then 
    UserBufferDeactivate ()
EndIf
UserBufferClear ()
SayFormattedMessage (Ot_User_Buffer, MSGHotkeyHelp+cscBufferNewLine+cscBufferNewLine)
SayFormattedMessage (Ot_User_Buffer, MSGPressEscape)
EndScript

Void Function sayZoomNotification (Int notificationNumber)
; we're doing stringSegments from the right, so subtract from 0 To make it a negative number
notificationNumber = (0-notificationNumber)
Var
    String output
Let output = StringSegment (zNotificationOutput, "|", notificationNumber)
If StringIsBlank (output) Then 
    sayMessage (OT_ERROR, MSGNoAlertsAvailable)
    Return
EndIf
If IsSameScript () Then 
    SayFormattedMessage (Ot_User_Buffer, output)
    SayFormattedMessage (Ot_User_Buffer, MSGPressEscape)
else
    Say (output, OT_USER_REQUESTED_INFORMATION)
EndIf
EndFunction

Script SayNotification1 ()
sayZoomNotification (1)
EndScript

Script SayNotification2 ()
sayZoomNotification (2)
EndScript

Script SayNotification3 ()
sayZoomNotification (3)
EndScript

Script SayNotification4 ()
sayZoomNotification (4)
EndScript

Script SayNotification5 ()
sayZoomNotification (5)
EndScript

Script SayNotification6 ()
sayZoomNotification (6)
EndScript

Script SayNotification7 ()
sayZoomNotification (7)
EndScript

Script SayNotification8 ()
sayZoomNotification (8)
EndScript

Script SayNotification9 ()
sayZoomNotification (9)
EndScript

Script SayNotification10 ()
sayZoomNotification (10)
EndScript

; Detect Participants List.

Function IsParticipantsList ()
Var
Handle grip,
String SParticipants
Let grip = GetFocus ()
Let SParticipants = GetWindowName (grip)
If StringContains (SParticipants, MSGParticipant) 
    || StringContains (SParticipants, MSGAttendee) Then ; this is the correct area but is it the List Box?
        If GetObjectType ()== "list box item" Then ; this is the Participants List.
Return True
else
    Return False
EndIf
EndIf
EndFunction

Function IsChatList ()
Var
Handle grip,
String SChats
Let grip = GetFocus ()
Let SChats= GetWindowName (grip)
If StringContains (SChats, MSGChat) 
    && GetObjectType ()== "list box item" Then ; this is the Chat List.
    Return True
else
    Return False
EndIf

EndFunction

 Void Function focusChangedEvent(Handle hwnd, Handle hwndPrev)
; Suppress unwanted speech, temporary measure. 
If hwnd == hwndPrev 
    If focusInChatEdit && IsFocusInChatEdit()
        Return
    ElIf IsWaitingOnExtraEventsFromRefresh()
        Return
    EndIf
EndIf
focusInChatEdit = IsFocusInChatEdit()

If IsParticipantsList () 
|| IsChatList () Then 
    Return
EndIf

; Default.
focusChangedEvent(hwnd, hwndPrev)

EndFunction
Int Function handleCustomWindows (Handle FocusWindow)
Var
    Handle realWindow,
    Int type,
    String realWindowName,
    String SObjectName,
    String output,
    Int WindowHierarchyX,
    ; For SayControlEX Function:
    String controlName,
    String controlType = getObjectType (),
    String controlValue
customBrailleComboBoxData = New Collection ; clear previously cached combo items
type = getObjectSubtypeCode ()
; identify the various controls
realWindow = GetRealWindow (focusWindow)
realWindowName = GetWindowName (realWindow)
WindowHierarchyX = GetWindowHierarchyX (focusWindow)
; Is this the Settings dialog?
If type == WT_LISTBOXITEM 
&& RealWindowName == MSGSettings Then ; it is the Settings List Box.
    If HasSpokenCategory == 0 Then ; speak the prompt To Let the user know the location.
        SayUsingVoice (VCTX_Message, MSGCategoryList, Ot_User_Requested_Information)
    EndIf ; End of whether we should say the prompt.
    Let HasSpokenCategory = 1 ; Set the flag so we don't say it again.
else
    Let HasSpokenCategory = 0 ; We can say the prompt now.
EndIf ; End of Settings List Box.
If GetObjectName(SOURCE_CACHED_DATA, 0)== MSGScreenShare Then ; Screen share is active but there is no focus.
    PerformScript Tab ()
Return
EndIf ; end of screen share.

If type != WT_COMBOBOX
; outside of meeting scheduler or Not On troublesome combo boxes.
    Return handleCustomWindows (focusWindow)
EndIf
Var String objectClassName = getObjectClassName ()
If WindowHierarchyX == 1 Then 
    controlName = MSGDate
    controlValue = GetObjectValue(SOURCE_CACHED_DATA)
ElIf WindowHierarchyX == 2 Then 
    controlName = MSGTime
    controlValue = GetObjectValue(SOURCE_CACHED_DATA)
ElIf WindowHierarchyX == 3 Then 
    controlName = MSGHours
    controlValue = GetObjectValue(SOURCE_CACHED_DATA)
ElIf WindowHierarchyX == 4 Then 
    controlName = MSGMinutes
    controlValue = GetObjectValue(SOURCE_CACHED_DATA)
ElIf objectClassName == ZoomSchedulerComboBoxClass 
|| objectClassName == ZoomSettingsComboBoxClass Then
    getDataFromCustomComboBoxes (controlName, controlValue)
EndIf ; end of timezone
If stringIsBlank (controlName) Then
Return handleCustomWindows (focusWindow)
EndIf
; just For code readability, the following variables are empty:
Var String controlState, String containerName, String containerType;
sayControlEX (focusWindow, controlName, controlType, controlState, containerName, containerType, controlValue)
Return True
EndFunction

Void Function getDataFromCustomComboBoxes (String byRef name, String byRef value)
Var String text = getObjectName ()
If ! stringContains (text, "(") && ! stringContains (text, ")") Then
    name = text
    value = getObjectValue (SOURCE_CACHED_DATA)
    Return
EndIf
name = stringSegment (text, "(", 1)
text = stringChopLeft (text, stringLength (name))
value = stringSegment (text, ")", 1)+")"
name = stringTrimTrailingBlanks (name)
value = stringTrimLeadingBlanks (value)
; Now remove extra content from Name component by substituting:
If stringContains (name, wnTimeZone) Then
    name = msgTimeZone
ElIf StringContains (name, wnMicrophone) Then
    name = msgMicrophone
ElIf stringContains (name, wnSpeakers) Then
    name = msgSpeakers
EndIf
EndFunction

Int Function BrailleCallbackObjectIdentify ()
Var
    Int typeCode = getObjectTypeCode (),
    String objectClassName = getObjectClassName (),
        String controlName, String controlValue
If typeCode == WT_DIALOG Then
; could be dialog or dialog page:
    Return WT_STATIC
ElIf ! typeCode && getObjectClassName () == "ZPFTEWndClass" Then
; a static whose name is relevant:
    Return WT_STATIC
EndIf
If objectClassName == ZoomSchedulerComboBoxClass 
|| objectClassName == ZoomSettingsComboBoxClass Then
; can't automatically pass Collection items into a param, so Use the local variables first:
    getDataFromCustomComboBoxes (controlName, controlValue)
    customBrailleComboBoxData.controlName = controlName
    customBrailleComboBoxData.ControlValue = controlValue
EndIf
If (focusInChatEdit)
    Return WT_EDIT
EndIf

Return BrailleCallbackObjectIdentify ()
EndFunction

Int Function BrailleAddObjectName (Int subtypeCode)
If (focusInChatEdit)
    Return True
EndIf
    
If subtypeCode != WT_COMBOBOX 
|| ! CollectionItemCount (customBrailleComboBoxData) Then
    Return BrailleAddObjectName (subtypeCode)
EndIf
BrailleAddString (customBrailleComboBoxData.ControlName, 0, 0, 0)
Return True
EndFunction

Int Function BrailleAddObjectValue (Int subtypeCode)
If subtypeCode == WT_COMBOBOX 
&& CollectionItemCount (customBrailleComboBoxData) Then
    BrailleAddString (customBrailleComboBoxData.ControlValue, 0, 0, 0)
    Return True
EndIf
; Handle edits.
; The Zoom Cloud Meeting app uses a single window containing all controls. 
;It does Not appear To support the UIA text pattern. 
;Text in the window is rendered in the OSM and JAWS can track the caret. 
;We thus add the focused line If the subtype code is WT_EDIT and the window class is ZPFTEWndClass etc.
If subtypeCode==WT_EDIT Then
    Var String class = GetWindowClass(GetFocus())
    If class=="ZPFTEWndClass" ; main window
        || class=="zWaitHostWndClass" ; Meeting ID or name field.
        || class=="ZPConfChatWndClass" ; meeting chat area.
        || class=="ConfMeetingInfoWndClass" Then
        BrailleAddFocusLine();
        Return True
    EndIf
EndIf

If (focusInChatEdit)
    BrailleAddFocusLine();
    Return True
EndIf

If subtypeCode != WT_STATIC Then
    Return BrailleAddObjectValue (subtypeCode)
EndIf
; where we have a dialog or dialog page with focus, and Braille would otherwise be empty:
Var String text = GetObjectName(SOURCE_CACHED_DATA)
If ! stringIsBlank (text) Then
    BrailleAddString (text, 0, 0, 0)
    Return True
EndIf
Return BrailleAddObjectValue (subtypeCode)
EndFunction

 

Script WindowKeysHelp ()
Var String help = msgWindowKeysHelp+cscBufferNewLine+cscBufferNewLine+cMsgBuffExit
If UserBufferIsActive () Then 
    UserBufferDeactivate ()
EndIf
UserBufferClear ()
sayMessage (OT_USER_BUFFER, help)
EndScript

Script ZoomNotifyCurrentSpeaker ()
; The Zoom keystroke is control+2, which conflicts with our notifications.
; We're Not doing anything but send the key, Zoom is responsible To push the notification back To us / any accessibility software when the keystroke is sent.
TypeKey (ksNotifyCurrentSpeaker)
; That's all we're doing now because the alert is dealt with elsewhere.
EndScript

Script AnnounceJAWSSettingsForZoom ()
; Now assigned To Control+F9. Included in Hotkey Help.
SettingsReminder()
EndScript

; Added For Settings Dialog.
 
 Script ScreenSensitiveHelp ()
If HasSpokenCategory == 1 Then ;; We are in the appropriate category list in the Settings dialog.
        If UserBufferIsActive () Then 
    UserBufferDeactivate ()
EndIf
UserBufferClear ()
SayFormattedMessage(ot_user_buffer, MSGZoomCategoryList+cscBufferNewLine+cscBufferNewLine)
    SayFormattedMessage(ot_user_buffer, MSGPressEscape)
else ; Default
    performScript ScreenSensitiveHelp()
EndIf   

EndScript
; The following scripts are required To facilitate access To Participant and Chat Lists. Temporary measure.

Script Tab ()
CancelWaitingForExtraEventsFromRefresh()
performscript tab ()
pause ()
If IsParticipantsList () Then 
    SayUsingVoice (VCTX_Message, MSGParticipantsList, ot_user_requested_information)
    sayline ()
EndIf
If IsChatList () Then 
    SayUsingVoice (VCTX_Message, MSGChatList, ot_user_requested_information)
    sayline ()
EndIf

EndScript

Script ShiftTab()
CancelWaitingForExtraEventsFromRefresh()
performscript ShiftTab()
pause ()
If IsParticipantsList () Then 
    SayUsingVoice (VCTX_Message, MSGParticipantsList, ot_user_requested_information)
    sayline ()
EndIf
If IsChatList () Then 
    SayUsingVoice (VCTX_Message, MSGChatList, ot_user_requested_information)
    sayline ()
EndIf

EndScript

Script SayNextLine ()
CancelWaitingForExtraEventsFromRefresh()
If IsParticipantsList () 
|| IsChatList () Then 
    If IsPCCursor () Then 
        PerformScript SayNextLine ()
        pause ()
        SayLine ()
    else
        PerformScript SayNextLine ()
    EndIf
else
    PerformScript SayNextLine ()
EndIf
EndScript

Script SayPriorLine()
CancelWaitingForExtraEventsFromRefresh()
If IsParticipantsList () 
|| IsChatList () Then 
    If IsPCCursor () Then 
        PerformScript SayPriorLine ()
        pause ()
        SayLine ()
    else
        PerformScript SayPriorLine ()
    EndIf
else
    PerformScript SayPriorLine ()
EndIf
EndScript

Script JAWSHome ()
CancelWaitingForExtraEventsFromRefresh()
PerformScript JAWSHome ()
If IsParticipantsList () 
|| IsChatList () Then 
    If IsPCCursor () Then 
        pause ()
        SayLine ()
    EndIf
EndIf
EndScript

Script JAWSEnd ()
CancelWaitingForExtraEventsFromRefresh()
PerformScript JAWSEnd ()
If IsParticipantsList () 
|| IsChatList () Then 
        If IsPCCursor () Then 
        pause ()
        SayLine ()
    EndIf
EndIf
EndScript

Int Function IsFocusInChatEdit()
If (!GetObjectIsEditable ())
    Return False
EndIf

Var String currentClass = GetWindowClass (GetFocus ())
If (currentClass != ZoomChatClass) 
    Return False
EndIf

Return True
EndFunction

Void Function NameChangedEvent (Handle hwnd, Int objId, Int childId, Int nObjType,
    String sOldName, String sNewName)
If IsWaitingOnExtraEventsFromRefresh()
&& GetObjectSubtypeCode() == wt_button
&& sNewName == getObjectname()
    ; We are assuming an MSAARefresh coincides with a name change For the button in focus, and that this is the event we want To speak.
    MarkSpeechAsHandledInExtraEventFromRefresh()
EndIf
If (focusInChatEdit)
    Return ; do nothing here To prevent default from running
EndIf
NameChangedEvent (hwnd, objId, childId, nObjType, sOldName, sNewName)
EndFunction

Void Function SayObjectTypeAndText(optional Int nLevel, Int includeContainerName, Int drawHighLight)
If (focusInChatEdit)
    Return IndicateControlType (WT_EDIT, cscNull, GetLine())
EndIf

SayObjectTypeAndText(nLevel, includeContainerName, drawHighLight)
EndFunction

Int Function GetMeetingShareRect(Int byref left, Int byref top, Int byref right, Int byref bottom)
Var Handle hApp = GetAppMainWindow(GetFocus())
Var Handle hShare = FindWindow(hApp, cscNull, wnMeetingShare)
If !hShare || !GetWindowRect (hShare, left, right, top, bottom) Then
    Return False
EndIf
If !IsValidRect(left, top, right, bottom)
    Return False
EndIf
Var Handle hTools = FindWindow(hApp, cscNull, wnMeetingTools)
If hTools && IsWindowVisible(hTools) Then
    Var Int mtLeft, Int mtTop, Int mtRight, Int mtBottom
    If GetWindowRect (hTools, mtLeft, mtRight, mtTop, mtBottom) Then
        bottom = min(bottom, mtTop)
    EndIf
EndIf
Return True
EndFunction

Void Function PictureSmartAllInOneZoom (Int serviceOptions)
Var
    Int left, Int top, Int right, Int bottom
If GetMeetingShareRect(left, top, right, bottom)
    PictureSmartWithAreaShared(serviceOptions, left, top, right, bottom)
    Return
EndIf
PerformScript PictureSmartWithControl(serviceOptions)
EndFunction

Script PictureSmartAllInOne (optional Int serviceOptions)
PictureSmartAllInOneZoom (PSServiceOptions_Single | serviceOptions)
EndScript

Script PictureSmartAllInOneMultiService (optional Int serviceOptions)
PictureSmartAllInOneZoom (PSServiceOptions_Multi | serviceOptions)
EndScript

Int Function SetCustomBackgroundOCRRect()
Var
    Int iLeft, Int iRight, Int iTop, Int iBottom
If !GetMeetingShareRect(iLeft, iTop, iRight, iBottom)
    Return False
EndIf
Return UpdateBackgroundOCRRectCollection(iLeft, iTop, iRight, iBottom)
EndFunction