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) | 
