Skip to content

Commit ad83eb9

Browse files
committed
IsMouseButtonDown
Adds the modern (2.0.1+) query function backed by the same WH_GETMESSAGE hook stream that drives GLOBAL_MOUSE_DOWN/UP — state flips at the exact same point the events fire. Accepts the 1..5 ID form, the "LeftButton".."Button5" name form, or no arg for "any button held".
1 parent 8995e82 commit ad83eb9

3 files changed

Lines changed: 108 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
3535
| [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` |
3636
| [GameTooltip](docs/API.md#gametooltip) | `GameTooltip:GetGameObject`, `GameTooltip:GetItem`, `GameTooltip:GetOwner`, `GameTooltip:GetSpell`, `GameTooltip:GetUnitGUID`, `GameTooltip:HasGameObject`, `GameTooltip:HasItem`, `GameTooltip:HasSpell`, `GameTooltip:HasUnit`, `GameTooltip:SetEquipmentSet`, `GameTooltip:SetInventoryItemByID`, `GameTooltip:SetItemByGUID`, `GameTooltip:SetItemByID`, `GameTooltip:SetSpellByID`, `GameTooltip:SetTalentByID`, `GameTooltip:SetUnitAura` |
3737
| [Hooks](docs/API.md#hooks) | `hooksecurefunc` |
38-
| [Input](docs/API.md#input) | `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
38+
| [Input](docs/API.md#input) | `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsMouseButtonDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
3939
| [Instance](docs/API.md#instance) | `GetInstanceInfo` |
4040
| [Item](docs/API.md#item) | `C_Item.DoesItemExist`, `C_Item.DoesItemExistByID`, `C_Item.EquipItemByName`, `C_Item.GetCurrentItemLevel`, `C_Item.GetDetailedItemLevelInfo`, `C_Item.GetItemCount`, `C_Item.GetItemFamily`, `C_Item.GetItemGUID`, `C_Item.GetItemIcon`, `C_Item.GetItemIconByID`, `C_Item.GetItemID`, `C_Item.GetItemInfoInstant`, `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.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.IsItemOpenable`, `C_Item.IsLocked`, `C_Item.LockItem`, `C_Item.LockItemByGUID`, `C_Item.UnlockAllItems`, `C_Item.UnlockItem`, `C_Item.RequestLoadItemData`, `C_Item.RequestLoadItemDataByID`, `C_Item.UseItemByName`, `GetAuctionItemID`, `GetAuctionSellItemID`, `GetAverageItemLevel`, `GetCraftReagentItemID`, `GetInboxItemID`, `GetInventoryItemDurability`, `GetInventoryItemID`, `GetInventoryItemsForSlot`, `GetInventoryItemRepairCost`, `GetItemIcon`, `GetLootRollItemID`, `GetLootSlotItemID`, `GetMerchantItemID`, `GetQuestItemID`, `GetQuestLogItemID`, `GetTradePlayerItemID`, `GetTradeSkillItemID`, `GetTradeSkillReagentItemID`, `GetTradeTargetItemID`, `OffhandHasWeapon` |
4141
| [Macros](docs/API.md#macros) | `GetLooseMacroIcons`, `GetLooseMacroItemIcons`, `GetMacroIcons`, `GetMacroItemIcons`, `GetMacroSpell` |

docs/API.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ build instructions.
152152
- [`IsLeftControlKeyDown()` / `IsRightControlKeyDown()`](#isleftcontrolkeydown--isrightcontrolkeydown)
153153
- [`IsLeftAltKeyDown()` / `IsRightAltKeyDown()`](#isleftaltkeydown--isrightaltkeydown)
154154
- [`IsModifierKeyDown()`](#ismodifierkeydown)
155+
- [`IsMouseButtonDown([button])`](#ismousebuttondownbutton)
155156

156157
- [Instance](#instance)
157158
- [`GetInstanceInfo()`](#getinstanceinfo)
@@ -3337,6 +3338,41 @@ otherwise. Equivalent to
33373338
`IsShiftKeyDown() or IsControlKeyDown() or IsAltKeyDown()` but in one
33383339
call.
33393340

3341+
### `IsMouseButtonDown([button])`
3342+
3343+
Returns `true` if the given mouse button is currently held, `false`
3344+
otherwise. With no argument (or `nil`), returns `true` if **any**
3345+
mouse button is held.
3346+
3347+
`button` is either a 1-based ID or a name string:
3348+
3349+
| ID | Name |
3350+
|-----|------|
3351+
| `1` | `"LeftButton"` |
3352+
| `2` | `"RightButton"` |
3353+
| `3` | `"MiddleButton"` |
3354+
| `4` | `"Button4"` (XBUTTON1 / first side button) |
3355+
| `5` | `"Button5"` (XBUTTON2 / second side button) |
3356+
3357+
Unrecognized IDs / names return `false` (matches modern semantics:
3358+
the named button just isn't held — bad input doesn't fall through
3359+
to the any-button check).
3360+
3361+
State is maintained from the same `WH_GETMESSAGE` hook that drives
3362+
`GLOBAL_MOUSE_DOWN` / `GLOBAL_MOUSE_UP`, so press/release transitions
3363+
register at exactly the moment the corresponding event fires. Hook
3364+
scopes to WoW's message queue — clicks made while the game is
3365+
alt-tabbed out don't deliver, which means the state can stay
3366+
"down" if a button was held while focus left the window. Most uses
3367+
of `IsMouseButtonDown` are gated behind a frame that just received
3368+
mouse focus, so this rarely matters in practice.
3369+
3370+
```lua
3371+
if IsMouseButtonDown("RightButton") then
3372+
-- right-button-held bindings
3373+
end
3374+
```
3375+
33403376
## Instance
33413377

33423378
### `GetInstanceInfo()`

src/input/GlobalMouse.cpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
// per-frame `OnMouseDown` scripts — these fire even when the click
1919
// misses any UI frame.
2020
//
21+
// `IsMouseButtonDown([button])` — query the current held state of a
22+
// specific button by name or 1-based ID, or "any button held" when
23+
// called with no args. State is maintained from the same hook stream
24+
// as the events (single source of truth — events firing and the
25+
// state being readable happen at the same point in the pipeline).
26+
//
2127
// Same `WH_GETMESSAGE` thread-message hook pattern as
2228
// [Input::Modifier](Modifier.cpp): we intercept the WM_*BUTTON*
2329
// messages on the engine's main thread *before* dispatch. The hook
@@ -28,6 +34,9 @@
2834
#include "Game.h"
2935
#include "event/Custom.h"
3036

37+
#include <cstdint>
38+
#include <cstring>
39+
3140
#include <windows.h>
3241

3342
namespace Input::GlobalMouse {
@@ -37,6 +46,19 @@ namespace {
3746
constexpr const char *kDownEvent = "GLOBAL_MOUSE_DOWN";
3847
constexpr const char *kUpEvent = "GLOBAL_MOUSE_UP";
3948

49+
// Held-state bitmap — bit `i` set means the button with 1-based
50+
// `IsMouseButtonDown` ID `i+1` is currently held. Modern API IDs:
51+
// 1 = LeftButton, 2 = RightButton, 3 = MiddleButton,
52+
// 4 = Button4 (XBUTTON1), 5 = Button5 (XBUTTON2).
53+
constexpr int BIT_LEFT_BUTTON = 0;
54+
constexpr int BIT_RIGHT_BUTTON = 1;
55+
constexpr int BIT_MIDDLE_BUTTON = 2;
56+
constexpr int BIT_BUTTON4 = 3;
57+
constexpr int BIT_BUTTON5 = 4;
58+
constexpr uint32_t MASK_ANY_BUTTON = 0x1F;
59+
60+
uint32_t g_mouseButtonMask = 0;
61+
4062
HHOOK g_msgHook = nullptr;
4163

4264
// Maps WM_*BUTTON{DOWN,UP} → ("LeftButton"|..., isDown). Returns
@@ -60,16 +82,60 @@ const char *DecodeButton(UINT msg, WPARAM wParam, bool *outDown) {
6082
}
6183
}
6284

85+
// Maps a "LeftButton"/.../"Button5" name to its bit index. -1 for
86+
// any other string.
87+
int NameToBitIdx(const char *name) {
88+
if (name == nullptr)
89+
return -1;
90+
if (std::strcmp(name, "LeftButton") == 0) return BIT_LEFT_BUTTON;
91+
if (std::strcmp(name, "RightButton") == 0) return BIT_RIGHT_BUTTON;
92+
if (std::strcmp(name, "MiddleButton") == 0) return BIT_MIDDLE_BUTTON;
93+
if (std::strcmp(name, "Button4") == 0) return BIT_BUTTON4;
94+
if (std::strcmp(name, "Button5") == 0) return BIT_BUTTON5;
95+
return -1;
96+
}
97+
6398
void ProcessMouseMessage(UINT msg, WPARAM wParam) {
6499
bool down = false;
65100
const char *name = DecodeButton(msg, wParam, &down);
66101
if (name == nullptr)
67102
return;
103+
104+
const int bitIdx = NameToBitIdx(name);
105+
if (bitIdx >= 0) {
106+
const uint32_t bit = 1u << bitIdx;
107+
if (down) g_mouseButtonMask |= bit;
108+
else g_mouseButtonMask &= ~bit;
109+
}
110+
68111
const int slot = Event::Custom::Lookup(down ? kDownEvent : kUpEvent);
69112
if (slot >= 0)
70113
Event::Custom::Fire(slot, "%s", name);
71114
}
72115

116+
int __fastcall Script_IsMouseButtonDown(void *L) {
117+
bool down;
118+
if (Game::Lua::GetTop(L) == 0) {
119+
// No-arg form: any button held.
120+
down = (g_mouseButtonMask & MASK_ANY_BUTTON) != 0;
121+
} else {
122+
int bitIdx = -1;
123+
if (Game::Lua::IsNumber(L, 1)) {
124+
const int id = static_cast<int>(Game::Lua::ToNumber(L, 1));
125+
if (id >= 1 && id <= 5)
126+
bitIdx = id - 1;
127+
} else if (Game::Lua::IsString(L, 1)) {
128+
bitIdx = NameToBitIdx(Game::Lua::ToString(L, 1));
129+
}
130+
// Unrecognized button (bad id, unknown name) → false, matching
131+
// the modern API's "did this specific button happen to be held"
132+
// semantic. Bad input doesn't promote to the any-button check.
133+
down = (bitIdx >= 0) && (g_mouseButtonMask & (1u << bitIdx)) != 0;
134+
}
135+
Game::Lua::PushBool(L, down);
136+
return 1;
137+
}
138+
73139
LRESULT CALLBACK GetMsgHook(int code, WPARAM wParam, LPARAM lParam) {
74140
// Only process when the message is being removed from the queue
75141
// (`PM_REMOVE` / `GetMessage`); `PM_NOREMOVE` peeks would deliver
@@ -88,7 +154,11 @@ void InstallHook() {
88154
GetCurrentThreadId());
89155
}
90156

91-
void RegisterLuaFunctions() { InstallHook(); }
157+
void RegisterLuaFunctions() {
158+
Game::Lua::RegisterGlobalFunction("IsMouseButtonDown",
159+
&Script_IsMouseButtonDown);
160+
InstallHook();
161+
}
92162

93163
const Event::Custom::AutoReserve _reserveDown{kDownEvent};
94164
const Event::Custom::AutoReserve _reserveUp{kUpEvent};

0 commit comments

Comments
 (0)