Mercurial > wow > reaction
comparison libs/AceHook-2.1/AceHook-2.1.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.1 | |
3 Revision: $Rev: 19980 $ | |
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.1 | |
8 SVN: http://svn.wowace.com/root/trunk/Ace2/AceHook-2.1 | |
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.1" | |
14 local MINOR_VERSION = "$Revision: 19980 $" | |
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 "HookScript", | |
30 "SecureHook", | |
31 "Unhook", | |
32 "UnhookAll", | |
33 "HookReport", | |
34 "IsHooked", | |
35 } | |
36 | |
37 --[[--------------------------------------------------------------------------------- | |
38 Library Definitions | |
39 ----------------------------------------------------------------------------------]] | |
40 | |
41 local protectedScripts = { | |
42 OnClick = true, | |
43 } | |
44 | |
45 local _G = getfenv(0) | |
46 | |
47 local handlers, scripts, actives, registry, onceSecure | |
48 | |
49 --[[--------------------------------------------------------------------------------- | |
50 Private definitions (Not exposed) | |
51 ----------------------------------------------------------------------------------]] | |
52 | |
53 local new, del | |
54 do | |
55 local list = setmetatable({}, {__mode = "k"}) | |
56 function new() | |
57 local t = next(list) | |
58 if not t then | |
59 return {} | |
60 end | |
61 list[t] = nil | |
62 return t | |
63 end | |
64 | |
65 function del(t) | |
66 setmetatable(t, nil) | |
67 for k in pairs(t) do | |
68 t[k] = nil | |
69 end | |
70 list[t] = true | |
71 end | |
72 end | |
73 | |
74 local function createFunctionHook(self, func, handler, orig, secure) | |
75 if not secure then | |
76 if type(handler) == "string" then | |
77 -- The handler is a method, need to self it | |
78 local uid | |
79 uid = function(...) | |
80 if actives[uid] then | |
81 return self[handler](self, ...) | |
82 else | |
83 return orig(...) | |
84 end | |
85 end | |
86 return uid | |
87 else | |
88 -- The handler is a function, just call it | |
89 local uid | |
90 uid = function(...) | |
91 if actives[uid] then | |
92 return handler(...) | |
93 else | |
94 return orig(...) | |
95 end | |
96 end | |
97 return uid | |
98 end | |
99 else | |
100 -- secure hooks don't call the original method | |
101 if type(handler) == "string" then | |
102 -- The handler is a method, need to self it | |
103 local uid | |
104 uid = function(...) | |
105 if actives[uid] then | |
106 return self[handler](self, ...) | |
107 end | |
108 end | |
109 return uid | |
110 else | |
111 -- The handler is a function, just call it | |
112 local uid | |
113 uid = function(...) | |
114 if actives[uid] then | |
115 return handler(...) | |
116 end | |
117 end | |
118 return uid | |
119 end | |
120 end | |
121 end | |
122 | |
123 local function createMethodHook(self, object, method, handler, orig, secure) | |
124 if not secure then | |
125 if type(handler) == "string" then | |
126 local uid | |
127 uid = function(...) | |
128 if actives[uid] then | |
129 return self[handler](self, ...) | |
130 else | |
131 return orig(...) | |
132 end | |
133 end | |
134 return uid | |
135 else | |
136 -- The handler is a function, just call it | |
137 local uid | |
138 uid = function(...) | |
139 if actives[uid] then | |
140 return handler(...) | |
141 else | |
142 return orig(...) | |
143 end | |
144 end | |
145 return uid | |
146 end | |
147 else | |
148 -- secure hooks don't call the original method | |
149 if type(handler) == "string" then | |
150 local uid | |
151 uid = function(...) | |
152 if actives[uid] then | |
153 return self[handler](self, ...) | |
154 end | |
155 end | |
156 return uid | |
157 else | |
158 -- The handler is a function, just call it | |
159 local uid | |
160 uid = function(...) | |
161 if actives[uid] then | |
162 return handler(...) | |
163 end | |
164 end | |
165 return uid | |
166 end | |
167 end | |
168 end | |
169 | |
170 local function hookFunction(self, func, handler, secure) | |
171 local orig = _G[func] | |
172 | |
173 if not orig or type(orig) ~= "function" then | |
174 AceHook:error("Attempt to hook a non-existant function %q", func) | |
175 end | |
176 | |
177 if not handler then | |
178 handler = func | |
179 end | |
180 | |
181 local uid = registry[self][func] | |
182 if uid then | |
183 if actives[uid] then | |
184 -- We have an active hook from this source. Don't multi-hook | |
185 AceHook:error("%q already has an active hook from this source.", func) | |
186 end | |
187 | |
188 if handlers[uid] == handler then | |
189 -- The hook is inactive, so reactivate it | |
190 actives[uid] = true | |
191 return | |
192 else | |
193 self.hooks[func] = nil | |
194 registry[self][func] = nil | |
195 handlers[uid] = nil | |
196 uid = nil | |
197 end | |
198 end | |
199 | |
200 if type(handler) == "string" then | |
201 if type(self[handler]) ~= "function" then | |
202 AceHook:error("Could not find the the handler %q when hooking function %q", handler, func) | |
203 end | |
204 elseif type(handler) ~= "function" then | |
205 AceHook:error("Could not find the handler you supplied when hooking %q", func) | |
206 end | |
207 | |
208 uid = createFunctionHook(self, func, handler, orig, secure) | |
209 registry[self][func] = uid | |
210 actives[uid] = true | |
211 handlers[uid] = handler | |
212 | |
213 if not secure then | |
214 _G[func] = uid | |
215 self.hooks[func] = orig | |
216 else | |
217 hooksecurefunc(func, uid) | |
218 end | |
219 end | |
220 | |
221 local function unhookFunction(self, func) | |
222 if not registry[self][func] then | |
223 AceHook:error("Tried to unhook %q which is not currently hooked.", func) | |
224 end | |
225 | |
226 local uid = registry[self][func] | |
227 | |
228 if actives[uid] then | |
229 -- See if we own the global function | |
230 if self.hooks[func] and _G[func] == uid then | |
231 _G[func] = self.hooks[func] | |
232 self.hooks[func] = nil | |
233 registry[self][func] = nil | |
234 handlers[uid] = nil | |
235 actives[uid] = nil | |
236 -- Magically all-done | |
237 else | |
238 actives[uid] = nil | |
239 end | |
240 end | |
241 end | |
242 | |
243 local function hookMethod(self, obj, method, handler, script, secure) | |
244 if not handler then | |
245 handler = method | |
246 end | |
247 | |
248 if not obj or type(obj) ~= "table" then | |
249 AceHook:error("The object you supplied could not be found, or isn't a table.") | |
250 end | |
251 | |
252 local uid = registry[self][obj] and registry[self][obj][method] | |
253 if uid then | |
254 if actives[uid] then | |
255 -- We have an active hook from this source. Don't multi-hook | |
256 AceHook:error("%q already has an active hook from this source.", method) | |
257 end | |
258 | |
259 if handlers[uid] == handler then | |
260 -- The hook is inactive, reactivate it. | |
261 actives[uid] = true | |
262 return | |
263 else | |
264 if self.hooks[obj] then | |
265 self.hooks[obj][method] = nil | |
266 end | |
267 registry[self][obj][method] = nil | |
268 handlers[uid] = nil | |
269 actives[uid] = nil | |
270 scripts[uid] = nil | |
271 uid = nil | |
272 end | |
273 end | |
274 | |
275 if type(handler) == "string" then | |
276 if type(self[handler]) ~= "function" then | |
277 AceHook:error("Could not find the handler %q you supplied when hooking method %q", handler, method) | |
278 end | |
279 elseif type(handler) ~= "function" then | |
280 AceHook:error("Could not find the handler you supplied when hooking method %q", method) | |
281 end | |
282 | |
283 local orig | |
284 if script then | |
285 if not obj.GetScript then | |
286 AceHook:error("The object you supplied does not have a GetScript method.") | |
287 end | |
288 if not obj:HasScript(method) then | |
289 AceHook:error("The object you supplied doesn't allow the %q method.", method) | |
290 end | |
291 | |
292 orig = obj:GetScript(method) | |
293 if type(orig) ~= "function" then | |
294 -- Sometimes there is not a original function for a script. | |
295 orig = function() end | |
296 end | |
297 else | |
298 orig = obj[method] | |
299 end | |
300 if not orig then | |
301 AceHook:error("Could not find the method or script %q you are trying to hook.", method) | |
302 end | |
303 | |
304 if not self.hooks[obj] then | |
305 self.hooks[obj] = new() | |
306 end | |
307 if not registry[self][obj] then | |
308 registry[self][obj] = new() | |
309 end | |
310 | |
311 local uid = createMethodHook(self, obj, method, handler, orig, secure) | |
312 registry[self][obj][method] = uid | |
313 actives[uid] = true | |
314 handlers[uid] = handler | |
315 scripts[uid] = script and true or nil | |
316 | |
317 if script then | |
318 obj:SetScript(method, uid) | |
319 self.hooks[obj][method] = orig | |
320 elseif not secure then | |
321 obj[method] = uid | |
322 self.hooks[obj][method] = orig | |
323 else | |
324 hooksecurefunc(obj, method, uid) | |
325 end | |
326 end | |
327 | |
328 local function unhookMethod(self, obj, method) | |
329 if not registry[self][obj] or not registry[self][obj][method] then | |
330 AceHook:error("Attempt to unhook a method %q that is not currently hooked.", method) | |
331 return | |
332 end | |
333 | |
334 local uid = registry[self][obj][method] | |
335 | |
336 if actives[uid] then | |
337 if scripts[uid] then -- If this is a script | |
338 if obj:GetScript(method) == uid then | |
339 -- We own the script. Revert to normal. | |
340 obj:SetScript(method, self.hooks[obj][method]) | |
341 self.hooks[obj][method] = nil | |
342 registry[self][obj][method] = nil | |
343 handlers[uid] = nil | |
344 scripts[uid] = nil | |
345 actives[uid] = nil | |
346 else | |
347 actives[uid] = nil | |
348 end | |
349 else | |
350 if self.hooks[obj] and self.hooks[obj][method] and obj[method] == uid then | |
351 -- We own the method. Revert to normal. | |
352 obj[method] = self.hooks[obj][method] | |
353 self.hooks[obj][method] = nil | |
354 registry[self][obj][method] = nil | |
355 handlers[uid] = nil | |
356 actives[uid] = nil | |
357 else | |
358 actives[uid] = nil | |
359 end | |
360 end | |
361 end | |
362 if self.hooks[obj] and not next(self.hooks[obj]) then | |
363 self.hooks[obj] = del(self.hooks[obj]) | |
364 end | |
365 if not next(registry[self][obj]) then | |
366 registry[self][obj] = del(registry[self][obj]) | |
367 end | |
368 end | |
369 | |
370 -- ("function" [, handler] [, hookSecure]) or (object, "method" [, handler] [, hookSecure]) | |
371 function AceHook:Hook(object, method, handler, hookSecure) | |
372 if type(object) == "string" then | |
373 method, handler, hookSecure = object, method, handler | |
374 if handler == true then | |
375 handler, hookSecure = nil, true | |
376 end | |
377 AceHook:argCheck(handler, 3, "function", "string", "nil") | |
378 AceHook:argCheck(hookSecure, 4, "boolean", "nil") | |
379 if issecurevariable(method) or onceSecure[method] then | |
380 if hookSecure then | |
381 onceSecure[method] = true | |
382 else | |
383 AceHook:error("Attempt to hook secure function %q. Use `SecureHook' or add `true' to the argument list to override.", method) | |
384 end | |
385 end | |
386 hookFunction(self, method, handler, false) | |
387 else | |
388 if handler == true then | |
389 handler, hookSecure = nil, true | |
390 end | |
391 AceHook:argCheck(object, 2, "table") | |
392 AceHook:argCheck(method, 3, "string") | |
393 AceHook:argCheck(handler, 4, "function", "string", "nil") | |
394 AceHook:argCheck(hookSecure, 5, "boolean", "nil") | |
395 if not hookSecure and issecurevariable(object, method) then | |
396 AceHook:error("Attempt to hook secure method %q. Use `SecureHook' or add `true' to the argument list to override.", method) | |
397 end | |
398 hookMethod(self, object, method, handler, false, false) | |
399 end | |
400 end | |
401 | |
402 -- ("function", handler) or (object, "method", handler) | |
403 function AceHook:SecureHook(object, method, handler) | |
404 if type(object) == "string" then | |
405 method, handler = object, method | |
406 AceHook:argCheck(handler, 3, "function", "string", "nil") | |
407 hookFunction(self, method, handler, true) | |
408 else | |
409 AceHook:argCheck(object, 2, "table") | |
410 AceHook:argCheck(method, 3, "string") | |
411 AceHook:argCheck(handler, 4, "function", "string", "nil") | |
412 hookMethod(self, object, method, handler, false, true) | |
413 end | |
414 end | |
415 | |
416 function AceHook:HookScript(frame, script, handler) | |
417 AceHook:argCheck(frame, 2, "table") | |
418 if not frame[0] or type(frame.IsProtected) ~= "function" then | |
419 AceHook:error("Bad argument #2 to `HookScript'. Expected frame.") | |
420 end | |
421 AceHook:argCheck(script, 3, "string") | |
422 AceHook:argCheck(handler, 4, "function", "string", "nil") | |
423 if frame:IsProtected() and protectedScripts[script] then | |
424 AceHook:error("Cannot hook secure script %q.", script) | |
425 end | |
426 hookMethod(self, frame, script, handler, true, false) | |
427 end | |
428 | |
429 -- ("function") or (object, "method") | |
430 function AceHook:IsHooked(obj, method) | |
431 if type(obj) == "string" then | |
432 if registry[self][obj] and actives[registry[self][obj]] then | |
433 return true, handlers[registry[self][obj]] | |
434 end | |
435 else | |
436 AceHook:argCheck(obj, 2, "string", "table") | |
437 AceHook:argCheck(method, 3, "string") | |
438 if registry[self][obj] and registry[self][obj][method] and actives[registry[self][obj][method]] then | |
439 return true, handlers[registry[self][obj][method]] | |
440 end | |
441 end | |
442 | |
443 return false, nil | |
444 end | |
445 | |
446 -- ("function") or (object, "method") | |
447 function AceHook:Unhook(obj, method) | |
448 if type(obj) == "string" then | |
449 unhookFunction(self, obj) | |
450 else | |
451 AceHook:argCheck(obj, 2, "string", "table") | |
452 AceHook:argCheck(method, 3, "string") | |
453 unhookMethod(self, obj, method) | |
454 end | |
455 end | |
456 | |
457 function AceHook:UnhookAll() | |
458 for key, value in pairs(registry[self]) do | |
459 if type(key) == "table" then | |
460 for method in pairs(value) do | |
461 self:Unhook(key, method) | |
462 end | |
463 else | |
464 self:Unhook(key) | |
465 end | |
466 end | |
467 end | |
468 | |
469 function AceHook:HookReport() | |
470 DEFAULT_CHAT_FRAME:AddMessage("This is a list of all active hooks for this object:") | |
471 if not next(registry[self]) then | |
472 DEFAULT_CHAT_FRAME:AddMessage("No hooks") | |
473 end | |
474 | |
475 for key, value in pairs(registry[self]) do | |
476 if type(value) == "table" then | |
477 for method, uid in pairs(value) do | |
478 DEFAULT_CHAT_FRAME:AddMessage(string.format("object: %s method: %q |cff%s|r%s", tostring(key), method, actives[uid] and "00ff00Active" or "ffff00Inactive", not self.hooks[key][method] and " |cff7f7fff-Secure-|r" or "")) | |
479 end | |
480 else | |
481 DEFAULT_CHAT_FRAME:AddMessage(string.format("function: %q |cff%s|r%s", tostring(key), actives[value] and "00ff00Active" or "ffff00Inactive", not self.hooks[key] and " |cff7f7fff-Secure-|r" or "")) | |
482 end | |
483 end | |
484 end | |
485 | |
486 function AceHook:OnInstanceInit(object) | |
487 if not object.hooks then | |
488 object.hooks = new() | |
489 end | |
490 if not registry[object] then | |
491 registry[object] = new() | |
492 end | |
493 end | |
494 | |
495 AceHook.OnManualEmbed = AceHook.OnInstanceInit | |
496 | |
497 function AceHook:OnEmbedDisable(target) | |
498 self.UnhookAll(target) | |
499 end | |
500 | |
501 local function activate(self, oldLib, oldDeactivate) | |
502 AceHook = self | |
503 | |
504 self.handlers = oldLib and oldLib.handlers or {} | |
505 self.registry = oldLib and oldLib.registry or {} | |
506 self.scripts = oldLib and oldLib.scripts or {} | |
507 self.actives = oldLib and oldLib.actives or {} | |
508 self.onceSecure = oldLib and oldLib.onceSecure or {} | |
509 | |
510 handlers = self.handlers | |
511 registry = self.registry | |
512 scripts = self.scripts | |
513 actives = self.actives | |
514 onceSecure = self.onceSecure | |
515 | |
516 self:activate(oldLib, oldDeactivate) | |
517 | |
518 if oldDeactivate then | |
519 oldDeactivate(oldLib) | |
520 end | |
521 end | |
522 | |
523 AceLibrary:Register(AceHook, MAJOR_VERSION, MINOR_VERSION, activate) |