I have shared in a previous post how to run Teams Meeting Reactions with a hotkey or command with AutoHotkey; this solution was based on ImageSearch/ FindText. I present here an improved solution based on UIAutomation.
Screencast
Challenge
One main hurdle I had to struggle with, is that the UI Elements in Teams are not always accessible.
Example if the Reactions toolbar is visible, other UI Elements like the Reactions button are not accessible.
Sometimes the Reactions Toolbar button isn't accessible by its AutomationId (reaction-menu-button).
I try to find the Reactions button by its position right to the Chat button (AutomationId=chat-button) or two buttons right to the roster(people button (AutomationId=roster-button)
If is doesn't work I try by locating the Controls Meeting Toolbar (Credit for the coding idea to Descolada)
Using UIA.ElementFromPoint makes the element accessible by its Id but returns sometimes the parent Window Element.
So after
ReactionsEl := UIA.ElementFromPoint(BR.r+(BR.r-BR.l)//2, BR.t+20) ; Reactions button after chat
I do:
ReactionsEl := TeamsEl.FindFirstBy("AutomationId=reaction-menu-button") ; Sometimes Element is made accessible by ElementFromPoint but returns only parent window element to be sure I have the right element.
Once the Reactions images are displayed, it is easy to access the single reactions by the Element name or AutomationId. Example: For Like the AutomationId is 'like-button' and the name 'Like'. For Love the AutomationId is 'heart-button' and the name is 'Love'.
Code
It is included in my AHK Teams Library - function Teams_MeetingReactions
You can also find it in this Gist:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Teams_MeetingReaction(Reaction) { | |
; Reaction can be Like | Applause| Love | Laugh | Surprised | |
; See documentation https://tdalon.blogspot.com/2022/07/ahk-teams-meeting-reactions-uia.html | |
WinId := Teams_GetMeetingWindow() | |
If !WinId ; empty | |
return | |
WinGet, curWinId, ID, A | |
UIA := UIA_Interface() | |
TeamsEl := UIA.ElementFromHandle(WinId) | |
; Shortcut if Reactions toolbar already available-> directly click and exit | |
ReactionEl := TeamsEl.FindFirstByName(Reaction) | |
If ReactionEl | |
Goto, React | |
ReactionsEl := TeamsEl.FindFirstBy("AutomationId=reaction-menu-button") | |
If ReactionsEl | |
Goto ClickReactions | |
; Click by position | |
WinActivate, ahk_id %WinId% ; needs to be activated for UIA.ElementFromPoint to work | |
;MsgBox % TeamsEl.DumpAll() | |
; Look for Chat button | |
BtnEl := TeamsEl.FindFirstBy("AutomationId=chat-button") | |
If BtnEl { | |
BR := BtnEl.CurrentBoundingRectangle | |
ReactionsEl := UIA.ElementFromPoint(BR.r+(BR.r-BR.l)//2, BR.t+20) ; Reactions button after chat | |
ReactionsEl := TeamsEl.FindFirstBy("AutomationId=reaction-menu-button") ; Sometimes Element is made accessible by ElementFromPoint but returns only parent window element | |
If ReactionsEl | |
Goto ClickReactions | |
} | |
; Look for Roster/People button | |
BtnEl := TeamsEl.FindFirstBy("AutomationId=roster-button") | |
If BtnEl { | |
BR := BtnEl.CurrentBoundingRectangle | |
ReactionsEl := UIA.ElementFromPoint(BR.r+3*(BR.r-BR.l)//2, BR.t+20) ; Reactions button after chat after roster | |
ReactionsEl := TeamsEl.FindFirstBy("AutomationId=reaction-menu-button") ; Sometimes Element is made accessible by ElementFromPoint but returns only parent window element | |
If ReactionsEl | |
Goto ClickReactions | |
} | |
; Look for Controls meeting Element | |
ControlsEl := TeamsEl.FindFirstByName("Meeting controls") | |
If ControlsEl { ; Meeting controls not accessible | |
br := ControlsEl.CurrentBoundingRectangle | |
btnwidth := (br.r-br.l)//10 ; there are 10 buttons in the Meeting controls block | |
ReactionsEl := UIA.ElementFromPoint(br.l+2.5*btnwidth, br.t+20) ; Reactions button is on 3rd position | |
ReactionsEl := TeamsEl.FindFirstBy("AutomationId=reaction-menu-button") ; Sometimes Element is made accessible by ElementFromPoint but returns only parent window element | |
If ReactionsEl | |
Goto ClickReactions | |
} | |
;PowerTools_ErrDlg("Meeting Reactions button not found by Id nor position!") | |
TrayTip TeamsShortcuts: ERROR, Meeting Reactions button not found by Id nor position!,,0x2 | |
return | |
;MsgBox % ReactionsEl.DumpAll() ; DBG | |
ClickReactions: | |
ReactionsEl.Click() ; Click element without moving the mouse | |
ReactionEl:=TeamsEl.WaitElementExistByName(Reaction,,,,2000) ; timeout=2s | |
If !ReactionEl { | |
TrayTip TeamsShortcuts: ERROR, Meeting Reaction button for '%Reaction%'' not found!,,0x2 | |
;MsgBox % ReactionsEl.DumpAll() ; DBG | |
return | |
} | |
React: | |
ReactionEl.Click() | |
Tooltip("Teams Meeting Reaction: " . Reaction,1000) | |
;SendInput {Esc} ; Escape Reactions Menu | |
; Restore previous window | |
WinActivate, ahk_id %curWinId% | |
} ; eofun | |
; ------------------------------------------------------------------------------------------------------------------- | |
Teams_Click(Id) { | |
If (ok := Teams_FindText(Id)) | |
{ | |
CoordMode, Mouse | |
X:=ok.1.x, Y:=ok.1.y, Comment:=ok.1.id | |
Click, %X%, %Y% | |
} | |
return ok | |
} ; eofun |
Clicking on the Reaction will activate the Teams Meeting Window.
In the code the previously active window is restored at the end.
A tooltip is quickly displayed at the mouse position to confirm the Reaction was triggered.
The mouse position won't be changed (this is another advantage compared to the FindText previous approach.)
Usage
You can also run this from the Teams/y Launcher included with the TeamsShortcuts PowerTool.
See screencast that demonstrates how it works.