Skip to content

Commit 4daf6b9

Browse files
committed
input: add GetMouseButtonClicked()
Returns the button name of the most recent mouse-button message ("LeftButton"/.../"Button5"), or nil before any click. Rides the existing WH_GETMESSAGE stream in GlobalMouse that already backs GLOBAL_MOUSE_DOWN/UP and IsMouseButtonDown, so no engine hook is needed: g_lastButton is captured on every press/release, and the Script fn pushes it (null tail-jumps to pushnil for the "no click yet" case). The hook runs at message-dequeue, synchronously before the engine dispatches the click to the Lua handler, so the value reflects the button of the handler currently running (and is readable from nested helpers where arg1 isn't in scope). Not cleared between events -- no "handler ended" signal in the 1.12 stream -- so outside a mouse handler it returns the last-clicked button rather than nil; documented.
1 parent 4c6c9de commit 4daf6b9

3 files changed

Lines changed: 59 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
3939
| [Gossip](docs/API.md#gossip) | `C_GossipInfo.CloseGossip`, `C_GossipInfo.GetActiveQuests`, `C_GossipInfo.GetAvailableQuests`, `C_GossipInfo.GetNumActiveQuests`, `C_GossipInfo.GetNumAvailableQuests`, `C_GossipInfo.GetNumOptions`, `C_GossipInfo.GetOptions`, `C_GossipInfo.GetText`, `C_GossipInfo.SelectActiveQuest`, `C_GossipInfo.SelectAvailableQuest`, `C_GossipInfo.SelectOption`, `C_GossipInfo.SelectOptionByIndex` |
4040
| [GameTooltip](docs/API.md#gametooltip) | `GameTooltip:GetGameObject`, `GameTooltip:GetItem`, `GameTooltip:GetOwner`, `GameTooltip:GetSpell`, `GameTooltip:GetUnitGUID`, `GameTooltip:HasGameObject`, `GameTooltip:HasItem`, `GameTooltip:HasSpell`, `GameTooltip:HasUnit`, `GameTooltip:IsEquippedItem`, `GameTooltip:SetEquipmentSet`, `GameTooltip:SetHyperlinkCompareItem`, `GameTooltip:SetInventoryItemByID`, `GameTooltip:SetItemByGUID`, `GameTooltip:SetItemByID`, `GameTooltip:SetSpellByID`, `GameTooltip:SetTalentByID`, `GameTooltip:SetUnitAura`, `OnTooltipSetItem` (script) |
4141
| [Hooks](docs/API.md#hooks) | `hooksecurefunc` |
42-
| [Input](docs/API.md#input) | `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsMouseButtonDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
42+
| [Input](docs/API.md#input) | `GetMouseButtonClicked`, `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsMouseButtonDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
4343
| [Instance](docs/API.md#instance) | `GetInstanceInfo` |
4444
| [Item](docs/API.md#item) | `C_Item.DoesItemExist`, `C_Item.DoesItemExistByID`, `C_Item.EquipItemByName`, `C_Item.GetCurrentItemLevel`, `C_Item.GetDetailedItemLevelInfo`, `C_Item.GetEnchantInfo`, `C_Item.GetItemCount`, `C_Item.GetItemFamily`, `C_Item.GetItemGUID`, `C_Item.GetItemIcon`, `C_Item.GetItemIconByID`, `C_Item.GetItemID`, `C_Item.GetItemInfoInstant`, `C_Item.GetItemInventorySlotInfo`, `C_Item.GetItemInventorySlotKey`, `C_Item.GetItemInventoryType`, `C_Item.GetItemInventoryTypeByID`, `C_Item.GetItemLink`, `C_Item.GetItemLocation`, `C_Item.GetItemMaxStackSize`, `C_Item.GetItemMaxStackSizeByID`, `C_Item.GetItemName`, `C_Item.GetItemNameByID`, `C_Item.GetItemQuality`, `C_Item.GetItemQualityByID`, `C_Item.GetItemSellPrice`, `C_Item.GetItemSellPriceByID`, `C_Item.GetItemSetID`, `C_Item.GetItemSetIDByID`, `C_Item.GetItemSetInfo`, `C_Item.GetItemSpell`, `C_Item.GetItemStatDelta`, `C_Item.GetItemStats`, `C_Item.GetItemSubClassInfo`, `C_Item.GetItemUniqueness`, `C_Item.GetItemUniquenessByID`, `C_Item.GetStackCount`, `C_Item.GetWeaponEnchantInfo`, `C_Item.IsBound`, `C_Item.IsEquippableItem`, `C_Item.IsEquippedItem`, `C_Item.IsItemDataCached`, `C_Item.IsItemDataCachedByID`, `C_Item.IsItemGUIDInInventory`, `C_Item.IsItemOpenable`, `C_Item.IsLocked`, `C_Item.LockItem`, `C_Item.LockItemByGUID`, `C_Item.UnlockAllItems`, `C_Item.UnlockItem`, `C_Item.RequestLoadItemData`, `C_Item.RequestLoadItemDataByID`, `C_Item.UseAtCursor`, `C_Item.UseAtUnit`, `C_Item.UseItemByName`, `GetAuctionItemID`, `GetAuctionSellItemID`, `GetAverageItemLevel`, `GetCraftReagentItemID`, `GetInboxItemID`, `GetInventoryItemDurability`, `GetInventoryItemID`, `GetInventoryItemsForSlot`, `GetInventoryItemRepairCost`, `GetItemIcon`, `GetLootRollItemID`, `GetLootSlotItemID`, `GetMerchantItemID`, `GetQuestItemID`, `GetQuestLogItemID`, `GetTradePlayerItemID`, `GetTradeSkillItemID`, `GetTradeSkillReagentItemID`, `GetTradeTargetItemID`, `OffhandHasWeapon` |
4545
| [Loot](docs/API.md#loot) | `C_Loot.GetNearbyLootableUnits`, `C_Loot.GetLastScanResults`, `C_Loot.IsScanInProgress`, `C_Loot.LootUnit`, `C_Loot.LootUnitItem`, `C_Loot.ScanNearbyLoot` |

docs/API.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ build instructions.
194194
- [`IsLeftAltKeyDown()` / `IsRightAltKeyDown()`](#isleftaltkeydown--isrightaltkeydown)
195195
- [`IsModifierKeyDown()`](#ismodifierkeydown)
196196
- [`IsMouseButtonDown([button])`](#ismousebuttondownbutton)
197+
- [`GetMouseButtonClicked()`](#getmousebuttonclicked)
197198

198199
- [Instance](#instance)
199200
- [`GetInstanceInfo()`](#getinstanceinfo)
@@ -4297,6 +4298,34 @@ if IsMouseButtonDown("RightButton") then
42974298
end
42984299
```
42994300

4301+
### `GetMouseButtonClicked()`
4302+
4303+
Returns the name of the button from the most recent mouse-button
4304+
message (`"LeftButton"`, `"RightButton"`, `"MiddleButton"`,
4305+
`"Button4"`, `"Button5"`), or `nil` before any click has occurred.
4306+
4307+
Modern addons call this inside a mouse handler (`OnClick`,
4308+
`OnMouseDown`, `OnMouseUp`, `OnDragStart`, …) to learn which button
4309+
drove it — an alternative to reading vanilla's `arg1`, and readable
4310+
from nested helper functions where `arg1` isn't in scope. The value
4311+
is captured by the same `WH_GETMESSAGE` hook behind
4312+
`GLOBAL_MOUSE_DOWN` / `GLOBAL_MOUSE_UP`, at message-dequeue —
4313+
synchronously *before* the engine dispatches the click to the Lua
4314+
handler — so it reflects the button of the handler currently running.
4315+
4316+
It is not cleared between events, so outside a mouse handler it
4317+
returns the last-clicked button rather than `nil` (the 1.12 message
4318+
stream carries no "handler ended" signal to clear on). In practice
4319+
it's only meaningful inside a mouse handler, matching how it's used.
4320+
4321+
```lua
4322+
button:SetScript("OnClick", function()
4323+
if GetMouseButtonClicked() == "RightButton" then
4324+
-- right-click behavior
4325+
end
4326+
end)
4327+
```
4328+
43004329
## Instance
43014330

43024331
### `GetInstanceInfo()`

src/input/GlobalMouse.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
// as the events (single source of truth — events firing and the
2525
// state being readable happen at the same point in the pipeline).
2626
//
27+
// `GetMouseButtonClicked()` — the button name of the most recent
28+
// mouse button message (`"LeftButton"` / … / `"Button5"`), or nil
29+
// before any click. Modern addons call it inside a mouse handler to
30+
// learn which button drove it. The `WH_GETMESSAGE` hook runs at
31+
// message-dequeue, synchronously *before* the engine dispatches that
32+
// click to the Lua handler, so the value captured there is the right
33+
// one throughout the handler that the same message triggers.
34+
//
2735
// Same `WH_GETMESSAGE` thread-message hook pattern as
2836
// [Input::Modifier](Modifier.cpp): we intercept the WM_*BUTTON*
2937
// messages on the engine's main thread *before* dispatch. The hook
@@ -59,6 +67,13 @@ constexpr uint32_t MASK_ANY_BUTTON = 0x1F;
5967

6068
uint32_t g_mouseButtonMask = 0;
6169

70+
// Name of the button from the most recent mouse-button message, or
71+
// nullptr before any click. Points at one of DecodeButton's string
72+
// literals (stable for the process lifetime), so storing the pointer
73+
// is safe. `lua_pushstring(L, nullptr)` tail-jumps to pushnil, so a
74+
// null here surfaces as nil to the Lua caller with no extra branch.
75+
const char *g_lastButton = nullptr;
76+
6277
HHOOK g_msgHook = nullptr;
6378

6479
// Maps WM_*BUTTON{DOWN,UP} → ("LeftButton"|..., isDown). Returns
@@ -101,6 +116,11 @@ void ProcessMouseMessage(UINT msg, WPARAM wParam) {
101116
if (name == nullptr)
102117
return;
103118

119+
// Capture for GetMouseButtonClicked on both press and release —
120+
// during an OnClick / OnMouseUp handler the "clicked" button is the
121+
// one just released, same as the press for a normal click.
122+
g_lastButton = name;
123+
104124
const int bitIdx = NameToBitIdx(name);
105125
if (bitIdx >= 0) {
106126
const uint32_t bit = 1u << bitIdx;
@@ -136,6 +156,13 @@ int __fastcall Script_IsMouseButtonDown(void *L) {
136156
return 1;
137157
}
138158

159+
int __fastcall Script_GetMouseButtonClicked(void *L) {
160+
// Null → pushnil (the engine's pushstring tail-jumps on NULL), so
161+
// callers before any click see nil, matching the modern API.
162+
Game::Lua::PushString(L, g_lastButton);
163+
return 1;
164+
}
165+
139166
LRESULT CALLBACK GetMsgHook(int code, WPARAM wParam, LPARAM lParam) {
140167
// Only process when the message is being removed from the queue
141168
// (`PM_REMOVE` / `GetMessage`); `PM_NOREMOVE` peeks would deliver
@@ -157,6 +184,8 @@ void InstallHook() {
157184
void RegisterLuaFunctions() {
158185
Game::Lua::RegisterGlobalFunction("IsMouseButtonDown",
159186
&Script_IsMouseButtonDown);
187+
Game::Lua::RegisterGlobalFunction("GetMouseButtonClicked",
188+
&Script_GetMouseButtonClicked);
160189
InstallHook();
161190
}
162191

0 commit comments

Comments
 (0)