--------------------------------------------------------------- -- Secure probes for signed code --------------------------------------------------------------- -- Probes are used to trigger callbacks to different handles -- when an event happens that normally can't be processed in a -- secure scope. A probed object needs to be signed or secure. -- Probed objects will become inherently secure after probing. local _, db = ...; local Probe, ProbeMixin, _probeID = db:Register('Probe', {}), {}, 1; local SCRIPT, TEMPLATES --- Create a new secure probe. -- @param owner : Owner of the probe. The owner will be shown/hidden in response to the probe. -- @param object : Object to probe. Use only on secure addon objects or signed (official) objects. -- @param state : State to which the probe should respond. -- @param name : Name of the probe. Can safely be omitted. -- @param script : Script to fire when using probescript template. Requires a secure environment. -- @return probe : Returns the created probe. Generally not useful, unless the probe will be re-cycled. function Probe:CreateProbe(owner, object, state, name, script) assert(object ~= nil and owner ~= nil and state ~= nil, 'Usage: Probe:CreateProbe(object, owner, state)') assert(not InCombatLockdown(), 'Probe cannot be created in combat.') if type(object) == 'string' then object = _G[object] end assert(C_Widget.IsFrameWidget(object), 'Probed object is not a valid frame.') local probe = CreateFrame('Frame', name or ('%sProbe%s'):format(_, _probeID), object, 'SecureHandlerShowHideTemplate, SecureHandlerEnterLeaveTemplate') Mixin(probe, ProbeMixin) probe:SetType(state) probe:SetOwner(owner) probe:SetObject(object) probe:SetResponseScript(script) _probeID = _probeID + 1 return probe end --- Show/Hide hook wrapper (insecure) -- @param owner : Responding frame. Will be shown/hidden in response to the target object. -- @param object : Object to hook. function Probe:InsecureHookShowHide(owner, object) assert(object ~= nil and owner ~= nil, 'Usage: Probe:InsecureHookShowHide(object, owner)') if type(object) == 'string' then object = _G[object] end assert(type(object) == 'table', 'Probed object is not a valid frame.') object:HookScript('OnShow', function() owner:Show() end) object:HookScript('OnHide', function() owner:Hide() end) owner:SetShown(object:IsShown()) end function Probe:HideFrame(frame, ignoreAlpha) assert(frame, 'Usage: Probe:HideFrame(frame)') frame:SetSize(0, 0) frame:EnableMouse(false) frame:EnableKeyboard(false) frame:SetAlpha(ignoreAlpha and frame:GetAlpha() or 0) frame:ClearAllPoints() frame:SetAttribute('nodeignore', true) end --------------------------------------------------------------- -- Probe object mixin --------------------------------------------------------------- function ProbeMixin:SetType(pType) local template = TEMPLATES[pType] assert(template, 'Usage: Probe:CreateProbe(object, owner, state): Supported states: showhide, enterleave, all') for script in pairs(TEMPLATES.all) do self:SetAttribute(script, nil) end for script, snippet in pairs(template) do self:SetAttribute(script, snippet) end end function ProbeMixin:SetOwner(new) assert(not InCombatLockdown(), (self:GetName() or 'Probe')..' cannot be repurposed in combat.') self:SetFrameRef('owner', new) self:Execute([[owner = self:GetFrameRef('owner')]]) end function ProbeMixin:SetObject(new) assert(not InCombatLockdown(), (self:GetName() or 'Probe')..' cannot be relocated in combat.') self:SetParent(new) end function ProbeMixin:SetResponseScript(script) if type(script) == 'string' and loadstring(script) then self:Execute( ("_cb=[[%s]]"):format(script) ) end end function ProbeMixin:DebugConnection() assert(not InCombatLockdown(), 'Cannot debug probe connection in combat.') self:Execute([[ local probedObject = self:GetParent() print('Probe name:', self:GetName() or '') print('Probed state:', owner:GetAttribute('pc') or 0) print('Probed object:', (not probedObject and '') or (probedObject:GetName() or '')) print('Owner of probe:', owner:GetName() or '') print('Owner probe script:', owner:GetAttribute('_onprobecount'))]]) end ---------------------------- SCRIPT = { show = [[local _=(owner:GetAttribute('pc') or 0)+1 owner:SetAttribute('pc',_) owner:Hide() owner:Show()]], hide = [[local _=(owner:GetAttribute('pc') or 1)-1 owner:SetAttribute('pc',_) if _ < 1 then owner:Hide() end]], pcsh = [[local _=(owner:GetAttribute('pc') or 0)+1 owner:SetAttribute('pc',_) if _cb then owner:Run(_cb,_) end]], pchi = [[local _=(owner:GetAttribute('pc') or 1)-1 owner:SetAttribute('pc',_) if _cb then owner:Run(_cb,_) end]], omit = [[return nil]], } ---------------------------- TEMPLATES = { all = { _onenter = SCRIPT.show, _onleave = SCRIPT.hide, _onshow = SCRIPT.show, _onhide = SCRIPT.hide, }, enterleave = { _onenter = SCRIPT.show, _onleave = SCRIPT.hide, }, showhide = { _onshow = SCRIPT.show, _onhide = SCRIPT.hide, }, probescript = { _onshow = SCRIPT.pcsh, _onhide = SCRIPT.pchi, }, ------------------------ invertall = { _onenter = SCRIPT.hide, _onleave = SCRIPT.show, _onshow = SCRIPT.hide, _onhide = SCRIPT.show, }, invertenterleave = { _onenter = SCRIPT.hide, _onleave = SCRIPT.show, }, invertshowhide = { _onshow = SCRIPT.hide, _onhide = SCRIPT.show, }, ------------------------ omit = { _onshow = SCRIPT.omit, _onhide = SCRIPT.omit, }, } ---------------------------- LibStub:GetLibrary('Carpenter'):ExtendAPI('Probe', function(self, ...) self:SetAttribute('probe', Probe:CreateProbe(self, ...)) end)