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