Skip to content

UiHandle

A reference to one native UI window or control. The handle behind every native-UI event payload and every eq2.ui.* / eq2.quest.* / eq2.loot.* / eq2.travel.* window lookup.

UiHandle is a real sol2 usertype, not a plain table — the one exception to the "call members with ." rule everywhere else on this site. Call its members with ::

local w = eq2.ui.window("Choice")
if w and w:visible() then
  w:click()
end

Returned by eq2.ui.window() / eq2.ui.find(), by eq2.quest.offer_window() / .reward_window(), by eq2.loot.window(), by eq2.reply_dialog.window(), and as the h field on on_ui_action / on_ui_showhide event payloads.

Handles are opaque and can go stale

A UiHandle is a slot in clockwork's own handle table, not a raw pointer — that table has 4096 slots, shared across the whole client. Every method below is fail-soft: a stale, wrong-kind, or exhausted handle returns nil / false rather than erroring, so check the return value, not just whether the call happened.

Arm event listeners only while you need them

on_ui_action mints a fresh handle per observed event. An always-on subscription across a long session can exhaust the table, after which every handle request fails for the rest of the session. Subscribe when your window opens, unsubscribe when it closes. See Native UI.

Reading

h:visible() / h:enabled()

bool. The control's visible and enabled flags.

h:text()

string or nil. The Label-class display string, when this control has one. nil on a read failure — distinct from an empty string.

h:name()

string or nil. The control's own backing-object name (e.g. "RewardPack" for the quest offer window) — not the same as text(), which is display text.

h:rows()

array of string. Row text for a List-class control.

h:selected()

number. 0-based selected row index for a List-class control. -1 when nothing is selected or the read failed.

h:children()

array of {name, handle}, where handle is a UiHandle for each direct child.

for _, c in ipairs(w:children()) do
  eq2.print(c.name)
end

Acting

h:click()

bool. Fires the control's OnPress event. Works for most simple buttons — but see click_full() below for the one that doesn't.

h:click_full()

bool. The unified native click for the game's own Button class: drives the real OnActivateOnDeactivateOnPress sequence through the game's own emitters, rather than OnPress alone.

Prefer this when click() does nothing

Some buttons gate their action on activation state, so a bare click() is silently inert. The Fast Travel destination pin and the map's "Next" confirm button both need click_full().

h:button_activate(activate)

bool. Fires OnActivate (true) or OnDeactivate (false) alone, without the press. Mostly a recon primitive — click_full() is what reproduces a real click.

h:emit(event_name)

bool. Replays a named native event by re-issuing the game's own broadcaster call with whatever arguments were captured the last time that event genuinely fired anywhere — no fabricated arguments. Returns false if nothing has been captured for event_name yet, i.e. it needs "seeding" by a real occurrence first. click_full() is self-contained and needs no seeding, which is why it's preferred when it applies.

h:set_text(s)

bool. Sets a control's display text.

h:select(index)

bool. Selects a row in a List-class control by index.

h:select_row(index)

bool. Sets a Table SIDL control's SelectedRow property. index is the 0-based native display-row index; pass -1 to clear the selection. This is the Broker/Table row-selection primitive — there is no mouse-hit-test equivalent for a Table row.

Warning

Static, not yet live-confirmed against every Table control.

h:check(b)

bool. Sets a checkbox-class control's checked state.

h:scroll(delta) / h:scroll_to(y)

bool. Adjusts a ScrollPage control's scroll offset — relative or absolute — and kicks a relayout. self must be the ScrollPage control itself, not the Table/list it scrolls.

Warning

Static, not yet live-confirmed.

h:find(path)

UiHandle or nil. Recursive child search from this control, through the game's own child-lookup. See eq2.ui.find() for the top-level equivalent starting from the UI root.

Struct inspection

A guarded raw-read/write surface for confirming struct offsets live, the UiHandle counterpart to actor. Every read is bounds-checked and SEH-guarded — a bad offset returns nil/false instead of crashing the client — but nothing here decodes a field for you; you are reading raw bytes at an offset you already suspect is meaningful.

Recon primitives, not a stable API

These exist to confirm an offset from Lua instead of attaching a debugger. Don't ship a feature built on a hand-typed offset without confirming it first, and expect offsets to move across game patches.

h:peek_u8(off) / h:peek_u16(off) / h:peek_u32(off)

number or nil. Unsigned read at control + off.

h:peek_i32(off)

number or nil. Signed 32-bit read.

h:peek_f32(off)

number or nil. 32-bit float read.

h:peek_u64_hex(off)

string or nil. 64-bit read, formatted as "0x...".

h:poke_u8(off, val) / h:poke_u16(off, val) / h:poke_u32(off, val) / h:poke_i32(off, val)

bool. Writes val at control + off. Pure field write, no native call — nothing re-derives dependent state or triggers a relayout on its own.

h:ptr()

string or nil. This control's own address, as "0x..." — for diagnostics and log correlation, not for arithmetic in Lua.

h:vtbl(slot)

string or nil. Absolute address at vtable slot slot (byte offset), as "0x...".

h:vtbl_rva(slot)

number or nil. Same read as vtbl(), but as an EverQuest2.exe-relative RVA — paste-able straight into Ghidra, and stable across ASLR-affected launches where vtbl()'s absolute address isn't. nil when the slot doesn't resolve to an address inside the game module (e.g. a thunk into a system DLL).

h:peek_rva(off)

number or nil. Reads the pointer at control + off and reports it as a RVA — the vtbl_rva() companion for a vfptr that isn't at offset 0 (multiple inheritance can put a second vfptr partway into an object). nil when the value isn't a pointer inside the game module, so a data field that merely looks like a pointer reads nil rather than a bogus RVA.

h:adopt_at(off)

UiHandle or nil. Reads the pointer at control + off and adopts the object it points to as a new handle, so every peek_*/poke_* above then works on it directly.

Exists for containers that keep their contents in a private array instead of the generic child list children() walks — a control can report #children() == 0 while still holding real contents behind a raw pointer field.

One handle per adopted object

The handle table is keyed by pointer, and adopting an object that is already held as a different handle silently repoints that other handle. Only adopt an address nothing else currently holds a handle to.

h:peek_deref_u32(off, sub_off) / h:peek_deref_f32(off, sub_off) / h:peek_deref_u64_hex(off, sub_off)

Reads at *(control + off) + sub_off — one pointer hop past adopt_at's single dereference. The right tool for an array of pointers whose contents you want without minting a handle for each element: adopt_at needs the target's first 8 bytes to look like a readable vtable to mint, and an array slot that's currently empty (null first bytes) fails that check even though "empty slot" is a legitimate state, not an error.

h:peek_deref_rva(off, sub_off)

number or nil. Same one-hop read as above, reported as an RVA — same module-membership guard as peek_rva().

h:adopt_deref(off, sub_off)

UiHandle or nil. Adopts the element pointer found via the same one-hop read peek_deref_* uses, rather than reading a value out of it. The companion to peek_deref_* for "give me a handle to the thing this pointer-to-a-pointer refers to," and — unlike adopt_at on the same field — correctly returns nil on a legitimately empty array slot instead of failing to mint.

See also

  • Native UIeq2.ui.windows() / .window() / .find(), and the on_ui_action / on_ui_showhide events that hand you a UiHandle.
  • actor struct inspection — the same guarded raw-read pattern, applied to actors instead of controls.