Skip to content

Commit ce796b2

Browse files
committed
Fully reworked the virtual clicking logic, to make them all secure clicks
And reworked the handling of Gossip/Dialog UI to avoid possible issues with quest items
1 parent 9bf758c commit ce796b2

File tree

1 file changed

+56
-154
lines changed

1 file changed

+56
-154
lines changed

main.lua

Lines changed: 56 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@ _G.DialogKeyNS = ns -- expose ourselves to the world :)
1111
local DialogKey = LibStub("AceAddon-3.0"):NewAddon(name, "AceEvent-3.0", "AceHook-3.0")
1212
ns.Core = DialogKey
1313

14-
local defaultPopupBlacklist = { -- If a confirmation dialog contains one of these strings, don't accept it
15-
--"Are you sure you want to go back to Shal'Aran?", -- Withered Training Scenario -- why exclude this?
16-
--"Are you sure you want to return to your current timeline?", -- Leave Chromie Time -- why exclude this?
17-
--"You will be removed from Timewalking Campaigns once you use this scroll.", -- "A New Adventure Awaits" Chromie Time scroll -- why exclude this?
14+
local defaultPopupBlacklist = { -- If a popup dialog contains one of these strings, don't click it
1815
AREA_SPIRIT_HEAL, -- Prevents cancelling the resurrection
1916
TOO_MANY_LUA_ERRORS,
20-
END_BOUND_TRADEABLE,
21-
ADDON_ACTION_FORBIDDEN,
17+
END_BOUND_TRADEABLE, -- Probably quite reasonable to make the user click on this one
18+
ADDON_ACTION_FORBIDDEN, -- Don't disable and reload UI on errors
2219
}
2320

2421
local function callFrameMethod(frame, method, ...)
@@ -46,11 +43,6 @@ function DialogKey:GetFrameByName(frameName)
4643
end
4744

4845
DialogKey.playerChoiceButtons = {}
49-
DialogKey.handledCustomFrames = {}
50-
DialogKey.ignoreCheckCustomFrames = false
51-
DialogKey.proxyFrames = {}
52-
DialogKey.proxyFrameNamePrefix = "DialogKeyNumy_ProxyFrame_"
53-
DialogKey.proxyFrameIndex = 0
5446
DialogKey.activeOverrideBindings = {}
5547

5648
function DialogKey:OnInitialize()
@@ -68,19 +60,7 @@ function DialogKey:OnInitialize()
6860
self:RegisterEvent("PLAYER_REGEN_DISABLED")
6961
self:RegisterEvent("ADDON_LOADED")
7062

71-
self:SecureHook("CreateFrame", "CheckCustomFrames")
72-
73-
self.frame = CreateFrame("Frame", nil, UIParent)
74-
self.frame:SetScript("OnKeyDown", function(_, ...) self:HandleKey(...) end)
75-
self.frame:SetFrameStrata("TOOLTIP") -- Ensure we receive keyboard events first
76-
self.frame:EnableKeyboard(true)
77-
self.frame:SetPropagateKeyboardInput(true)
78-
79-
for i = 1, 4 do
80-
self:SecureHookScript(_G["StaticPopup" .. i], "OnShow", "OnPopupShow")
81-
self:SecureHookScript(_G["StaticPopup" .. i], "OnUpdate", "OnPopupUpdate")
82-
self:SecureHookScript(_G["StaticPopup" .. i], "OnHide", "OnPopupHide")
83-
end
63+
self:InitMainProxyFrame()
8464

8565
self:SecureHook("QuestInfoItem_OnClick", "SelectItemReward")
8666
self:SecureHook(GossipFrame, "Update", "OnGossipFrameUpdate")
@@ -109,7 +89,6 @@ function DialogKey:ADDON_LOADED(_, addon)
10989
self:SecureHook(PlayerChoiceFrame, "TryShow", "OnPlayerChoiceShow")
11090
self:SecureHookScript(PlayerChoiceFrame, "OnHide", "OnPlayerChoiceHide")
11191
end
112-
self:CheckCustomFrames()
11392
end
11493

11594
function DialogKey:QUEST_COMPLETE()
@@ -142,6 +121,31 @@ function DialogKey:InitGlowFrame()
142121
self.glowFrame.tex:SetColorTexture(1,1,0,0.5)
143122
end
144123

124+
function DialogKey:InitMainProxyFrame()
125+
local frame = CreateFrame("Button", "DialogKey_Numy_MainClickProxyFrame", UIParent, "InsecureActionButtonTemplate")
126+
frame:RegisterForClicks("AnyUp", "AnyDown")
127+
frame:SetAttribute("type", "click")
128+
frame:SetAttribute("typerelease", "click")
129+
frame:SetAttribute("pressAndHoldAction", "1")
130+
frame:SetScript("PreClick", function()
131+
if InCombatLockdown() then return end
132+
self:ClearOverrideBindings(frame)
133+
local clickButton = frame:GetAttribute("clickbutton")
134+
self:Glow(clickButton)
135+
end)
136+
frame:HookScript("OnClick", function()
137+
if InCombatLockdown() then return end
138+
frame:SetAttribute("clickbutton", nil)
139+
frame:SetPropagateKeyboardInput(true)
140+
end)
141+
frame:SetScript("OnKeyDown", function(_, ...) self:HandleKey(...) end)
142+
frame:SetFrameStrata("TOOLTIP") -- Ensure we receive keyboard events first
143+
frame:EnableKeyboard(true)
144+
frame:SetPropagateKeyboardInput(true)
145+
146+
self.frame = frame
147+
end
148+
145149
function DialogKey:OnPlayerChoiceShow()
146150
if not self.db.handlePlayerChoice then return end
147151
local frame = PlayerChoiceFrame;
@@ -181,7 +185,7 @@ function DialogKey:OnGossipFrameUpdate(gossipFrame)
181185

182186
local n = 1
183187
for _, frame in scrollbox:EnumerateFrames() do
184-
local data = frame.GetElementData and frame.GetElementData()
188+
local data = frame.GetElementData and frame:GetElementData()
185189
local tag
186190
if data.buttonType == GOSSIP_BUTTON_TYPE_OPTION then
187191
tag = "name"
@@ -195,7 +199,9 @@ function DialogKey:OnGossipFrameUpdate(gossipFrame)
195199
end
196200
if n > 10 then break end
197201
end
198-
scrollbox:OnSizeChanged()
202+
local oldScale = scrollbox:GetScale()
203+
scrollbox:SetScale(oldScale + 0.002) -- trigger OnSizeChanged
204+
RunNextFrame(function() scrollbox:SetScale(oldScale) end) -- OnSizeChanged only fires if the size actually changed at the end of the frame
199205
end
200206

201207
--- @return StaticPopupTemplate|nil
@@ -244,7 +250,6 @@ end
244250

245251
function DialogKey:ShouldIgnoreInput()
246252
if InCombatLockdown() then return true end
247-
self.frame:SetPropagateKeyboardInput(true)
248253

249254
if self.db.ignoreWithModifier and (IsShiftKeyDown() or IsControlKeyDown() or IsAltKeyDown()) then return true end
250255
-- Ignore input while typing, unless at the Send Mail confirmation while typing into it!
@@ -358,165 +363,73 @@ function DialogKey:SetOverrideBindings(owner, targetName, keys)
358363
end
359364
end
360365

361-
function DialogKey:CheckCustomFrames()
362-
if self.ignoreCheckCustomFrames then return end
363-
for frameName, _ in pairs(self.db.customFrames) do
364-
local frame = self:GetFrameByName(frameName)
365-
if frame and not self.handledCustomFrames[frame] then
366-
self.handledCustomFrames[frame] = frameName
367-
self:SecureHookScript(frame, "OnShow", "OnCustomFrameShow")
368-
self:SecureHookScript(frame, "OnHide", "OnCustomFrameHide")
369-
if frame:IsVisible() then
370-
self:OnCustomFrameShow(frame)
371-
end
372-
end
373-
end
374-
end
375-
376-
--- @param frame Frame
377-
--- @return Button, string
378-
function DialogKey:AcquireProxyButton(frame)
379-
local proxyButton = self.proxyFrames[frame]
380-
if not proxyButton then
381-
self.proxyFrameIndex = self.proxyFrameIndex + 1
382-
local proxyName = self.proxyFrameNamePrefix .. self.proxyFrameIndex
383-
self.ignoreCheckCustomFrames = true
384-
proxyButton = CreateFrame("Button", proxyName, nil, "SecureActionButtonTemplate")
385-
self.ignoreCheckCustomFrames = false
386-
proxyButton:SetAttribute("type", "click")
387-
proxyButton:SetAttribute("typerelease", "click")
388-
proxyButton:SetAttribute("clickbutton", frame)
389-
proxyButton:RegisterForClicks("AnyUp", "AnyDown")
390-
proxyButton:SetAttribute("pressAndHoldAction", "1")
391-
proxyButton:HookScript("OnClick", function() self:Glow(frame) end)
392-
proxyButton.name = proxyName
393-
proxyButton.target = frame
394-
self.proxyFrames[frame] = proxyButton
395-
end
396-
return proxyButton, proxyButton.name
397-
end
398-
399-
function DialogKey:OnCustomFrameShow(frame)
400-
if InCombatLockdown() or not self.db.customFrames[self.handledCustomFrames[frame]] then return end
401-
402-
local proxyButton, proxyName = self:AcquireProxyButton(frame)
403-
404-
self:SetOverrideBindings(proxyButton, proxyName, self.db.keys)
405-
end
406-
407-
function DialogKey:OnCustomFrameHide(frame)
366+
function DialogKey:SetClickbuttonBinding(frame, key)
408367
if InCombatLockdown() then return end
368+
self.frame:SetAttribute("clickbutton", frame)
369+
self:SetOverrideBindings(self.frame, self.frame:GetName(), {key})
409370

410-
local proxyButton = self:AcquireProxyButton(frame)
411-
412-
self:ClearOverrideBindings(proxyButton)
413-
end
414-
415-
DialogKey.checkOnUpdate = {}
416-
--- @param popupFrame StaticPopupTemplate # One of the StaticPopup1-4 frames
417-
function DialogKey:OnPopupShow(popupFrame)
418-
-- Todo: consider supporting DialogKey.db.ignoreDisabledButtons option
419-
-- right now disabled buttons *are* clicked, but clicking them does nothing (although the key press is still eaten regardless)
420-
self.checkOnUpdate[popupFrame] = false
421-
-- only act if the popup is both visible, and the first visible one
422-
if InCombatLockdown() or popupFrame ~= self:GetFirstVisiblePopup() then return end
423-
424-
local button = self:GetPopupButton(popupFrame)
425-
self:ClearOverrideBindings(popupFrame)
426-
if button == false then
427-
-- false means that the text is empty, and we should check again OnUpdate, for the text to be filled
428-
self.checkOnUpdate[popupFrame] = true
429-
return
430-
end
431-
if not button then return end
432-
433-
self:SetOverrideBindings(popupFrame, button:GetName(), self.db.keys)
434-
end
435-
436-
--- @param popupFrame StaticPopupTemplate # One of the StaticPopup1-4 frames
437-
function DialogKey:OnPopupUpdate(popupFrame)
438-
if not self.checkOnUpdate[popupFrame] then return end
439-
440-
self:OnPopupShow(popupFrame)
441-
end
442-
443-
--- @param popupFrame StaticPopupTemplate # One of the StaticPopup1-4 frames
444-
function DialogKey:OnPopupHide(popupFrame)
445-
if InCombatLockdown() then return end
446-
447-
self:ClearOverrideBindings(popupFrame)
371+
-- just in case something goes horribly wrong, we do NOT want to get the user stuck in a situation where the keyboard stops working
372+
RunNextFrame(function() self:ClearOverrideBindings(self.frame) end)
448373
end
449374

450375
function DialogKey:HandleKey(key)
451-
if self:ShouldIgnoreInput() then return end
452-
376+
if not InCombatLockdown() then self.frame:SetPropagateKeyboardInput(true) end
453377
local doAction = (key == self.db.keys[1] or key == self.db.keys[2])
454378
local keynum = doAction and 1 or tonumber(key)
455379
if key == "0" then
456380
keynum = 10
457381
end
382+
if not doAction and not keynum then return end
383+
if self:ShouldIgnoreInput() then return end
458384
-- DialogKey pressed, interact with popups, accepts..
459385
if doAction then
460-
461-
-- Click Popup - the actual click is performed via OverrideBindings
386+
-- Popups
462387
local popupFrame = self:GetFirstVisiblePopup()
463388
local popupButton = popupFrame and self:GetPopupButton(popupFrame)
464389
if popupButton then
465-
self.frame:SetPropagateKeyboardInput(true)
466-
self:Glow(popupButton)
390+
self:SetClickbuttonBinding(popupButton, key)
467391
return
468392
end
469393

470394
-- Crafting Orders
471395
local craftingOrderFrame = self:GetFirstVisibleCraftingOrderFrame()
472396
if craftingOrderFrame then
473-
self.frame:SetPropagateKeyboardInput(false)
474-
self:Glow(craftingOrderFrame)
475-
craftingOrderFrame:Click()
397+
self:SetClickbuttonBinding(craftingOrderFrame, key)
476398
return
477399
end
478400

479401
-- Custom Frames
480402
local customFrame = self:GetFirstVisibleCustomFrame()
481403
if customFrame then
482-
self.frame:SetPropagateKeyboardInput(true)
404+
self:SetClickbuttonBinding(customFrame, key)
483405
return
484406
end
485407

486408
-- Auction House
487409
if self.db.postAuctions and AuctionHouseFrame and AuctionHouseFrame:IsVisible() then
488410
if AuctionHouseFrame.displayMode == AuctionHouseFrameDisplayMode.CommoditiesSell then
489-
self.frame:SetPropagateKeyboardInput(false)
490-
self:Glow(AuctionHouseFrame.CommoditiesSellFrame.PostButton)
491-
AuctionHouseFrame.CommoditiesSellFrame:PostItem()
411+
self:SetClickbuttonBinding(AuctionHouseFrame.CommoditiesSellFrame.PostButton, key)
492412
return
493413
elseif AuctionHouseFrame.displayMode == AuctionHouseFrameDisplayMode.ItemSell then
494-
self.frame:SetPropagateKeyboardInput(false)
495-
self:Glow(AuctionHouseFrame.ItemSellFrame.PostButton)
496-
AuctionHouseFrame.ItemSellFrame:PostItem()
414+
self:SetClickbuttonBinding(AuctionHouseFrame.ItemSellFrame.PostButton, key)
497415
return
498416
end
499417
end
500418

501419
-- Complete Quest
502420
if QuestFrameProgressPanel:IsVisible() then
503-
self.frame:SetPropagateKeyboardInput(false)
504421
if not QuestFrameCompleteButton:IsEnabled() and self.db.ignoreDisabledButtons then
505422
-- click "Cencel" button when "Complete" is disabled on progress panel
506-
self:Glow(QuestFrameGoodbyeButton)
507-
CloseQuest()
423+
self:SetClickbuttonBinding(QuestFrameGoodbyeButton, key)
508424
else
509-
self:Glow(QuestFrameCompleteButton)
510-
CompleteQuest()
425+
self:SetClickbuttonBinding(QuestFrameCompleteButton, key)
511426
end
512427
return
513428
-- Accept Quest
514429
elseif QuestFrameDetailPanel:IsVisible() then
515-
self.frame:SetPropagateKeyboardInput(false)
516-
self:Glow(QuestFrameAcceptButton)
517-
AcceptQuest()
430+
self:SetClickbuttonBinding(QuestFrameAcceptButton, key)
518431
return
519-
-- Take Quest Reward
432+
-- Take Quest Reward - using manual API
520433
elseif QuestFrameRewardPanel:IsVisible() then
521434
self.frame:SetPropagateKeyboardInput(false)
522435
if self.itemChoice == -1 and GetNumQuestChoices() > 1 then
@@ -533,9 +446,7 @@ function DialogKey:HandleKey(key)
533446
if self.db.handlePlayerChoice and next(self.playerChoiceButtons) and (doAction or self.db.numKeysForPlayerChoice) then
534447
local button = self.playerChoiceButtons[keynum]
535448
if button then
536-
self.frame:SetPropagateKeyboardInput(false)
537-
self:Glow(button)
538-
button:Click()
449+
self:SetClickbuttonBinding(button, key)
539450
return
540451
end
541452
end
@@ -548,9 +459,7 @@ function DialogKey:HandleKey(key)
548459
if doAction and choice and choice.info and choice.info.questID and choice.activeQuestButton and not choice.info.isComplete and self.db.ignoreDisabledButtons then
549460
keynum = keynum + 1
550461
else
551-
self.frame:SetPropagateKeyboardInput(false)
552-
self:Glow(self.frames[keynum])
553-
self.frames[keynum]:Click()
462+
self:SetClickbuttonBinding(self.frames[keynum], key)
554463
return
555464
end
556465
end
@@ -567,19 +476,17 @@ function DialogKey:HandleKey(key)
567476
keynum = 1
568477
end
569478
else
570-
self.frame:SetPropagateKeyboardInput(false)
571-
self:Glow(self.frames[keynum])
572-
self.frames[keynum]:Click()
479+
self:SetClickbuttonBinding(self.frames[keynum], key)
573480
return
574481
end
575482
end
576483
end
577484

578485
-- QuestReward Frame (select item)
579486
if self.db.numKeysForQuestRewards and keynum and keynum <= GetNumQuestChoices() and QuestFrameRewardPanel:IsVisible() then
580-
self.frame:SetPropagateKeyboardInput(false)
581487
self.itemChoice = keynum
582-
GetClickFrame("QuestInfoRewardsFrameQuestInfoItem" .. key):Click()
488+
self:SetClickbuttonBinding(GetClickFrame("QuestInfoRewardsFrameQuestInfoItem" .. key), key)
489+
return
583490
end
584491
end
585492

@@ -684,10 +591,6 @@ function DialogKey:AddFrame(frameName)
684591
self.db.customFrames[frameName] = true
685592
self:Glow(frame, 0.25, true)
686593
self:print("Added frame:", frameName, ". Remove it again with /dialogkey remove; or in the options UI.")
687-
self:CheckCustomFrames()
688-
if frame:IsVisible() then
689-
self:OnCustomFrameShow(frame)
690-
end
691594
end
692595

693596
function DialogKey:RemoveFrame(frameName)
@@ -706,7 +609,6 @@ function DialogKey:RemoveFrame(frameName)
706609
self.db.customFrames[frameName] = nil
707610
self:Glow(frame, 0.25, true)
708611
self:print("Removed frame:", frameName)
709-
self:OnCustomFrameHide(frame)
710612
end
711613

712614
--- Returns the first clickable frame that has mouse focus

0 commit comments

Comments
 (0)