Mercurial > wow > reaction
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libs/AceHook-2.0/AceHook-2.0.lua Tue Mar 20 21:03:57 2007 +0000 @@ -0,0 +1,554 @@ +--[[ +Name: AceHook-2.0 +Revision: $Rev: 18708 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceHook-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceHook-2.0 +Description: Mixin to allow for safe hooking of functions, methods, and scripts. +Dependencies: AceLibrary, AceOO-2.0 +]] + +local MAJOR_VERSION = "AceHook-2.0" +local MINOR_VERSION = "$Revision: 18708 $" + +-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +local lua51 = loadstring("return function(...) return ... end") and true or false + +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end + +--[[--------------------------------------------------------------------------------- + Create the library object +----------------------------------------------------------------------------------]] + +local AceOO = AceLibrary:GetInstance("AceOO-2.0") +local AceHook = AceOO.Mixin { + "Hook", + "Unhook", + "UnhookAll", + "HookReport", + "IsHooked", + "HookScript", + } + +local table_setn = lua51 and function() end or table.setn + +if lua51 then + AceHook.__deprecated = MAJOR_VERSION .. " is deprecated in WoW 2.0" +end + +--[[--------------------------------------------------------------------------------- + Library Definitions +----------------------------------------------------------------------------------]] + +local protFuncs = { + CameraOrSelectOrMoveStart = true, CameraOrSelectOrMoveStop = true, + TurnOrActionStart = true, TurnOrActionStop = true, + PitchUpStart = true, PitchUpStop = true, + PitchDownStart = true, PitchDownStop = true, + MoveBackwardStart = true, MoveBackwardStop = true, + MoveForwardStart = true, MoveForwardStop = true, + Jump = true, StrafeLeftStart = true, + StrafeLeftStop = true, StrafeRightStart = true, + StrafeRightStop = true, ToggleMouseMove = true, + ToggleRun = true, TurnLeftStart = true, + TurnLeftStop = true, TurnRightStart = true, + TurnRightStop = true, +} + +local _G = getfenv(0) + +local handlers, funcs, scripts, actives + +--[[--------------------------------------------------------------------------------- + Private definitions (Not exposed) +----------------------------------------------------------------------------------]] + +--[[---------------------------------------------------------------------- + _debug - Internal Method +-------------------------------------------------------------------------]] +local function print(text) + DEFAULT_CHAT_FRAME:AddMessage(text) +end + +local function _debug(self, msg) + local name = self.hooks.name + if name then + print(string.format("[%s]: %s", name, msg)) + else + print(msg) + end +end + +local new, del +do + local list = setmetatable({}, {__mode = "k"}) + function new() + local t = next(list) + if not t then + return {} + end + list[t] = nil + return t + end + + function del(t) + setmetatable(t, nil) + table_setn(t, 0) + for k in pairs(t) do + t[k] = nil + end + list[t] = true + end +end + +local origMetatable = { + __call = function(self, ...) + return self.orig(...) + end +} + +--[[---------------------------------------------------------------------- + AceHook:_getFunctionHook- internal method +-------------------------------------------------------------------------]] + +local function _getFunctionHook(self, func, handler, orig) + if type(handler) == "string" then + -- The handler is a method, need to self it + return function(...) + if actives[orig] then + return self[handler](self, ...) + else + return orig(...) + end + end + else + -- The handler is a function, just call it + return function(...) + if actives[orig] then + return handler(...) + else + return orig(...) + end + end + end +end + +--[[---------------------------------------------------------------------- + AceHook:_getMethodHook - Internal Method +-------------------------------------------------------------------------]] +local function _getMethodHook(self, object, method, handler, orig, script) + if type(handler) == "string" then + -- The handler is a method, need to self it + if script then + return function() + if actives[orig] then + return self[handler](self, object) + else + return orig() + end + end + else + return function(obj,...) + if actives[orig] then + return self[handler](self, obj, ...) + else + return orig(obj, ...) + end + end + end + else + -- The handler is a function, just call it + if script then + return function() + if actives[orig] then + return handler(object) + else + return orig() + end + end + else + return function(obj, ...) + if actives[orig] then + return handler(obj, ...) + else + return orig(obj, ...) + end + end + end + end +end + +--[[---------------------------------------------------------------------- + AceHook:HookFunc - internal method. + o You can only hook each function once from each source. + o If there is an inactive hook for this func/handler pair, we reactivate it + o If there is an inactive hook for another handler, we error out. + o Looks for handler as a method of the calling class, error if not available + o If handler is a function, it just uses it directly through the wrapper +-------------------------------------------------------------------------]] +local function _hookFunc(self, func, handler) + local orig = _G[func] + + if not orig or type(orig) ~= "function" then + _debug(self, string.format("Attempt to hook a non-existant function %q", func),3) + return + end + + if not handler then handler = func end + + if self.hooks[func] then + local orig = self.hooks[func].orig + -- We have an active hook from this source. Don't multi-hook + if actives[orig] then + _debug(self, string.format("%q already has an active hook from this source.", func)) + return + end + -- The hook is inactive, so reactivate it + if handlers[orig] == handler then + actives[orig] = true + return + else + AceHook:error("There is a stale hook for %q can't hook or reactivate.", func) + end + end + + if type(handler) == "string" then + if type(self[handler]) ~= "function" then + AceHook:error("Could not find the the handler %q when hooking function %q", handler, func) + end + elseif type(handler) ~= "function" then + AceHook:error("Could not find the handler you supplied when hooking %q", func) + end + + local t = setmetatable(new(), origMetatable) + self.hooks[func] = t + t.orig = orig + + actives[orig] = true + handlers[orig] = handler + local newFunc = _getFunctionHook(self, func, handler, orig) + funcs[orig] = newFunc + + _G[func] = newFunc +end + +--[[---------------------------------------------------------------------- + AceHook:UnhookFunc - internal method + o If you attempt to unhook a function that has never been hooked, or to unhook in a + system that has never had a hook before, the system will error with a stack trace + o If we own the global function, then put the original back in its place and remove + all references to the Hooks[func] structure. + o If we don't own the global function (we've been hooked) we deactivate the hook, + forcing the handler to passthrough. +-------------------------------------------------------------------------]] +local function _unhookFunc(self, func) + if not self.hooks[func] or not funcs[self.hooks[func].orig] then + _debug(self, string.format("Tried to unhook %q which is not currently hooked.", func)) + return + end + + local orig = self.hooks[func].orig + + if actives[orig] then + -- See if we own the global function + if _G[func] == funcs[orig] then + _G[func] = orig + self.hooks[func] = del(self.hooks[func]) + handlers[orig] = nil + funcs[orig] = nil + scripts[orig] = nil + actives[orig] = nil + -- Magically all-done + else + actives[orig] = nil + end + end +end + +--[[---------------------------------------------------------------------- + AceHook:HookMeth - Takes an optional fourth argument + o script - Signifies whether this is a script hook or not +-------------------------------------------------------------------------]] + +local function _hookMeth(self, obj, method, handler, script) + if not handler then handler = method end + if (not obj or type(obj) ~= "table") then + AceHook:error("The object you supplied could not be found, or isn't a table.") + end + + if self.hooks[obj] and self.hooks[obj][method] then + local orig = self.hooks[obj][method].orig + -- We have an active hook from this source. Don't multi-hook + if actives[orig] then + _debug(self, string.format("%q already has an active hook from this source.", method)) + return + end + -- The hook is inactive, so reactivate it. + if handlers[orig] == handler then + actives[orig] = true + return + else + AceHook:error("There is a stale hook for %q can't hook or reactivate.", method) + end + end + -- We're clear to try the hook, let's make some checks first + if type(handler) == "string" then + if type(self[handler]) ~= "function" then + AceHook:error("Could not find the handler %q you supplied when hooking method %q", handler, method) + end + elseif type(handler) ~= "function" then + AceHook:error("Could not find the handler you supplied when hooking method %q", method) + end + -- Handler has been found, so now try to find the method we're trying to hook + local orig + -- Script + if script then + if not obj.GetScript then + AceHook:error("The object you supplied does not have a GetScript method.") + end + if not obj:HasScript(method) then + AceHook:error("The object you supplied doesn't allow the %q method.", method) + end + -- Sometimes there is not a original function for a script. + orig = obj:GetScript(method) + if not orig then + orig = function() end + end + -- Method + else + orig = obj[method] + end + if not orig then + AceHook:error("Could not find the method or script %q you are trying to hook.", method) + end + if not self.hooks[obj] then + self.hooks[obj] = new() + end + local t = setmetatable(new(), origMetatable) + self.hooks[obj][method] = t + t.orig = orig + + actives[orig] = true + handlers[orig] = handler + scripts[orig] = script and true or nil + local newFunc = _getMethodHook(self, obj, method, handler, orig, script) + funcs[orig] = newFunc + + if script then + obj:SetScript(method, newFunc) + else + obj[method] = newFunc + end +end + +--[[---------------------------------------------------------------------- + AceHook:UnhookMeth - Internal method + o If you attempt to unhook a method that has never been hooked, or to unhook in a + system that has never had a hook before, the system will error with a stack trace + o If we own the global method, then put the original back in its place and remove + all references to the Hooks[obj][method] structure. + o If we don't own the global method (we've been hooked) we deactivate the hook, + forcing the handler to passthrough. +-------------------------------------------------------------------------]] +local function _unhookMeth(self, obj, method) + if not self.hooks[obj] or not self.hooks[obj][method] or not funcs[self.hooks[obj][method].orig] then + _debug(self, string.format("Attempt to unhook a method %q that is not currently hooked.", method)) + return + end + + local orig = self.hooks[obj][method].orig + + if actives[orig] then + -- If this is a script + if scripts[orig] then + if obj:GetScript(method) == funcs[orig] then + -- We own the script. Kill it. + obj:SetScript(method, orig) + self.hooks[obj][method] = del(self.hooks[obj][method]) + handlers[orig] = nil + funcs[orig] = nil + scripts[orig] = nil + actives[orig] = nil + else + actives[orig] = nil + end + else + if obj[method] == funcs[orig] then + -- We own the method. Kill it. + obj[method] = orig + self.hooks[obj][method] = del(self.hooks[obj][method]) + handlers[orig] = nil + funcs[orig] = nil + scripts[orig] = nil + actives[orig] = nil + else + actives[orig] = nil + end + end + end + if not next(self.hooks[obj]) then + -- Spank the table + self.hooks[obj] = del(self.hooks[obj]) + end +end + +function AceHook:OnInstanceInit(object) + if not object.hooks then + object.hooks = new() + end + + local name + + if type(rawget(object, 'GetLibraryVersion')) == "function" then + name = object:GetLibraryVersion() + end + if not name and type(object.GetName) == "function" then + name = object:GetName() + end + if not name and type(object.name) == "string" then + name = object.name + end + if not name then + for k,v in pairs(_G) do + if v == object then + name = tostring(k) + break + end + end + end + + object.hooks.name = name +end + +AceHook.OnManualEmbed = AceHook.OnInstanceInit + +--[[---------------------------------------------------------------------- + AceHook:Hook + self:Hook("functionName", ["handlerName" | handler]) + self:Hook(ObjectName, "Method", ["Handler" | handler]) +-------------------------------------------------------------------------]] +function AceHook:Hook(arg1, arg2, arg3) + if type(arg1)== "string" then + if protFuncs[arg1] then + if self.hooks.name then + AceHook:error("%s tried to hook %q, which is a Blizzard protected function.", self.hooks.name, arg1) + else + _debug(self, string.format("An Addon tried to hook %q, which is a Blizzard protected function.", arg1)) + end + else + _hookFunc(self, arg1, arg2) + end + else + _hookMeth(self, arg1, arg2, arg3) + end +end + +function AceHook:HookScript(arg1, arg2, arg3) + _hookMeth(self, arg1, arg2, arg3, true) +end + +--[[---------------------------------------------------------------------- + AceHook:IsHooked() + self:Hook("functionName") + self:Hook(ObjectName, "Method") + + Returns whether or not the given function is hooked in the current + namespace. A hooked, but inactive function is considered NOT + hooked in this context. +-------------------------------------------------------------------------]] +function AceHook:IsHooked(obj, method) + if method and obj then + if self.hooks and self.hooks[obj] and self.hooks[obj][method] and actives[self.hooks[obj][method].orig] then + return true, handlers[self.hooks[obj][method].orig] + end + else + if self.hooks and self.hooks[obj] and actives[self.hooks[obj].orig] then + return true, handlers[self.hooks[obj].orig] + end + end + + return false, nil +end + +--[[---------------------------------------------------------------------- + AceHook:Unhook + self:Unhook("functionName") + self:Unhook(ObjectName, "Method") +-------------------------------------------------------------------------]] +function AceHook:Unhook(arg1, arg2) + if type(arg1) == "string" then + _unhookFunc(self, arg1) + else + _unhookMeth(self, arg1, arg2) + end +end + +--[[---------------------------------------------------------------------- + AceHook:UnhookAll - Unhooks all active hooks from the calling source +-------------------------------------------------------------------------]] +function AceHook:UnhookAll() + for key, value in pairs(self.hooks) do + if type(key) == "table" then + for method in pairs(value) do + self:Unhook(key, method) + end + else + self:Unhook(key) + end + end +end + + +function AceHook:OnEmbedDisable(target) + self.UnhookAll(target) +end + +--[[---------------------------------------------------------------------- + AceHook:HookReport - Lists registered hooks from this source +-------------------------------------------------------------------------]] + +function AceHook:HookReport() + _debug(self, "This is a list of all active hooks for this object:") + if not self.hooks then _debug(self, "No registered hooks.") return end + + for key, value in pairs(self.hooks) do + if type(value) == "table" then + for method in pairs(value) do + _debug(self, string.format("key: %s method: %q |cff%s|r", tostring(key), method, self.hooks[key][method].active and "00ff00Active" or "ffff00Inactive")) + end + else + _debug(self, string.format("key: %s value: %q |cff%s|r", tostring(key), tostring(value), self.hooks[key].active and "00ff00Active" or "ffff00Inactive")) + end + end +end + +--[[--------------------------------------------------------------------------------- + Stub and Library registration +----------------------------------------------------------------------------------]] + +local function activate(self, oldLib, oldDeactivate) + AceHook = self + + self.handlers = oldLib and oldLib.handlers or {} + self.funcs = oldLib and oldLib.funcs or {} + self.scripts = oldLib and oldLib.scripts or {} + self.actives = oldLib and oldLib.actives or {} + + handlers = self.handlers + funcs = self.funcs + scripts = self.scripts + actives = self.actives + + self:activate(oldLib, oldDeactivate) + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceHook, MAJOR_VERSION, MINOR_VERSION, activate)