diff --git a/Includes/Config.au3 b/Includes/Config.au3 index eeec8a7..2d29bf8 100644 --- a/Includes/Config.au3 +++ b/Includes/Config.au3 @@ -24,6 +24,22 @@ Func LoadMeetingConfig() $g_UserSettings.Add("MidweekTime", _UTF8ToString(IniRead($CONFIG_FILE, "Meetings", "MidweekTime", ""))) $g_UserSettings.Add("WeekendDay", _UTF8ToString(IniRead($CONFIG_FILE, "Meetings", "WeekendDay", ""))) $g_UserSettings.Add("WeekendTime", _UTF8ToString(IniRead($CONFIG_FILE, "Meetings", "WeekendTime", ""))) + + ; Backward-compat migration for versions that incorrectly wrote day fields under [General] + If $g_UserSettings.Item("MidweekDay") = "" Then + Local $legacyMidweekDay = _UTF8ToString(IniRead($CONFIG_FILE, "General", "MidweekDay", "")) + If $legacyMidweekDay <> "" Then + $g_UserSettings.Item("MidweekDay") = $legacyMidweekDay + IniWrite($CONFIG_FILE, "Meetings", "MidweekDay", $legacyMidweekDay) + EndIf + EndIf + If $g_UserSettings.Item("WeekendDay") = "" Then + Local $legacyWeekendDay = _UTF8ToString(IniRead($CONFIG_FILE, "General", "WeekendDay", "")) + If $legacyWeekendDay <> "" Then + $g_UserSettings.Item("WeekendDay") = $legacyWeekendDay + IniWrite($CONFIG_FILE, "Meetings", "WeekendDay", $legacyWeekendDay) + EndIf + EndIf $g_UserSettings.Add("HostToolsValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "HostToolsValue", ""))) $g_UserSettings.Add("ParticipantValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "ParticipantValue", ""))) $g_UserSettings.Add("MuteAllValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "MuteAllValue", ""))) @@ -36,6 +52,12 @@ Func LoadMeetingConfig() $g_UserSettings.Add("StartVideoValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "StartVideoValue", ""))) $g_UserSettings.Add("ZoomSecurityUnmuteValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "ZoomSecurityUnmuteValue", ""))) $g_UserSettings.Add("ZoomSecurityShareScreenValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "ZoomSecurityShareScreenValue", ""))) + ; Optional advanced labels for share-screen permission flow + $g_UserSettings.Add("ShareOptionsValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "ShareOptionsValue", ""))) + $g_UserSettings.Add("HostToolsForShareValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "HostToolsForShareValue", ""))) + $g_UserSettings.Add("WhoCanShareComboValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "WhoCanShareComboValue", ""))) + $g_UserSettings.Add("WhoCanShareHostOnlyValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "WhoCanShareHostOnlyValue", ""))) + $g_UserSettings.Add("WhoCanShareParticipantsValue", _UTF8ToString(IniRead($CONFIG_FILE, "ZoomStrings", "WhoCanShareParticipantsValue", ""))) $g_UserSettings.Add("KeyboardShortcut", _UTF8ToString(IniRead($CONFIG_FILE, "General", "KeyboardShortcut", ""))) ; Window snapping preference (Disabled|Left|Right) diff --git a/Includes/GUI.au3 b/Includes/GUI.au3 index adbbb67..0281fd5 100644 --- a/Includes/GUI.au3 +++ b/Includes/GUI.au3 @@ -18,6 +18,7 @@ #include "UIAutomation.au3" #include "Utils.au3" #include "ZoomOperations.au3" +#include "StateProfiler.au3" ; ================================================================================================ @@ -186,6 +187,9 @@ Func ShowConfigGUI() ; Action buttons (adjusted for wider GUI) Global $idSaveBtn = GUICtrlCreateButton(t("BTN_SAVE"), 10, $currentY, 100, 30) Global $idQuitBtn = GUICtrlCreateButton(t("BTN_QUIT"), 120, $currentY, 100, 30) + $g_DiagnosticsBtn = GUICtrlCreateButton("UI Diagnostics", 230, $currentY, 110, 30) + $g_PathWizardBtn = GUICtrlCreateButton("Path Wizard", 350, $currentY, 100, 30) + $g_StateProfilerBtn = GUICtrlCreateButton("State Profiler", 460, $currentY, 110, 30) $currentY += 30 @@ -196,6 +200,9 @@ Func ShowConfigGUI() ; Set button event handlers GUICtrlSetOnEvent($idSaveBtn, "SaveConfigGUI") GUICtrlSetOnEvent($idQuitBtn, "QuitApp") + GUICtrlSetOnEvent($g_DiagnosticsBtn, "RunUIDiagnostics") + GUICtrlSetOnEvent($g_PathWizardBtn, "RunPathCaptureWizard") + GUICtrlSetOnEvent($g_StateProfilerBtn, "RunStateTrainingWizard") ; ================================================================================================ ; DYNAMIC HEIGHT CALCULATION AND GUI RESIZING @@ -696,7 +703,8 @@ Func GetElementNamesForField() EndIf ; Get Zoom window object - If Not _GetZoomWindow() Then Return + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return ; Open Host Tools menu to collect names from it too Local $oHostMenu = _OpenHostTools() @@ -948,8 +956,8 @@ Func SaveConfigGUI() ; Save day settings (values are 1-7 ints) Local $midWeekDay = $g_UserSettings.Item("MidweekDay") Local $weekendDay = $g_UserSettings.Item("WeekendDay") - IniWrite($CONFIG_FILE, "General", "MidweekDay", $midWeekDay) - IniWrite($CONFIG_FILE, "General", "WeekendDay", $weekendDay) + IniWrite($CONFIG_FILE, "Meetings", "MidweekDay", $midWeekDay) + IniWrite($CONFIG_FILE, "Meetings", "WeekendDay", $weekendDay) ; Update keyboard shortcut _UpdateKeyboardShortcut() @@ -1051,7 +1059,7 @@ Func TrayEvent() Switch $msg Case 0 Return - Case $TRAY_EVENT_PRIMARYDOWN + Case $TRAY_EVENT_PRIMARYDOWN, $TRAY_EVENT_PRIMARYUP ; Show/Hide config on click If $g_ConfigGUI Then If BitAND(WinGetState($g_ConfigGUI), 2) Then ; Visible @@ -1063,16 +1071,10 @@ Func TrayEvent() Else ShowConfigGUI() EndIf - Case $TRAY_EVENT_SECONDARYDOWN ; Right-click + Case $TRAY_EVENT_SECONDARYDOWN, $TRAY_EVENT_SECONDARYUP ; Right-click ShowConfigGUI() - While $g_ConfigGUI - Sleep(100) - WEnd Case $TRAY_EVENT_PRIMARYDOUBLE ; Double-click ShowConfigGUI() - While $g_ConfigGUI - Sleep(100) - WEnd EndSwitch EndFunc ;==>TrayEvent @@ -1354,7 +1356,8 @@ Func GetElementNames() Debug("Active Zoom meeting found, collecting element names...", "VERBOSE") ; Get Zoom window object - If Not _GetZoomWindow() Then Return + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return ; Open Host Tools menu to collect names from it too Local $oHostMenu = _OpenHostTools() @@ -1380,3 +1383,57 @@ Func GetElementNames() Debug("Element names collection completed", "VERBOSE") EndFunc ;==>GetElementNames + + +; Captures UI names from Zoom + HostTools and saves to a diagnostics file. +Func RunUIDiagnostics() + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then + ReportUserFacingError("UI diagnostics failed: Zoom meeting window not found.") + Return + EndIf + Local $oHostMenu = _OpenHostTools() + Local $aNames = GetElementNamesFromWindows($oZoomWindow, $oHostMenu) + If UBound($aNames) = 0 Then + ReportUserFacingError("UI diagnostics found no element names.") + Return + EndIf + + Local $outFile = @ScriptDir & "\zoom_ui_diagnostics.txt" + Local $h = FileOpen($outFile, $FO_OVERWRITE + $FO_CREATEPATH) + If $h = -1 Then + ReportUserFacingError("Could not write diagnostics file: " & $outFile) + Return + EndIf + FileWriteLine($h, "ZoomMate UI Diagnostics - " & @YEAR & "-" & @MON & "-" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC) + For $i = 0 To UBound($aNames) - 1 + FileWriteLine($h, $aNames[$i]) + Next + FileClose($h) + MsgBox(64 + 262144, "ZoomMate", "Diagnostics saved:" & @CRLF & $outFile) +EndFunc ;==>RunUIDiagnostics + +; Lightweight wizard to capture panel/button relationship labels and persist to ini. +Func RunPathCaptureWizard() + Local $moreLabel = InputBox("Path Wizard", "Label for More button/menu item:", GetUserSetting("MoreMeetingControlsValue")) + If @error Then Return + Local $hostToolsLabel = InputBox("Path Wizard", "Label for Host Tools button/item:", GetUserSetting("HostToolsValue")) + If @error Then Return + Local $participantsLabel = InputBox("Path Wizard", "Label for Participants section/button:", GetUserSetting("ParticipantValue")) + If @error Then Return + + IniWrite($CONFIG_FILE, "UiPathMap", "MoreButton", _StringToUTF8($moreLabel)) + IniWrite($CONFIG_FILE, "UiPathMap", "HostToolsButton", _StringToUTF8($hostToolsLabel)) + IniWrite($CONFIG_FILE, "UiPathMap", "ParticipantsNode", _StringToUTF8($participantsLabel)) + + ; Also update standard labels used by automation. + $g_UserSettings.Item("MoreMeetingControlsValue") = $moreLabel + $g_UserSettings.Item("HostToolsValue") = $hostToolsLabel + $g_UserSettings.Item("ParticipantValue") = $participantsLabel + IniWrite($CONFIG_FILE, "ZoomStrings", "MoreMeetingControlsValue", _StringToUTF8($moreLabel)) + IniWrite($CONFIG_FILE, "ZoomStrings", "HostToolsValue", _StringToUTF8($hostToolsLabel)) + IniWrite($CONFIG_FILE, "ZoomStrings", "ParticipantValue", _StringToUTF8($participantsLabel)) + + CheckConfigFields() + MsgBox(64 + 262144, "ZoomMate", "Path map saved. You can re-run this anytime after Zoom UI changes.") +EndFunc ;==>RunPathCaptureWizard diff --git a/Includes/Globals.au3 b/Includes/Globals.au3 index 6765bb5..f4081a6 100644 --- a/Includes/Globals.au3 +++ b/Includes/Globals.au3 @@ -55,6 +55,9 @@ Global $g_ElementNamesSelectionResult = "" ; Selected element name resul Global $g_ElementNamesSelectionCallback = "" ; Callback function for selection Global $g_ActiveFieldForLookup = 0 ; Currently active field for lookup operations Global $g_FieldLabels = ObjCreate("Scripting.Dictionary") ; Maps field names to label control IDs +Global $g_DiagnosticsBtn = 0 +Global $g_PathWizardBtn = 0 +Global $g_StateProfilerBtn = 0 ; Day mapping containers for internationalization Global $g_DayLabelToNum = ObjCreate("Scripting.Dictionary") ; Day name -> number (1-7) diff --git a/Includes/MeetingAutomation.au3 b/Includes/MeetingAutomation.au3 index 942e34b..38e69b9 100644 --- a/Includes/MeetingAutomation.au3 +++ b/Includes/MeetingAutomation.au3 @@ -30,7 +30,8 @@ Func _LaunchZoom() Debug(t("INFO_ZOOM_LAUNCHED") & ": " & $meetingID, "INFO") Sleep(10000) ; Wait for Zoom to launch - If Not _GetZoomWindow() Then Return False + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return False _SnapZoomWindowToSide() @@ -51,6 +52,7 @@ Func _SetPreAndPostMeetingSettings() SetSecuritySetting(GetUserSetting("ZoomSecurityShareScreenValue"), False) ; Prevent screen sharing ToggleFeed("Audio", False) ; Turn off host audio ToggleFeed("Video", False) ; Turn off host video + EnsureGalleryView() ; TODO: Unmute All function Debug(t("INFO_CONFIG_BEFORE_AFTER_DONE"), "INFO") EndFunc ;==>_SetPreAndPostMeetingSettings @@ -71,9 +73,40 @@ Func _SetDuringMeetingSettings() MuteAll() ; Mute all participants ToggleFeed("Audio", True) ; Turn on host audio ToggleFeed("Video", True) ; Turn on host video + PulseSpotlightHostVideo(5000) + EnsureGalleryView() + _OpenParticipantsPanel() + _SnapZoomWindowToSide() Debug(t("INFO_CONFIG_DURING_MEETING_DONE"), "INFO") EndFunc ;==>_SetDuringMeetingSettings +; Runs a named automation scene (useful for external trigger integrations such as Electron) +; @param $sScene - Supported values: "prepost", "prestart" +; @return Boolean - True when a valid scene was executed +Func RunAutomationScene($sScene) + Local $sNormalizedScene = StringLower(StringStripWS($sScene, 3)) + + Switch $sNormalizedScene + Case "prepost" + Debug("Running automation scene: prepost", "INFO") + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return False + _SetPreAndPostMeetingSettings() + Return True + + Case "prestart" + Debug("Running automation scene: prestart", "INFO") + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return False + _SetDuringMeetingSettings() + Return True + + Case Else + Debug("Unknown automation scene requested: '" & $sScene & "'", "WARN") + Return False + EndSwitch +EndFunc ;==>RunAutomationScene + ; Checks current time against meeting schedule and applies appropriate settings ; @param $meetingTime - Scheduled meeting time in HH:MM format ; Checks current time against meeting schedule and applies appropriate settings @@ -109,7 +142,8 @@ Func CheckMeetingWindow($meetingTime) ElseIf $nowMin = ($meetingMin - $MEETING_START_WARNING_MINUTES) Then ; Meeting start window (1 minute before meeting) If Not $g_DuringMeetingSettingsConfigured Then - If Not _GetZoomWindow() Then Return 1000 ; Retry quickly if window not found + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return 1000 ; Retry quickly if window not found _SetDuringMeetingSettings() $g_DuringMeetingSettingsConfigured = True EndIf diff --git a/Includes/Settings.au3 b/Includes/Settings.au3 index 287414e..038c1e9 100644 --- a/Includes/Settings.au3 +++ b/Includes/Settings.au3 @@ -9,6 +9,7 @@ #include "UIAutomation.au3" #include "ElementActions.au3" #include "ZoomOperations.au3" +#include "ZoomPathEngine.au3" ; ================================================================================================ ; ZOOM SETTINGS MANAGEMENT FUNCTIONS @@ -19,50 +20,119 @@ Func _FindSecuritySettingInternal($sSetting, $oHostMenu) Return FindElementByPartialName($sSetting, Default, $oHostMenu) EndFunc ;==>_FindSecuritySettingInternal +; Finds first matching element from a pipe-separated list of partial names. +Func _FindElementByAnyName($sCandidates, $aControlTypes, $oParent) + Local $aNames = StringSplit($sCandidates, "|", 2) + For $i = 0 To UBound($aNames) - 1 + Local $name = StringStripWS($aNames[$i], 3) + If $name = "" Then ContinueLoop + Local $o = FindElementByPartialName($name, $aControlTypes, $oParent) + If IsObj($o) Then Return $o + Next + Return 0 +EndFunc ;==>_FindElementByAnyName + +; Sets "Who can share" via Share Options -> Host tools for sharing flow. +; @param $bAllowParticipants - True to allow all participants, False for host-only +Func SetShareScreenPermission($bAllowParticipants) + If Not EnsureZoomMainWindow() Then Return False + + ; Step 1: open Share options menu + Local $aMenuItemTypes[1] = [$UIA_MenuItemControlTypeId] + Local $shareOptionsCandidates = GetUserSetting("ShareOptionsValue") + If $shareOptionsCandidates = "" Then $shareOptionsCandidates = "Option partage|Share options" + Local $oShareOptions = _FindElementByAnyName($shareOptionsCandidates, $aMenuItemTypes, $oZoomWindow) + If Not IsObj($oShareOptions) Then + ReportUserFacingError("Share options button/menu not found.") + Return False + EndIf + If Not _ClickElement($oShareOptions, True) Then Return False + Sleep(350) + + ; Step 2: click Host tools for sharing item in popup menu + Local $oShareMenu = FindElementByClassName("WCN_ModelessWnd", Default, $oZoomWindow) + If Not IsObj($oShareMenu) Then + ReportUserFacingError("Share options menu did not open.") + Return False + EndIf + Local $hostToolsShareCandidates = GetUserSetting("HostToolsForShareValue") + If $hostToolsShareCandidates = "" Then $hostToolsShareCandidates = "Outils de l’hôte pour le partage|Host tools for sharing" + Local $oHostToolsShare = _FindElementByAnyName($hostToolsShareCandidates, $aMenuItemTypes, $oShareMenu) + If Not IsObj($oHostToolsShare) Then + ReportUserFacingError("'Host tools for sharing' item not found.") + Return False + EndIf + If Not _ClickElement($oHostToolsShare, True) Then Return False + Sleep(500) + + ; Step 3: select desired item from 'Who can share' combobox/list + Local $aComboTypes[1] = [$UIA_ComboBoxControlTypeId] + Local $comboCandidates = GetUserSetting("WhoCanShareComboValue") + If $comboCandidates = "" Then $comboCandidates = "Qui peut partager|Who can share" + Local $oWhoCanShareCombo = _FindElementByAnyName($comboCandidates, $aComboTypes, $oZoomWindow) + If IsObj($oWhoCanShareCombo) Then _ClickElement($oWhoCanShareCombo, True) + Sleep(300) + + Local $aListItemTypes[1] = [$UIA_ListItemControlTypeId] + Local $targetCandidates = "" + If $bAllowParticipants Then + $targetCandidates = GetUserSetting("WhoCanShareParticipantsValue") + If $targetCandidates = "" Then $targetCandidates = "Tous les participants|All participants" + Else + $targetCandidates = GetUserSetting("WhoCanShareHostOnlyValue") + If $targetCandidates = "" Then $targetCandidates = "Hôte seulement|Host only" + EndIf + Local $oTarget = _FindElementByAnyName($targetCandidates, $aListItemTypes, $oZoomWindow) + If Not IsObj($oTarget) Then + ReportUserFacingError("Could not find target 'Who can share' option: " & $targetCandidates) + Return False + EndIf + If Not _ClickElement($oTarget, True) Then Return False + + Return True +EndFunc ;==>SetShareScreenPermission + ; Sets a security setting to the desired state (enabled/disabled) ; @param $sSetting - Setting name to modify ; @param $bDesired - Desired state (True=enabled, False=disabled) Func SetSecuritySetting($sSetting, $bDesired) Debug(t("INFO_SETTING_SECURITY", $sSetting), "INFO") - Sleep(3000) - Local $oHostMenu = _OpenHostTools() - If Not IsObj($oHostMenu) Then Return False - - Local $oSetting = FindElementByPartialName($sSetting, Default, $oHostMenu) - If Not IsObj($oSetting) Then Return - - ; Check current state - Local $sLabel - $oSetting.GetCurrentPropertyValue($UIA_NamePropertyId, $sLabel) - - Debug("Element name: '" & $sLabel & "'", "VERBOSE") - - ; Setting is enabled if label does NOT contain unchecked indicator - Local $uncheckedValue = GetUserSetting("UncheckedValue") - Local $sLabelLower = StringLower($sLabel) - Local $uncheckedLower = StringLower($uncheckedValue) - - Debug("Raw label: '" & $sLabel & "'", "VERBOSE") - Debug("Label length: " & StringLen($sLabel), "VERBOSE") - Debug("Unchecked value: '" & $uncheckedValue & "'", "VERBOSE") - Debug("Unchecked length: " & StringLen($uncheckedValue), "VERBOSE") - Debug("Label lower: '" & $sLabelLower & "'", "VERBOSE") - Debug("Unchecked lower: '" & $uncheckedLower & "'", "VERBOSE") - - ; Check if unchecked value appears anywhere in the label (not just word boundaries) - Local $bEnabled = (StringInStr($sLabelLower, $uncheckedLower) = 0) - - Debug("StringInStr result: " & StringInStr($sLabelLower, $uncheckedLower), "VERBOSE") - Debug("Setting '" & $sLabel & "' | Current: " & ($bEnabled ? "True" : "False") & " | Desired: " & $bDesired, "VERBOSE") - - ; Only click if state needs to change - If $bEnabled <> $bDesired Then - _HoverElement($oSetting, 50) - _MoveMouseToStartOfElement($oSetting, True) ; Click at start of element to ensure change - Debug("Toggled setting '" & $sSetting & "'", "SETTING CHANGE") + + ; Share-screen permission is now under Share Options -> Host tools for sharing -> Who can share. + If $sSetting = GetUserSetting("ZoomSecurityShareScreenValue") Then + Return SetShareScreenPermission($bDesired) + EndIf + + Local $oSetting = EnsureSecurityToggleVisible($sSetting) + If Not IsObj($oSetting) Then Return False + + ; Determine current state. Prefer UIA toggle state when available, fallback to label parsing. + Local $bEnabled = False + Local $toggleState + $oSetting.GetCurrentPropertyValue($UIA_ToggleToggleStatePropertyId, $toggleState) + If Not @error And IsNumber($toggleState) Then + $bEnabled = ($toggleState = 1) ; 0=Off, 1=On, 2=Indeterminate Else + Local $sLabel = "" + $oSetting.GetCurrentPropertyValue($UIA_NamePropertyId, $sLabel) + Local $uncheckedValue = GetUserSetting("UncheckedValue") + $bEnabled = (StringInStr(StringLower($sLabel), StringLower($uncheckedValue)) = 0) + EndIf + + If $bEnabled = $bDesired Then + Debug("Security setting already in desired state: " & $sSetting, "VERBOSE") _CloseHostTools() + Return True EndIf + + _HoverElement($oSetting, 50) + If Not _MoveMouseToStartOfElement($oSetting, True) Then + ReportUserFacingError("Could not toggle security setting: " & $sSetting) + Return False + EndIf + Sleep(250) + Debug("Toggled security setting '" & $sSetting & "'", "SETTING CHANGE") + Return True EndFunc ;==>SetSecuritySetting ; Toggles host's audio or video feed on/off @@ -135,6 +205,24 @@ Func MuteAll() Return DialogClick("zChangeNameWndClass", GetUserSetting("YesValue")) EndFunc ;==>MuteAll +; Best-effort: switch host to gallery view. +; This uses Zoom's default Alt+F2 shortcut when available. +Func EnsureGalleryView() + Debug("Ensuring gallery view (best effort via Alt+F2).", "INFO") + If Not FocusZoomWindow() Then Return False + Send("!{F2}") + Sleep(300) + Return True +EndFunc ;==>EnsureGalleryView + +; Best-effort spotlight pulse for host video. +; Current implementation is a safe no-op placeholder until a robust UIA locator is configured. +Func PulseSpotlightHostVideo($durationMs = 5000) + Debug("Spotlight pulse requested for " & $durationMs & "ms. No-op until spotlight selector is configured.", "WARN") + Sleep($durationMs) + Return True +EndFunc ;==>PulseSpotlightHostVideo + ; Clicks a button in a dialog window by class name and button text ; @param $ClassName - Dialog window class name ; @param $ButtonLabel - Button text to click diff --git a/Includes/StateProfiler.au3 b/Includes/StateProfiler.au3 new file mode 100644 index 0000000..38921cd --- /dev/null +++ b/Includes/StateProfiler.au3 @@ -0,0 +1,158 @@ +#include-once +; ================================================================================================ +; STATE PROFILER - Guided Zoom state capture and signature analysis +; ================================================================================================ + +#include "Globals.au3" +#include "Utils.au3" +#include "UserSettings.au3" +#include "UIAutomation.au3" +#include "ZoomOperations.au3" + +Global Const $STATE_PROFILE_INI = @ScriptDir & "\zoom_state_profiles.ini" +Global Const $STATE_PROFILE_TXT = @ScriptDir & "\zoom_state_profiles.txt" + +; Returns a dictionary of current, high-level Zoom UI state flags. +Func GetCurrentZoomStateFlags() + Local $oFlags = ObjCreate("Scripting.Dictionary") + If Not IsObj($oFlags) Then Return 0 + + Local $oResolvedZoomWindow = _GetZoomWindow() + $oFlags.Add("ZoomWindowVisible", IsObj($oResolvedZoomWindow)) + If Not IsObj($oResolvedZoomWindow) Then Return $oFlags + + Local $oMoreMenu = FindElementByClassName("WCN_ModelessWnd", Default, $oZoomWindow) + $oFlags.Add("MoreMenuVisible", IsObj($oMoreMenu)) + + Local $oHostTools = _FindHostToolsContainer() + $oFlags.Add("HostToolsVisible", IsObj($oHostTools)) + + Local $ListType[1] = [$UIA_ListControlTypeId] + Local $oParticipantsPanel = FindElementByPartialName(GetUserSetting("ParticipantValue"), $ListType, $oZoomWindow) + $oFlags.Add("ParticipantsPanelVisible", IsObj($oParticipantsPanel)) + + Local $oParticipantsInHostTools = 0 + If IsObj($oHostTools) Then + $oParticipantsInHostTools = FindElementByPartialName(GetUserSetting("ParticipantValue"), Default, $oHostTools) + EndIf + $oFlags.Add("HostToolsParticipantsNodeVisible", IsObj($oParticipantsInHostTools)) + + Local $oMuteHostButton = FindElementByPartialName(GetUserSetting("CurrentlyUnmutedValue"), Default, $oZoomWindow) + $oFlags.Add("HostAudioOn", IsObj($oMuteHostButton)) + + Local $oStopVideoButton = FindElementByPartialName(GetUserSetting("StopVideoValue"), Default, $oZoomWindow) + $oFlags.Add("HostVideoOn", IsObj($oStopVideoButton)) + + ; Best effort: Gallery view is usually toggled via Alt+F2; Zoom does not reliably expose a stable named element. + $oFlags.Add("GalleryViewDetected", False) + + Return $oFlags +EndFunc ;==>GetCurrentZoomStateFlags + +; Captures current state flags and visible named elements into INI + text report. +Func CaptureCurrentStateSnapshot($stateName) + Local $oFlags = GetCurrentZoomStateFlags() + If Not IsObj($oFlags) Then + ReportUserFacingError("State capture failed: could not create state dictionary.") + Return False + EndIf + + Local $section = "State_" & $stateName + IniWrite($STATE_PROFILE_INI, $section, "Timestamp", @YEAR & "-" & @MON & "-" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC) + For $k In $oFlags.Keys + IniWrite($STATE_PROFILE_INI, $section, $k, String($oFlags.Item($k))) + Next + + Local $h = FileOpen($STATE_PROFILE_TXT, $FO_APPEND + $FO_CREATEPATH) + If $h = -1 Then + ReportUserFacingError("State capture failed: cannot write " & $STATE_PROFILE_TXT) + Return False + EndIf + + FileWriteLine($h, "============================================================") + FileWriteLine($h, "State: " & $stateName & " | " & @YEAR & "-" & @MON & "-" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC) + For $k In $oFlags.Keys + FileWriteLine($h, "Flag." & $k & "=" & String($oFlags.Item($k))) + Next + + _DumpVisibleNamedElements($h) + FileClose($h) + Return True +EndFunc ;==>CaptureCurrentStateSnapshot + +; Guided wizard that asks operator to place Zoom in each state, then captures signatures. +Func RunStateTrainingWizard() + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then + ReportUserFacingError("State profiler needs an active Zoom meeting window.") + Return + EndIf + + Local $states[9] = [ _ + "BASELINE_MEETING_VIEW", _ + "MORE_MENU_OPEN", _ + "HOST_TOOLS_OPEN", _ + "HOST_TOOLS_PARTICIPANTS_SECTION", _ + "PARTICIPANTS_PANEL_OPEN", _ + "AUDIO_ON_VIDEO_ON", _ + "AUDIO_OFF_VIDEO_OFF", _ + "GALLERY_VIEW", _ + "ACTIVE_SPEAKER_VIEW"] + + Local $instructions[9] = [ _ + "Leave Zoom in normal meeting view (no menus open).", _ + "Open More menu and keep it visible.", _ + "Open Host Tools panel/menu and keep it visible.", _ + "In Host Tools, open/select Participants node for security toggles.", _ + "Open the main Participants panel.", _ + "Set host audio ON and host video ON.", _ + "Set host audio OFF and host video OFF.", _ + "Switch to Gallery view.", _ + "Switch to Active Speaker view."] + + For $i = 0 To UBound($states) - 1 + Local $res = MsgBox(1 + 262144, "ZoomMate State Profiler", _ + "Prepare this state then click OK:" & @CRLF & @CRLF & _ + $states[$i] & @CRLF & $instructions[$i] & @CRLF & @CRLF & _ + "Click Cancel to stop the wizard.") + If $res <> 1 Then ExitLoop + + If Not CaptureCurrentStateSnapshot($states[$i]) Then + ReportUserFacingError("State capture failed for: " & $states[$i]) + ExitLoop + EndIf + Next + + MsgBox(64 + 262144, "ZoomMate", "State profiling completed." & @CRLF & _ + "Saved to:" & @CRLF & $STATE_PROFILE_INI & @CRLF & $STATE_PROFILE_TXT) +EndFunc ;==>RunStateTrainingWizard + +Func _DumpVisibleNamedElements($hFile) + If Not IsObj($oZoomWindow) Then Return + + Local $aTypes[7] = [$UIA_ButtonControlTypeId, $UIA_MenuItemControlTypeId, $UIA_CheckBoxControlTypeId, $UIA_ListControlTypeId, $UIA_PaneControlTypeId, $UIA_GroupControlTypeId, $UIA_WindowControlTypeId] + For $t = 0 To UBound($aTypes) - 1 + Local $pCondition + $oUIAutomation.CreatePropertyCondition($UIA_ControlTypePropertyId, $aTypes[$t], $pCondition) + Local $pElements + $oZoomWindow.FindAll($TreeScope_Descendants, $pCondition, $pElements) + Local $oElements = ObjCreateInterface($pElements, $sIID_IUIAutomationElementArray, $dtagIUIAutomationElementArray) + If IsObj($oElements) Then + Local $iCount + $oElements.Length($iCount) + FileWriteLine($hFile, "Type=" & $aTypes[$t] & " Count=" & $iCount) + Local $limit = $iCount + If $limit > 250 Then $limit = 250 + For $i = 0 To $limit - 1 + Local $pElement + $oElements.GetElement($i, $pElement) + Local $oElement = ObjCreateInterface($pElement, $sIID_IUIAutomationElement, $dtagIUIAutomationElement) + If IsObj($oElement) Then + Local $sName = "" + $oElement.GetCurrentPropertyValue($UIA_NamePropertyId, $sName) + If StringStripWS($sName, 3) <> "" Then FileWriteLine($hFile, " - " & $sName) + EndIf + Next + EndIf + Next +EndFunc ;==>_DumpVisibleNamedElements diff --git a/Includes/UIAutomation.au3 b/Includes/UIAutomation.au3 index 804850a..9bd1dab 100644 --- a/Includes/UIAutomation.au3 +++ b/Includes/UIAutomation.au3 @@ -63,14 +63,24 @@ EndFunc ;==>_FindZoomWindowInternal ; Focuses the main Zoom meeting window ; @return Boolean - True if successful, False otherwise -Func FocusZoomWindow() +Func FocusZoomWindow($oWindow = Default) Debug("Focusing Zoom window...", "VERBOSE") - Local $oZoomWindow = _GetZoomWindow() - If Not IsObj($oZoomWindow) Then Return False + + ; Reuse already-resolved window object when provided/cached to avoid refetch races. + Local $oWindowToFocus = 0 + If $oWindow <> Default And IsObj($oWindow) Then + $oWindowToFocus = $oWindow + ElseIf IsObj($oZoomWindow) Then + $oWindowToFocus = $oZoomWindow + Else + $oWindowToFocus = _GetZoomWindow() + EndIf + + If Not IsObj($oWindowToFocus) Then Return False ; Get the native HWND property from the UIA element Local $hWnd - $oZoomWindow.GetCurrentPropertyValue($UIA_NativeWindowHandlePropertyId, $hWnd) + $oWindowToFocus.GetCurrentPropertyValue($UIA_NativeWindowHandlePropertyId, $hWnd) If $hWnd And $hWnd <> 0 Then ; Convert to HWND pointer diff --git a/Includes/Utils.au3 b/Includes/Utils.au3 index 41af760..6662cef 100644 --- a/Includes/Utils.au3 +++ b/Includes/Utils.au3 @@ -72,3 +72,16 @@ EndFunc ;==>Debug Func UpdateTrayTooltip() TraySetToolTip("ZoomMate: " & $g_StatusMsg) EndFunc ;==>UpdateTrayTooltip + + +; Reports a user-facing error in GUI/tray and optionally message box. +Func ReportUserFacingError($message, $showPopup = True) + $g_StatusMsg = $message + UpdateTrayTooltip() + If $g_ErrorAreaLabel <> 0 Then + GUICtrlSetData($g_ErrorAreaLabel, $message) + EndIf + If $showPopup Then + MsgBox(16 + 262144, "ZoomMate", $message) + EndIf +EndFunc ;==>ReportUserFacingError diff --git a/Includes/ZoomOperations.au3 b/Includes/ZoomOperations.au3 index be8a730..175df82 100644 --- a/Includes/ZoomOperations.au3 +++ b/Includes/ZoomOperations.au3 @@ -16,73 +16,92 @@ ; ZOOM-SPECIFIC UI INTERACTION FUNCTIONS ; ================================================================================================ -; Opens the Host Tools menu in Zoom -; @return Object - Host menu object or False if failed -Func _OpenHostTools() - If Not IsObj($oZoomWindow) Then Return False - ; Check if host menu is already open +; Attempts to locate the Host Tools container in either legacy flyout or new side panel layouts. +; @return Object - Host tools container scope or 0 +Func _FindHostToolsContainer() + If Not IsObj($oZoomWindow) Then Return 0 + + ; Legacy popup/flyout menu Local $oHostMenu = FindElementByClassName("WCN_ModelessWnd", Default, $oZoomWindow) - If Not IsObj($oHostMenu) Then + If IsObj($oHostMenu) Then Return $oHostMenu - ; Controls might be hidden, show them by moving the mouse - Debug("Controls might be hidden. Moving mouse to show controls.", "VERBOSE") - Debug("Getting Zoom window.", "VERBOSE") - If Not _GetZoomWindow() Then Return False + ; New host tools panel often appears as Pane/Group/Window labeled like Host Tools + Local $aPanelTypes[3] = [$UIA_PaneControlTypeId, $UIA_GroupControlTypeId, $UIA_WindowControlTypeId] + Local $oHostPanel = FindElementByPartialName(GetUserSetting("HostToolsValue"), $aPanelTypes, $oZoomWindow) + If IsObj($oHostPanel) Then Return $oHostPanel - Debug("Moving mouse to start of element: 'Zoom window'", "VERBOSE") - _MoveMouseToStartOfElement($oZoomWindow, True) - Debug("Clicking Host Tools button.", "VERBOSE") + Return 0 +EndFunc ;==>_FindHostToolsContainer - ; Menu not open, find and click the Host Tools button - Local $oHostToolsButton = FindElementByPartialName(GetUserSetting("HostToolsValue"), Default, $oZoomWindow) +; Opens the Host Tools menu in Zoom +; @return Object - Host menu object or False if failed +Func _OpenHostTools() + If Not IsObj($oZoomWindow) Then Return False - ; Scenario 1: Try to find Host Tools button directly - If IsObj($oHostToolsButton) Then - Debug("Host Tools button found directly; clicking.", "VERBOSE") - If Not _ClickElement($oHostToolsButton) Then - Debug(t("ERROR_FAILED_CLICK_ELEMENT") & ": 'Host Tools button'", "ERROR") - Return False - Else - Sleep(500) - $oHostMenu = FindElementByClassName("WCN_ModelessWnd", Default, $oZoomWindow) - Return $oHostMenu - EndIf + ; Check if host tools are already open (legacy or new layout) + Local $oHostContainer = _FindHostToolsContainer() + If IsObj($oHostContainer) Then Return $oHostContainer + + ; Controls might be hidden, show them by moving the mouse + Debug("Controls might be hidden. Moving mouse to show controls.", "VERBOSE") + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return False + _MoveMouseToStartOfElement($oZoomWindow, True) + + ; First attempt: Host Tools button directly on toolbar + Local $aToolbarTypes[1] = [$UIA_ButtonControlTypeId] + Local $oHostToolsButton = FindElementByPartialName(GetUserSetting("HostToolsValue"), $aToolbarTypes, $oZoomWindow) + If IsObj($oHostToolsButton) Then + Debug("Host Tools button found directly; clicking.", "VERBOSE") + ; Force click works better for the main toolbar Host Tools button (btn_security) + If Not _ClickElement($oHostToolsButton, True) Then + Debug("Direct Host Tools click failed; trying More-menu fallback.", "WARN") Else - ; Scenario 2: Try to find More menu, then Host Tools - Debug("Host Tools button not found, looking for 'More' button.", "VERBOSE") - Local $oMoreMenu = GetMoreMenu() - If IsObj($oMoreMenu) Then - ; Now look for the Host Tools button in the More menu - Local $oHostToolsMenuItem = FindElementByPartialName(GetUserSetting("HostToolsValue"), Default, $oMoreMenu) - If IsObj($oHostToolsMenuItem) Then - Debug("Found Host Tools menu item. Hovering it to open submenu.", "VERBOSE") - If _HoverElement($oHostToolsMenuItem, 500) Then - ; Return the now-open host menu - $oHostMenu = FindElementByClassName("WCN_ModelessWnd", Default, $oZoomWindow) - Return $oHostMenu - Else - Debug(t("ERROR_FAILED_CLICK_ELEMENT", GetUserSetting("HostToolsValue")), "ERROR") - Return False - EndIf - Else - Debug(t("ERROR_FAILED_CLICK_ELEMENT", GetUserSetting("HostToolsValue")), "ERROR") - Return False - EndIf - Else - Debug(t("ERROR_FAILED_CLICK_ELEMENT", GetUserSetting("MoreMeetingControlsValue")), "ERROR") - Return False + Sleep(700) + $oHostContainer = _FindHostToolsContainer() + If IsObj($oHostContainer) Then Return $oHostContainer + Debug("Direct Host Tools click did not open a Host Tools container; trying More-menu fallback.", "VERBOSE") + EndIf + EndIf + + ; Second attempt: open More menu then Host Tools + Debug("Host Tools button not found/active, looking for 'More' button.", "VERBOSE") + Local $oMoreMenu = GetMoreMenu() + Local $aMoreMenuTypes[3] = [$UIA_TabItemControlTypeId, $UIA_ButtonControlTypeId, $UIA_MenuItemControlTypeId] + If IsObj($oMoreMenu) Then + ; In newer Zoom builds this is often a TabItem with a long localized name. + Local $oHostToolsMenuItem = FindElementByPartialName(GetUserSetting("HostToolsValue"), $aMoreMenuTypes, $oMoreMenu) + If IsObj($oHostToolsMenuItem) Then + Debug("Found Host Tools item in More. Clicking it.", "VERBOSE") + If Not _ClickElement($oHostToolsMenuItem, True) Then + ; some builds require hover first then click + _HoverElement($oHostToolsMenuItem, 400) + _MoveMouseToStartOfElement($oHostToolsMenuItem, True) EndIf + Sleep(700) + $oHostContainer = _FindHostToolsContainer() + If IsObj($oHostContainer) Then Return $oHostContainer + EndIf + Else + ; Fallback: some layouts expose More content in a non-modeless window tree. + Local $oHostToolsFromZoom = FindElementByPartialName(GetUserSetting("HostToolsValue"), $aMoreMenuTypes, $oZoomWindow) + If IsObj($oHostToolsFromZoom) Then + Debug("Found Host Tools item from Zoom window tree fallback. Clicking it.", "VERBOSE") + _ClickElement($oHostToolsFromZoom, True) + Sleep(700) + $oHostContainer = _FindHostToolsContainer() + If IsObj($oHostContainer) Then Return $oHostContainer EndIf EndIf - ; Return the now-open host menu - Return $oHostMenu + Debug(t("ERROR_FAILED_CLICK_ELEMENT", GetUserSetting("HostToolsValue")), "ERROR") + Return False EndFunc ;==>_OpenHostTools ; Internal function to find Host menu (used by cache) Func _FindHostMenuInternal() - Return FindElementByClassName("WCN_ModelessWnd", Default, $oZoomWindow) + Return _FindHostToolsContainer() EndFunc ;==>_FindHostMenuInternal ; Closes the Host Tools menu by clicking on the main window diff --git a/Includes/ZoomPathEngine.au3 b/Includes/ZoomPathEngine.au3 new file mode 100644 index 0000000..b2ff8a0 --- /dev/null +++ b/Includes/ZoomPathEngine.au3 @@ -0,0 +1,90 @@ +#include-once +; ================================================================================================ +; ZOOM PATH ENGINE - Deterministic, reusable UI navigation paths +; ================================================================================================ + +#include "Globals.au3" +#include "Utils.au3" +#include "UserSettings.au3" +#include "UIAutomation.au3" +#include "ElementActions.au3" +#include "ZoomOperations.au3" + +Global $g_LastPathError = "" + +Func _SetPathError($sMessage) + $g_LastPathError = $sMessage + ReportUserFacingError($sMessage) + Debug($sMessage, "ERROR", True) + Return False +EndFunc ;==>_SetPathError + +Func _GetPathError() + Return $g_LastPathError +EndFunc ;==>_GetPathError + +; Ensure Zoom main meeting window exists and is focused. +Func EnsureZoomMainWindow() + $g_LastPathError = "" + ; Resolve once, then reuse same object for focus to avoid double-fetch timing issues. + Local $oResolvedZoomWindow = _GetZoomWindow() + If Not IsObj($oResolvedZoomWindow) Then Return _SetPathError("Zoom main window not found.") + If Not FocusZoomWindow($oResolvedZoomWindow) Then Return _SetPathError("Unable to focus Zoom main window.") + Return True +EndFunc ;==>EnsureZoomMainWindow + +; Ensure More menu is visible and return container object. +Func EnsureMoreMenuVisible() + If Not EnsureZoomMainWindow() Then Return 0 + Local $oMoreMenu = GetMoreMenu() + If Not IsObj($oMoreMenu) Then + _SetPathError("Unable to open Zoom More menu.") + Return 0 + EndIf + Return $oMoreMenu +EndFunc ;==>EnsureMoreMenuVisible + +; Ensure Host Tools container is visible and return container. +Func EnsureHostToolsVisible() + If Not EnsureZoomMainWindow() Then Return 0 + Local $oHostTools = _OpenHostTools() + If Not IsObj($oHostTools) Then + _SetPathError("Unable to open Host Tools panel/menu.") + Return 0 + EndIf + Return $oHostTools +EndFunc ;==>EnsureHostToolsVisible + +; Ensure Participants section inside Host Tools is visible for security toggles. +Func EnsureHostToolsParticipantsScope() + Local $oHostTools = EnsureHostToolsVisible() + If Not IsObj($oHostTools) Then Return 0 + + Local $oParticipants = FindElementByPartialName(GetUserSetting("ParticipantValue"), Default, $oHostTools) + If IsObj($oParticipants) Then + _ClickElement($oParticipants, True) + Sleep(350) + EndIf + + ; Return host-tools scope regardless, caller can verify specific toggle presence + Return $oHostTools +EndFunc ;==>EnsureHostToolsParticipantsScope + +; Ensure a specific security toggle is discoverable before any action. +Func EnsureSecurityToggleVisible($sSetting) + Local $oScope = EnsureHostToolsParticipantsScope() + If Not IsObj($oScope) Then Return 0 + + Local $aSecurityTypes[4] = [$UIA_CheckBoxControlTypeId, $UIA_ButtonControlTypeId, $UIA_MenuItemControlTypeId, $UIA_TextControlTypeId] + Local $oSetting = FindElementByPartialName($sSetting, $aSecurityTypes, $oScope) + If Not IsObj($oSetting) Then + $oSetting = FindElementByPartialName($sSetting, $aSecurityTypes, $oZoomWindow) + EndIf + + If Not IsObj($oSetting) Then + _SetPathError("Security toggle not found: " & $sSetting) + Return 0 + EndIf + + Return $oSetting +EndFunc ;==>EnsureSecurityToggleVisible diff --git a/Includes/i18n.au3 b/Includes/i18n.au3 index ed547f3..2495c5b 100644 --- a/Includes/i18n.au3 +++ b/Includes/i18n.au3 @@ -12,18 +12,29 @@ #include "ru.au3" #include "uk.au3" -Global Const $TRANSLATIONS = ObjCreate("Scripting.Dictionary") +Global $TRANSLATIONS = 0 ; Initialize translations for each language _InitializeTranslations() Func _InitializeTranslations() - ; Add each language's translations to the main dictionary + ; Create dictionary lazily and make initialization idempotent. + If Not IsObj($TRANSLATIONS) Then + $TRANSLATIONS = ObjCreate("Scripting.Dictionary") + If Not IsObj($TRANSLATIONS) Then + Return SetError(1, 0, 0) + EndIf + EndIf + + ; Avoid duplicate-key runtime errors when this function is called more than once. + If $TRANSLATIONS.Exists("en") Then Return True + $TRANSLATIONS.Add("en", $TRANSLATIONS_EN) $TRANSLATIONS.Add("es", $TRANSLATIONS_ES) $TRANSLATIONS.Add("fr", $TRANSLATIONS_FR) $TRANSLATIONS.Add("ru", $TRANSLATIONS_RU) $TRANSLATIONS.Add("uk", $TRANSLATIONS_UK) + Return True EndFunc ;==>_InitializeTranslations ; Helper function to get translations for a specific language @@ -31,7 +42,7 @@ EndFunc ;==>_InitializeTranslations ; @return Object - Dictionary containing translations for the specified language Func _GetLanguageTranslations($langCode) ; Ensure translations are initialized - If $TRANSLATIONS.Count = 0 Then _InitializeTranslations() + If (Not IsObj($TRANSLATIONS)) Or $TRANSLATIONS.Count = 0 Then _InitializeTranslations() If $TRANSLATIONS.Exists($langCode) Then Return $TRANSLATIONS.Item($langCode) @@ -46,12 +57,12 @@ EndFunc ;==>_GetLanguageTranslations Func _ListAvailableLanguageNames() Local $list = "" ; Ensure translations are initialized - If $TRANSLATIONS.Count = 0 Then _InitializeTranslations() + If (Not IsObj($TRANSLATIONS)) Or $TRANSLATIONS.Count = 0 Then _InitializeTranslations() For $langCode In $TRANSLATIONS.Keys - Local $TRANSLATIONS = _GetLanguageTranslations($langCode) - If $TRANSLATIONS.Exists("LANGNAME") Then - Local $langName = $TRANSLATIONS.Item("LANGNAME") + Local $oLangTranslations = _GetLanguageTranslations($langCode) + If $oLangTranslations.Exists("LANGNAME") Then + Local $langName = $oLangTranslations.Item("LANGNAME") $list &= ($list = "" ? $langName : "|" & $langName) EndIf Next @@ -63,11 +74,11 @@ EndFunc ;==>_ListAvailableLanguageNames ; @return String - Language code or empty string if not found Func _GetLanguageCodeFromDisplayName($displayName) ; Ensure translations are initialized - If $TRANSLATIONS.Count = 0 Then _InitializeTranslations() + If (Not IsObj($TRANSLATIONS)) Or $TRANSLATIONS.Count = 0 Then _InitializeTranslations() For $langCode In $TRANSLATIONS.Keys - Local $TRANSLATIONS = _GetLanguageTranslations($langCode) - If $TRANSLATIONS.Exists("LANGNAME") And $TRANSLATIONS.Item("LANGNAME") = $displayName Then + Local $oLangTranslations = _GetLanguageTranslations($langCode) + If $oLangTranslations.Exists("LANGNAME") And $oLangTranslations.Item("LANGNAME") = $displayName Then Return $langCode EndIf Next @@ -78,9 +89,9 @@ EndFunc ;==>_GetLanguageCodeFromDisplayName ; @param $code - Language code (e.g., "en", "es") ; @return String - Display name or the code itself if not found Func _GetLanguageDisplayName($code) - Local $TRANSLATIONS = _GetLanguageTranslations($code) - If $TRANSLATIONS.Exists("LANGNAME") Then - Return $TRANSLATIONS.Item("LANGNAME") + Local $oLangTranslations = _GetLanguageTranslations($code) + If $oLangTranslations.Exists("LANGNAME") Then + Return $oLangTranslations.Item("LANGNAME") EndIf Return $code EndFunc ;==>_GetLanguageDisplayName @@ -95,10 +106,10 @@ Func t($key, $p0 = Default, $p1 = Default, $p2 = Default) If $currentLang = "" Then $currentLang = "en" ; Get translations for the current language - Local $TRANSLATIONS = _GetLanguageTranslations($currentLang) + Local $oLangTranslations = _GetLanguageTranslations($currentLang) - If $TRANSLATIONS.Exists($key) Then - Local $s = $TRANSLATIONS.Item($key) + If $oLangTranslations.Exists($key) Then + Local $s = $oLangTranslations.Item($key) ; Replace placeholders if provided If $p0 <> Default Then $s = StringReplace($s, "{0}", $p0, 0, $STR_CASESENSE) If $p1 <> Default Then $s = StringReplace($s, "{1}", $p1, 0, $STR_CASESENSE) diff --git a/README.md b/README.md index 4b0f054..8d0205d 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,37 @@ Once configured, ZoomMate runs automatically in the background: - Manages meeting controls throughout the session - Access the tray icon anytime to modify settings or view status +### Electron Integration (shortcut-driven scenes) + +You can trigger deterministic automation scenes from your Electron app by launching ZoomMate with a scene argument: + +```bash +ZoomMate.exe --scene prepost +ZoomMate.exe --scene prestart +``` + +- `prepost`: Intended for before meeting start and after meeting end. Applies host audio/video off, allows participant unmute, disables screen share, and attempts gallery view. +- `prestart`: Intended for just before the meeting starts. Applies mute-all, prevents participant unmute, host audio/video on, opens participants panel, snaps window to configured side, and attempts gallery view. + The script is designed to be unobtrusive and requires minimal interaction once initially configured. + + +## Reliability & Future-Proofing Toolkit + +ZoomMate now includes a deterministic path engine and diagnostics helpers so UI changes in Zoom can be handled faster: + +- **Path engine**: each action drills through required parent paths first (Zoom window -> More -> Host Tools -> Participants -> setting). +- **User-visible failures**: if a required panel/toggle cannot be found, ZoomMate reports the error in GUI/tray and stops that task. +- **UI Diagnostics button**: captures discoverable Zoom element names into `zoom_ui_diagnostics.txt`. +- **Path Wizard button**: lets operators re-capture and store key path labels (More, Host Tools, Participants) after Zoom UI updates. + + +### State Profiler Wizard + +Use **State Profiler** in the settings GUI to guide Zoom through key states and automatically capture signatures for each state. + +Captured outputs: +- `zoom_state_profiles.ini` (state flags) +- `zoom_state_profiles.txt` (visible named elements by control type) + +This is intended to make future checks deterministic (for example: detect if Participants panel is already open before trying to open it again). diff --git a/ZoomMate.au3 b/ZoomMate.au3 index 918fc26..9b90ccf 100644 --- a/ZoomMate.au3 +++ b/ZoomMate.au3 @@ -56,6 +56,8 @@ Global Const $MF_BYCOMMAND = 0x00000000 #include "Includes\UIAutomation.au3" #include "Includes\ElementActions.au3" #include "Includes\ZoomOperations.au3" +#include "Includes\ZoomPathEngine.au3" +#include "Includes\StateProfiler.au3" #include "Includes\KeyboardShortcuts.au3" #include "Includes\Settings.au3" #include "Includes\MeetingAutomation.au3" @@ -66,6 +68,7 @@ Global Const $MF_BYCOMMAND = 0x00000000 ; Sets up tray icon and handles tray events TraySetIcon($g_TrayIcon) +TraySetState($TRAY_ICONSTATE_SHOW) ; ================================================================================================ ; UIAUTOMATION COM INITIALIZATION @@ -104,6 +107,23 @@ _InitDayLabelMaps() ; FocusZoomWindow() ; _SetDuringMeetingSettings() + +; ================================================================================================ +; COMMAND-LINE SCENE TRIGGERING (for Electron integration) +; ================================================================================================ +; Usage examples: +; ZoomMate.exe --scene prepost +; ZoomMate.exe --scene prestart +If $CmdLine[0] >= 2 And StringLower($CmdLine[1]) = "--scene" Then + Debug("Command-line scene requested: " & $CmdLine[2], "INFO") + If RunAutomationScene($CmdLine[2]) Then + Exit + Else + Debug("Scene execution failed or scene name invalid: " & $CmdLine[2], "ERROR") + Exit 1 + EndIf +EndIf + ; ================================================================================================ ; MAIN APPLICATION LOOP ; ================================================================================================