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