vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 --[[
2 Name: AceHook-2.0
3 Revision: $Rev: 4597 $
4 Author(s): cladhaire (cladhaire@gmail.com)
5 ckknight (ckknight@gmail.com)
6 Inspired By: AceHook 1.x by Turan (<email here>)
7 Website: http://www.wowace.com/
8 Documentation: http://wiki.wowace.com/index.php/AceHook-2.0
9 SVN: http://svn.wowace.com/root/trunk/Ace2/AceHook-2.0
10 Description: Mixin to allow for safe hooking of functions, methods, and scripts.
11 Dependencies: AceLibrary, AceOO-2.0
12 ]]
13  
14 local MAJOR_VERSION = "AceHook-2.0"
15 local MINOR_VERSION = "$Revision: 4597 $"
16  
17 -- This ensures the code is only executed if the libary doesn't already exist, or is a newer version
18 if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
19 if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
20  
21 if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
22  
23 --[[---------------------------------------------------------------------------------
24 Create the library object
25 ----------------------------------------------------------------------------------]]
26  
27 local AceOO = AceLibrary:GetInstance("AceOO-2.0")
28 local AceHook = AceOO.Mixin {
29 "Hook",
30 "Unhook",
31 "UnhookAll",
32 "HookReport",
33 "IsHooked",
34 "HookScript",
35 }
36  
37 --[[---------------------------------------------------------------------------------
38 Library Definitions
39 ----------------------------------------------------------------------------------]]
40  
41 local protFuncs = {
42 CameraOrSelectOrMoveStart = true, CameraOrSelectOrMoveStop = true,
43 TurnOrActionStart = true, TurnOrActionStop = true,
44 PitchUpStart = true, PitchUpStop = true,
45 PitchDownStart = true, PitchDownStop = true,
46 MoveBackwardStart = true, MoveBackwardStop = true,
47 MoveForwardStart = true, MoveForwardStop = true,
48 Jump = true, StrafeLeftStart = true,
49 StrafeLeftStop = true, StrafeRightStart = true,
50 StrafeRightStop = true, ToggleMouseMove = true,
51 ToggleRun = true, TurnLeftStart = true,
52 TurnLeftStop = true, TurnRightStart = true,
53 TurnRightStop = true,
54 }
55  
56 local _G = getfenv(0)
57  
58 --[[---------------------------------------------------------------------------------
59 Private definitions (Not exposed)
60 ----------------------------------------------------------------------------------]]
61  
62 --[[----------------------------------------------------------------------
63 _debug - Internal Method
64 -------------------------------------------------------------------------]]
65 local function print(text)
66 DEFAULT_CHAT_FRAME:AddMessage(text)
67 end
68  
69 local function _debug(self, msg)
70 local name = self.hooks.name
71 if name then
72 print(string.format("[%s]: %s", name, msg))
73 else
74 print(msg)
75 end
76 end
77  
78 local donothing
79  
80 local new, del
81 do
82 local list = setmetatable({}, {__mode = "k"})
83 function new()
84 local t = next(list)
85 if not t then
86 return {}
87 end
88 list[t] = nil
89 return t
90 end
91  
92 function del(t)
93 setmetatable(t, nil)
94 table.setn(t, 0)
95 for k in pairs(t) do
96 t[k] = nil
97 end
98 list[t] = true
99 end
100 end
101  
102 --[[----------------------------------------------------------------------
103 AceHook:_getFunctionHook- internal method
104 -------------------------------------------------------------------------]]
105  
106 local function _getFunctionHook(self, func, handler)
107 local orig = self.hooks[func].orig
108 if type(handler) == "string" then
109 -- The handler is a method, need to self it
110 return function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
111 if self.hooks and self.hooks[func] and self.hooks[func].active then
112 return self[handler](self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
113 elseif orig then
114 return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
115 end
116 end
117 else
118 -- The handler is a function, just call it
119 return function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
120 if self.hooks and self.hooks[func] and self.hooks[func].active then
121 return handler(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
122 elseif orig then
123 return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
124 end
125 end
126 end
127 end
128  
129 --[[----------------------------------------------------------------------
130 AceHook:_getMethodHook - Internal Method
131 -------------------------------------------------------------------------]]
132 local function _getMethodHook(self, object, method, handler, script)
133 local orig = self.hooks[object][method].orig
134 if type(handler) == "string" then
135 -- The handler is a method, need to self it
136 if script then
137 return function()
138 if self.hooks and self.hooks[object] and self.hooks[object][method] and self.hooks[object][method].active then
139 return self[handler](self, object)
140 elseif orig then
141 return orig()
142 end
143 end
144 else
145 return function(obj,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
146 if self.hooks and self.hooks[obj] and self.hooks[obj][method] and self.hooks[obj][method].active then
147 return self[handler](self, obj, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
148 elseif orig then
149 return orig(object, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
150 end
151 end
152 end
153 else
154 -- The handler is a function, just call it
155 if script then
156 return function()
157 if self.hooks and self.hooks[object] and self.hooks[object][method] and self.hooks[object][method].active then
158 return handler(object)
159 elseif orig then
160 return orig()
161 end
162 end
163 else
164 return function(obj,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
165 if self.hooks and self.hooks[obj] and self.hooks[obj][method] and self.hooks[obj][method].active then
166 return handler(obj,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
167 elseif orig then
168 return orig(object, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
169 end
170 end
171 end
172 end
173 end
174  
175 --[[----------------------------------------------------------------------
176 AceHook:HookFunc - internal method.
177 o You can only hook each function once from each source.
178 o If there is an inactive hook for this func/handler pair, we reactivate it
179 o If there is an inactive hook for another handler, we error out.
180 o Looks for handler as a method of the calling class, error if not available
181 o If handler is a function, it just uses it directly through the wrapper
182 -------------------------------------------------------------------------]]
183 local function _hookFunc(self, func, handler)
184 local f = _G[func]
185  
186 if not f or type(f) ~= "function" then
187 _debug(self, string.format("Attempt to hook a non-existant function %q", func),3)
188 return
189 end
190  
191 if not handler then handler = func end
192  
193 if self.hooks[func] then
194 -- We have an active hook from this source. Don't multi-hook
195 if self.hooks[func].active then
196 _debug(self, string.format("%q already has an active hook from this source.", func))
197 return
198 end
199 -- The hook is inactive, so reactivate it
200 if self.hooks[func].handler == handler then
201 self.hooks[func].active = true
202 return
203 else
204 AceHook:error("There is a stale hook for %q can't hook or reactivate.", func)
205 end
206 end
207  
208 if type(handler) == "string" then
209 if type(self[handler]) ~= "function" then
210 AceHook:error("Could not find the the handler %q when hooking function %q", handler, func)
211 end
212 elseif type(handler) ~= "function" then
213 AceHook:error("Could not find the handler you supplied when hooking %q", func)
214 end
215  
216 local t = new()
217 self.hooks[func] = t
218 t.orig = f
219 t.active = true
220 t.handler = handler
221 t.func = _getFunctionHook(self, func, handler)
222  
223 _G[func] = t.func
224 end
225  
226 --[[----------------------------------------------------------------------
227 AceHook:UnhookFunc - internal method
228 o If you attempt to unhook a function that has never been hooked, or to unhook in a
229 system that has never had a hook before, the system will error with a stack trace
230 o If we own the global function, then put the original back in its place and remove
231 all references to the Hooks[func] structure.
232 o If we don't own the global function (we've been hooked) we deactivate the hook,
233 forcing the handler to passthrough.
234 -------------------------------------------------------------------------]]
235 local function _unhookFunc(self, func)
236 if not self.hooks[func] then
237 _debug(self, string.format("Tried to unhook %q which is not currently hooked.", func))
238 return
239 end
240 if self.hooks[func].active then
241 -- See if we own the global function
242 if _G[func] == self.hooks[func].func then
243 _G[func] = self.hooks[func].orig
244 del(self.hooks[func])
245 self.hooks[func] = nil
246 -- Magically all-done
247 else
248 self.hooks[func].active = nil
249 end
250 end
251 end
252  
253 --[[----------------------------------------------------------------------
254 AceHook:HookMeth - Takes an optional fourth argument
255 o script - Signifies whether this is a script hook or not
256 -------------------------------------------------------------------------]]
257  
258 local function _hookMeth(self, obj, method, handler, script)
259 if not handler then handler = method end
260 if (not obj or type(obj) ~= "table") then
261 AceHook:error("The object you supplied could not be found, or isn't a table.")
262 end
263  
264 if self.hooks[obj] and self.hooks[obj][method] then
265 -- We have an active hook from this source. Don't multi-hook
266 if self.hooks[obj][method].active then
267 _debug(self, string.format("%q already has an active hook from this source.", method))
268 return
269 end
270 -- The hook is inactive, so reactivate it.
271 if self.hooks[obj][method].handler == handler then
272 self.hooks[obj][method].active = true
273 return
274 else
275 AceHook:error("There is a stale hook for %q can't hook or reactivate.", method)
276 end
277 end
278 -- We're clear to try the hook, let's make some checks first
279 if type(handler) == "string" then
280 if type(self[handler]) ~= "function" then
281 AceHook:error("Could not find the handler %q you supplied when hooking method %q", handler, method)
282 end
283 elseif type(handler) ~= "function" then
284 AceHook:error("Could not find the handler you supplied when hooking method %q", method)
285 end
286 -- Handler has been found, so now try to find the method we're trying to hook
287 local orig
288 -- Script
289 if script then
290 if not obj.GetScript then
291 AceHook:error("The object you supplied does not have a GetScript method.")
292 end
293 if not obj:HasScript(method) then
294 AceHook:error("The object you supplied doesn't allow the %q method.", method)
295 end
296 -- Sometimes there is not a original function for a script.
297 orig = obj:GetScript(method)
298 if not orig then
299 if not donothing then
300 function donothing() end
301 end
302 orig = donothing
303 end
304 -- Method
305 else
306 orig = obj[method]
307 end
308 if not orig then
309 AceHook:error("Could not find the method or script %q you are trying to hook.", method)
310 end
311 if not self.hooks[obj] then
312 self.hooks[obj] = new()
313 end
314 local t = new()
315 self.hooks[obj][method] = t
316 t.orig = orig
317 t.active = true
318 t.handler = handler
319 t.script = script
320 t.func = _getMethodHook(self, obj, method, handler, script)
321  
322 if script then
323 obj:SetScript(method, t.func)
324 else
325 obj[method] = t.func
326 end
327 end
328  
329 --[[----------------------------------------------------------------------
330 AceHook:UnhookMeth - Internal method
331 o If you attempt to unhook a method that has never been hooked, or to unhook in a
332 system that has never had a hook before, the system will error with a stack trace
333 o If we own the global method, then put the original back in its place and remove
334 all references to the Hooks[obj][method] structure.
335 o If we don't own the global method (we've been hooked) we deactivate the hook,
336 forcing the handler to passthrough.
337 -------------------------------------------------------------------------]]
338 local function _unhookMeth(self, obj, method)
339 if not self.hooks[obj] or not self.hooks[obj][method] then
340 _debug(self, string.format("Attempt to unhook a method %q that is not currently hooked.", method))
341 return
342 end
343 if self.hooks[obj][method].active then
344 -- If this is a script
345 if self.hooks[obj][method].script then
346 if obj:GetScript(method) == self.hooks[obj][method].func then
347 -- We own the script. Kill it.
348 obj:SetScript(method, self.hooks[obj][method].orig)
349 del(self.hooks[obj][method])
350 self.hooks[obj][method] = nil
351 else
352 self.hooks[obj][method].active = nil
353 end
354 else
355 if obj[method] == self.hooks[obj][method].func then
356 -- We own the method. Kill it.
357 obj[method] = self.hooks[obj][method].orig
358 del(self.hooks[obj][method])
359 self.hooks[obj][method] = nil
360 else
361 self.hooks[obj][method].active = nil
362 end
363 end
364 end
365 if not next(self.hooks[obj]) then
366 -- Spank the table
367 del(self.hooks[obj])
368 self.hooks[obj] = nil
369 end
370 end
371  
372 --[[----------------------------------------------------------------------
373 AceHook:embed
374 AceHook:embed(object)
375 -------------------------------------------------------------------------]]
376 function AceHook:embed(object)
377 if not object.hooks then
378 object.hooks = new()
379 end
380  
381 local name
382  
383 if type(rawget(object, 'GetLibraryVersion')) == "function" then
384 name = object:GetLibraryVersion()
385 end
386 if not name and type(object.GetName) == "function" then
387 name = object:GetName()
388 end
389 if not name and type(object.name) == "string" then
390 name = object.name
391 end
392 if not name then
393 for k,v in pairs(_G) do
394 if v == object then
395 name = tostring(k)
396 break
397 end
398 end
399 end
400  
401 object.hooks.name = name
402 AceHook.super.embed(self, object)
403 end
404  
405 --[[----------------------------------------------------------------------
406 AceHook:Hook
407 self:Hook("functionName", ["handlerName" | handler])
408 self:Hook(ObjectName, "Method", ["Handler" | handler])
409 -------------------------------------------------------------------------]]
410 function AceHook:Hook(arg1, arg2, arg3)
411 if type(arg1)== "string" then
412 if protFuncs[arg1] then
413 if self.hooks.name then
414 string.format("%s tried to hook %q, which is a Blizzard protected function.", self.hooks.name, arg1)
415 AceHook:error("%s tried to hook %q, which is a Blizzard protected function.", self.hooks.name, arg1)
416 else
417 _debug(self, string.format("An Addon tried to hook %q, which is a Blizzard protected function.", arg1))
418 end
419 else
420 _hookFunc(self, arg1, arg2)
421 end
422 else
423 _hookMeth(self, arg1, arg2, arg3)
424 end
425 end
426  
427 function AceHook:HookScript(arg1, arg2, arg3)
428 _hookMeth(self, arg1, arg2, arg3, true)
429 end
430  
431 --[[----------------------------------------------------------------------
432 AceHook:IsHooked()
433 self:Hook("functionName")
434 self:Hook(ObjectName, "Method")
435  
436 Returns whether or not the given function is hooked in the current
437 namespace. A hooked, but inactive function is considered NOT
438 hooked in this context.
439 -------------------------------------------------------------------------]]
440 function AceHook:IsHooked(obj, method)
441 if method and obj then
442 if self.hooks and self.hooks[obj] and self.hooks[obj][method] and self.hooks[obj][method].active then
443 return true, self.hooks[obj][method].handler
444 end
445 else
446 if self.hooks and self.hooks[obj] and self.hooks[obj].active then
447 return true, self.hooks[obj].handler
448 end
449 end
450  
451 return false, nil
452 end
453  
454 --[[----------------------------------------------------------------------
455 AceHook:Unhook
456 self:Unhook("functionName")
457 self:Unhook(ObjectName, "Method")
458 -------------------------------------------------------------------------]]
459 function AceHook:Unhook(arg1, arg2)
460 if type(arg1) == "string" then
461 _unhookFunc(self, arg1)
462 else
463 _unhookMeth(self, arg1, arg2)
464 end
465 end
466  
467 --[[----------------------------------------------------------------------
468 AceHook:UnhookAll - Unhooks all active hooks from the calling source
469 -------------------------------------------------------------------------]]
470 function AceHook:UnhookAll()
471 for key, value in pairs(self.hooks) do
472 if type(key) == "table" then
473 for method in pairs(value) do
474 self:Unhook(key, method)
475 end
476 else
477 self:Unhook(key)
478 end
479 end
480 end
481  
482  
483 function AceHook:OnEmbedDisable(target)
484 self.UnhookAll(target)
485 end
486  
487 --[[----------------------------------------------------------------------
488 AceHook:HookReport - Lists registered hooks from this source
489 -------------------------------------------------------------------------]]
490  
491 function AceHook:HookReport()
492 _debug(self, "This is a list of all active hooks for this object:")
493 if not self.hooks then _debug(self, "No registered hooks.") return end
494  
495 for key, value in pairs(self.hooks) do
496 if type(key) == "table" then
497 for method in pairs(value) do
498 _debug(self, string.format("key: %s method: %q |cff%s|r", tostring(key), method, self.hooks[key][method].active and "00ff00Active" or "ffff00Inactive"))
499 end
500 else
501 _debug(self, string.format("key: %s value: %q |cff%s|r", tostring(key), value, self.hooks[key].active and "00ff00Active" or "ffff00Inactive"))
502 end
503 end
504 end
505  
506 --[[---------------------------------------------------------------------------------
507 Stub and Library registration
508 ----------------------------------------------------------------------------------]]
509  
510 function AceHook:activate(oldLib, oldDeactivate)
511 AceHook = AceLibrary(MAJOR_VERSION)
512  
513 AceHook.super.activate(self, oldLib, oldDeactivate)
514 end
515  
516 AceLibrary:Register(AceHook, MAJOR_VERSION, MINOR_VERSION, AceHook.activate)
517 AceHook = AceLibrary(MAJOR_VERSION)