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)