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