comparison libs/AceHook-2.0/AceHook-2.0.lua @ 1:c11ca1d8ed91

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