Skip to content

Commit 855b338

Browse files
committed
feat(frame): add GetClickFrame global
Backport the engine-registered C global behind the /click command: resolve a frame by its global name, returning it only when _G[name] holds a real frame whose GetName() matches. Mirrors 3.3.5's Script_GetClickFrame; reads the name from CFrameScriptObject vtable slot +4, verified against 1.12's own Script_GetName.
1 parent 6ca6af1 commit 855b338

3 files changed

Lines changed: 116 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ reference in **[docs/API.md](docs/API.md)**.
6767
| [Expansion](docs/API.md#expansion) | `ClassicExpansionAtLeast`, `ClassicExpansionAtMost`, `GetClassicExpansionLevel` |
6868
| [Faction](docs/API.md#faction) | `C_Reputation.GetFactionDataByIndex`, `C_Reputation.GetFactionStandings`, `C_Reputation.GetLastStandingChange`, `C_Reputation.GetWatchedFactionData`, `C_Reputation.SetWatchedFactionByID`, `GetFactionIDByIndex`, `GetFactionInfoByID`, `GetFactionParentID` |
6969
| [Focus](docs/API.md#focus) | `ClearFocus`, `FocusUnit` |
70-
| [Frame](docs/API.md#frame) | `region:SetPoint("point")` (one-arg form), `region:SetSize`, `region:GetSize`, `region:IsMouseOver`, `region:GetRect`, `region:IsDragging`, `GetMouseFoci`, `frame:SetShown`, `fontstring:GetStringHeight`, `fontstring:GetUnboundedStringWidth`, `fontstring:GetWrappedWidth`, `fontstring:GetNumLines`, `fontstring:GetLineHeight`, `fontstring:IsTruncated`, `fontstring:SetMaxLines`, `fontstring:GetMaxLines`, `fontstring:SetFormattedText`, `texture:SetRotation`, `texture:GetRotation`, `texture:SetVertexOffset`, `texture:GetVertexOffset`, `texture:SetColorTexture`, `texture:SetMask`, `frame:CreateMaskTexture`, `texture:AddMaskTexture`, `texture:RemoveMaskTexture`, `texture:GetNumMaskTextures`, `texture:GetMaskTexture`, `fontstring:SetRotation`, `fontstring:GetRotation`, `frame:SetResizeBounds`, `frame:HookScript`, `frame:IsEventRegistered`, `frame:GetEffectiveAlpha`, `frame:SetAttribute`, `frame:SetAttributeNoHandler`, `frame:ClearAttribute`, `frame:GetAttribute`, `OnAttributeChanged` (script), `SetModernScriptArgs`, `GetModernScriptArgs`, `SecureCmdOptionParse`, `RegisterStateDriver`, `UnregisterStateDriver`, `RegisterAttributeDriver`, `UnregisterAttributeDriver`, `RegisterUnitWatch`, `UnregisterUnitWatch`, `UnitWatchRegistered`, `SecureButton_GetAttribute`, `SecureButton_GetUnit`, `PreClick` (script), `PostClick` (script) |
70+
| [Frame](docs/API.md#frame) | `region:SetPoint("point")` (one-arg form), `region:SetSize`, `region:GetSize`, `region:IsMouseOver`, `region:GetRect`, `region:IsDragging`, `GetMouseFoci`, `frame:SetShown`, `fontstring:GetStringHeight`, `fontstring:GetUnboundedStringWidth`, `fontstring:GetWrappedWidth`, `fontstring:GetNumLines`, `fontstring:GetLineHeight`, `fontstring:IsTruncated`, `fontstring:SetMaxLines`, `fontstring:GetMaxLines`, `fontstring:SetFormattedText`, `texture:SetRotation`, `texture:GetRotation`, `texture:SetVertexOffset`, `texture:GetVertexOffset`, `texture:SetColorTexture`, `texture:SetMask`, `frame:CreateMaskTexture`, `texture:AddMaskTexture`, `texture:RemoveMaskTexture`, `texture:GetNumMaskTextures`, `texture:GetMaskTexture`, `fontstring:SetRotation`, `fontstring:GetRotation`, `frame:SetResizeBounds`, `frame:HookScript`, `frame:IsEventRegistered`, `frame:GetEffectiveAlpha`, `frame:SetAttribute`, `frame:SetAttributeNoHandler`, `frame:ClearAttribute`, `frame:GetAttribute`, `OnAttributeChanged` (script), `SetModernScriptArgs`, `GetModernScriptArgs`, `SecureCmdOptionParse`, `RegisterStateDriver`, `UnregisterStateDriver`, `RegisterAttributeDriver`, `UnregisterAttributeDriver`, `RegisterUnitWatch`, `UnregisterUnitWatch`, `UnitWatchRegistered`, `SecureButton_GetAttribute`, `SecureButton_GetUnit`, `PreClick` (script), `PostClick` (script), `GetClickFrame` |
7171
| [FriendList](docs/API.md#friendlist) | `C_FriendList.GetFriendInfo`, `C_FriendList.GetFriendInfoByIndex`, `C_FriendList.GetNumFriends`, `C_FriendList.GetNumOnlineFriends`, `C_FriendList.GetNumWhoResults`, `C_FriendList.GetWhoInfo`, `C_FriendList.IsFriend`, `C_FriendList.IsIgnored`, `C_FriendList.IsIgnoredByGuid`, `C_FriendList.IsWhoQueryPending`, `C_FriendList.SendWhoQueryByName`, `C_FriendList.SetFriendNotes`, `C_FriendList.SetFriendNotesByIndex` |
7272
| [GameObject](docs/API.md#gameobject) | `C_GameObjectInfo.GetGameObjectInfoByID`, `C_GameObjectInfo.RequestLoadGameObjectByID`, `ClosestGameObjectPosition` |
7373
| [Glue](docs/API.md#glue) | `C_Glue.IsOnGlueScreen` |

docs/API.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ build instructions.
204204
- [`RegisterUnitWatch` / `UnregisterUnitWatch` / `UnitWatchRegistered`](#registerunitwatch--unregisterunitwatch--unitwatchregistered)
205205
- [`SecureButton_GetAttribute` / `SecureButton_GetUnit`](#securebutton_getattribute--securebutton_getunit)
206206
- [`PreClick` / `PostClick` button scripts](#preclick--postclick-button-scripts)
207+
- [`GetClickFrame(name)`](#getclickframename)
207208

208209
- [FriendList](#friendlist)
209210
- [`C_FriendList.SendWhoQueryByName(name)`](#c_friendlistsendwhoquerybynamename)
@@ -4995,6 +4996,21 @@ the normal use: run code just before or after a button's click action. (Modern
49954996
WoW fires them even with no `OnClick`; that path is not available on this
49964997
client.)
49974998

4999+
### `GetClickFrame(name)`
5000+
5001+
Returns the frame with the given global name. The `/click` command uses it to
5002+
change a frame name into the frame.
5003+
5004+
The function returns the frame only when `_G[name]` holds a real frame, and the
5005+
`GetName()` of that frame is equal to `name`. If the global holds a different
5006+
value, a plain table, or a frame with a different name, the function returns
5007+
nil. This name test makes sure that a changed global does not return the wrong
5008+
frame.
5009+
5010+
```lua
5011+
GetClickFrame("MyButton") -- the frame named "MyButton", or nil
5012+
```
5013+
49985014
## FriendList
49995015

50005016
### `C_FriendList.SendWhoQueryByName(name)`

src/frame/ClickFrame.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// This file is part of ClassicAPI.
2+
//
3+
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
4+
// of the GNU General Public License as published by the Free Software Foundation, either
5+
// version 3 of the License, or (at your option) any later version.
6+
//
7+
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9+
// PURPOSE. See the GNU General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU General Public License along with
12+
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
13+
14+
// `GetClickFrame(name)` — backport of the C global WoW registers for the
15+
// `/click` handler to resolve a frame by its global name. Vanilla 1.12
16+
// never shipped it; 3.3.5's `Script_GetClickFrame` (`0x00564130`) is the
17+
// reference.
18+
//
19+
// It lives in the DLL, not addon Lua, because Blizzard shipped it as an
20+
// engine-registered C global — the backport mirrors where the real
21+
// function lives, not the fact that Lua could fake it.
22+
//
23+
// Behaviour, matching the 3.3.5 resolver `FUN_00562990` (minus its private
24+
// name→frame memoization cache, which is only a perf optimization over the
25+
// global lookup):
26+
// 1. `_G[name]` — a plain global-table lookup.
27+
// 2. The value must be a frame wrapper table backed by a
28+
// `CFrameScriptObject`.
29+
// 3. Its real `GetName()` must equal `name`. This is the load-bearing
30+
// check: it stops a reassigned global (`_G["Foo"] = someOtherFrame`)
31+
// from resolving to a differently-named frame.
32+
// Return the frame on a match, otherwise nil.
33+
//
34+
// `GetName()` is the object's vtable slot +4 (`__thiscall`, returns
35+
// `const char *`) — verified from 1.12's own `Script_GetName`
36+
// (`0x0079FF60` / `0x007A1390`), which resolves self the same way and reads
37+
// the name from that slot. It's a base virtual present on every
38+
// `CFrameScriptObject` subtype, so calling it after `ResolveObject` is
39+
// safe without the RTTI subtype gate `Script_GetName` does first.
40+
41+
#include "Game.h"
42+
#include "Offsets.h"
43+
44+
#include <cstring>
45+
46+
namespace Frame::ClickFrame {
47+
48+
namespace {
49+
// CFrameScriptObject::GetName — vtable slot +4, thiscall, no args.
50+
using GetName_t = const char *(__thiscall *)(void *self);
51+
52+
const char *ObjectName(void *obj) {
53+
auto vtable = *reinterpret_cast<void ***>(obj);
54+
auto getName = reinterpret_cast<GetName_t>(vtable[1]);
55+
return getName(obj);
56+
}
57+
} // namespace
58+
59+
static int __fastcall Script_GetClickFrame(void *L) {
60+
using namespace Game::Lua;
61+
62+
if (GetTop(L) < 1) {
63+
Error(L, "Usage: GetClickFrame(\"name\")");
64+
return 0;
65+
}
66+
const char *name = ToString(L, 1);
67+
if (name == nullptr) {
68+
PushNil(L);
69+
return 1;
70+
}
71+
72+
// getglobal(name): push _G[name].
73+
PushString(L, name);
74+
GetTable(L, GLOBALS_INDEX);
75+
76+
if (Type(L, -1) != TYPE_TABLE) {
77+
PushNil(L);
78+
return 1;
79+
}
80+
81+
void *obj = ResolveObject(L, -1);
82+
if (obj != nullptr) {
83+
const char *objName = ObjectName(obj);
84+
if (objName != nullptr && std::strcmp(objName, name) == 0)
85+
// The wrapper table is still on top — return it as-is.
86+
return 1;
87+
}
88+
89+
PushNil(L);
90+
return 1;
91+
}
92+
93+
static void RegisterLuaFunctions() {
94+
Game::Lua::RegisterGlobalFunction("GetClickFrame", &Script_GetClickFrame);
95+
}
96+
97+
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
98+
99+
} // namespace Frame::ClickFrame

0 commit comments

Comments
 (0)