Mercurial > wow > reaction
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) |