View Single Post
12-06-17, 11:53 AM   #6
aallkkaa
A Warpwood Thunder Caller
 
aallkkaa's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2017
Posts: 98
Originally Posted by vis781 View Post
I will try your idea, thank you. In the mean time could you help me with understanding secure frames a bit better... with a snippet I can try? Preferably using the WrapScript method so I can start to understand what it would be used for.
I myself can't really help all that much (I don't understand it well enough yet), but a while ago I had a problem to solve for which MunkDev gave me a ready sollution, which showcases the use of securestatedriver() and restricted environment pretty well, I believe.
For context, my purpose was: when in an actionbar page other than 1, after clicking a button on said page, automatically switch back to page 1 (something you can do with a macro, for each button, like /cast <spell> /changeactionbar 1).
This is what I got from MunkDev:
Originally Posted by MunkDev View Post
I tinkered a bit more with this and you can actually do the same thing with just one button that uses a state driver, a wrapped PreClick and a custom identifier passed as the 'button' argument to the click script.

This solution only uses one secure button, does what you want, and the only drawback is that it won't trigger the first time if you're on a swap page when changing key bindings. This method also works when changing key bindings in combat.

Lua Code:
  1. local header = CreateFrame('Button', 'AllInOneActionButton', nil, 'SecureHandlerBaseTemplate, SecureHandlerStateTemplate, SecureActionButtonTemplate')
  2. header:RegisterForClicks('AnyUp', 'AnyDown') -- process both press and release of an arbitrary key
  3. header:SetAttribute('action', 1) -- this attribute will be used in conjunction with 'actionbar' to swap the bar back
  4.  
  5. do  -- generate a state driver condition with generic values to ensure any bar change pushes an update.
  6.     -- the actual bar ID check is done in the _onstate-actionpage function body instead.
  7.     -- this method seems to work regardless of API changes that cause
  8.     -- some of these macro conditions to shift around on certain specs.
  9.     local conditionBase, driver = '[%s] %d; ', ''
  10.     local conditions = {
  11.         ----------------------------------
  12.         'vehicleui', 'possessbar', 'overridebar', 'shapeshift',
  13.         'bar:2', 'bar:3', 'bar:4', 'bar:5', 'bar:6',
  14.         'bonusbar:1', 'bonusbar:2', 'bonusbar:3', 'bonusbar:4'
  15.         ----------------------------------
  16.     }
  17.     -- concatenate conditions into a long macro condition that can be
  18.     -- evaluated by the state driver and send updates to the header.
  19.     -- e.g. "[vehicleui] 1; [possessbar] 2; [overridebar] 3;" etc.
  20.     for i, macroCondition in ipairs(conditions) do
  21.         driver = driver .. conditionBase:format(macroCondition, i)
  22.     end
  23.     ----------------------------------
  24.     -- append the list for the default bar (1) when none of the conditions apply.
  25.     driver = driver .. (#conditions + 1)
  26.  
  27.     -- register the state driver to handle page updates
  28.     RegisterStateDriver(header, 'actionpage', driver)
  29. end
  30.  
  31. -- set up the _onstate-actionpage function body that sets the new actionpage ID.
  32. -- this is a rewrite of the response in ActionBarController_UpdateAll.
  33. -- state drivers use the _onstate-<name> attribute when looking for the function body to run.
  34. -- 'newstate' is the argument passed to all state drivers expressing the outcome
  35. -- of the registered condition, but in this case it's overwritten by the page calculation.
  36. -- note that all code in a restricted environment is written as strings and compiled in
  37. -- real time using loadstring(), to ensure you don't have access to unrestricted API.
  38. header:SetAttribute('_onstate-actionpage', [[
  39.     -- update actionpage
  40.     if HasVehicleActionBar() then
  41.         newstate = GetVehicleBarIndex()
  42.     elseif HasOverrideActionBar() then
  43.         newstate = GetOverrideBarIndex()
  44.     elseif HasTempShapeshiftActionBar() then
  45.         newstate = GetTempShapeshiftBarIndex()
  46.     elseif GetBonusBarOffset() > 0 then
  47.         newstate = GetBonusBarOffset() + 6
  48.     else
  49.         newstate = GetActionBarPage()
  50.     end
  51.     self:SetAttribute('actionpage', newstate)
  52.  
  53.     -- update bindings
  54.     self:ClearBindings()
  55.     for i=1, 12 do
  56.         local key = GetBindingKey('ACTIONBUTTON' .. i)
  57.         if key then
  58.             self:SetBindingClick(false, key, self, tostring(i))
  59.         end
  60.     end
  61. ]])
  62.  
  63. -- wrap the preclick script on the header
  64. -- use the inherent 'button' argument to tell the header
  65. -- which action button you want to simulate.
  66. -- since PreClick runs before OnClick, you're able to change
  67. -- what the button should do while you're pressing it.
  68. header:WrapScript(header, 'PreClick', [[
  69.     if down then
  70.         -- set up the downpress attributes to perform the action first
  71.         self:SetID(tonumber(button)) -- see ActionButton_CalculateAction
  72.         self:SetAttribute('type', 'action')
  73.     else
  74.         -- set the type to actionbar on release, which swaps the bar back
  75.         self:SetAttribute('type', 'actionbar')
  76.     end
  77.     self:CallMethod('OnButtonPressed', down, button)
  78. ]])
  79.  
  80. -- finally, execute the actionpage response on load,
  81. -- to set the correct action page and your override bindings.
  82. -- 'Execute' is included in the SecureHandlerBaseTemplate,
  83. -- and allows you to execute code in a restricted environment.
  84. header:Execute(header:GetAttribute('_onstate-actionpage'))
  85.  
  86. -- this function is called from the preclick script,
  87. -- which visually indicates a button press on your bar.
  88. -- this part isn't functionally necessary,
  89. -- it just makes it look like you're pressing regular buttons,
  90. -- when you're actually pressing your hidden button.
  91. function header:OnButtonPressed(down, id)
  92.     local button = GetActionButtonForID(id)
  93.     if button then
  94.         if down and button:GetButtonState() == 'NORMAL' then
  95.             button:SetButtonState('PUSHED')
  96.         elseif not down and button:GetButtonState() == 'PUSHED' then
  97.             button:SetButtonState('NORMAL')
  98.         end
  99.     end
  100. end
I have done some very minor tweaking to it since (the code above borks OverrideActionBar - easy to fix; I also tried printing to the UIErrorsFrame ( UIErrorsFrame:AddMessage() ) on which page I was on but that didn't work - works fine with print() to the chat frame though - global frames don't seem to "exist" in the restricted environment).
You can see a list of attributes to use in: https://wow.gamepedia.com/SecureActionButtonTemplate
MunkDev also linked a very helpful (albeit not enough for my lack of coding-skills ) free ebook about the restricted environment: Iriel's Field Guide to Secure Handlers

Hope that helps!
  Reply With Quote