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