From bdef1f9412d21757bc4a21ed905daff32fd0df27 Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Sun, 1 Mar 2026 18:50:55 +0800 Subject: feat: add support for keyboard and mouse input --- Minecraft.Client/Windows64/KeyboardMouseInput.cpp | 217 +++++++++++++++++++++ Minecraft.Client/Windows64/KeyboardMouseInput.h | 76 ++++++++ Minecraft.Client/Windows64/Windows64_Minecraft.cpp | 84 ++++++++ 3 files changed, 377 insertions(+) create mode 100644 Minecraft.Client/Windows64/KeyboardMouseInput.cpp create mode 100644 Minecraft.Client/Windows64/KeyboardMouseInput.h (limited to 'Minecraft.Client/Windows64') diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.cpp b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp new file mode 100644 index 00000000..e67946da --- /dev/null +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp @@ -0,0 +1,217 @@ +#include "stdafx.h" + +#ifdef _WINDOWS64 + +#include "KeyboardMouseInput.h" + +KeyboardMouseInput KMInput; + +KeyboardMouseInput::KeyboardMouseInput() + : m_mouseDeltaX(0.0f) + , m_mouseDeltaY(0.0f) + , m_mouseDeltaXAccum(0.0f) + , m_mouseDeltaYAccum(0.0f) + , m_scrollDelta(0) + , m_scrollDeltaAccum(0) + , m_captured(false) + , m_hWnd(NULL) + , m_initialized(false) +{ + memset(m_keyState, 0, sizeof(m_keyState)); + memset(m_keyStatePrev, 0, sizeof(m_keyStatePrev)); + memset(m_mouseButtons, 0, sizeof(m_mouseButtons)); + memset(m_mouseButtonsPrev, 0, sizeof(m_mouseButtonsPrev)); +} + +KeyboardMouseInput::~KeyboardMouseInput() +{ + if (m_captured) + { + SetCapture(false); + } +} + +void KeyboardMouseInput::Init(HWND hWnd) +{ + m_hWnd = hWnd; + m_initialized = true; + + // Register for raw mouse input + RAWINPUTDEVICE rid; + rid.usUsagePage = HID_USAGE_PAGE_GENERIC; + rid.usUsage = HID_USAGE_GENERIC_MOUSE; + rid.dwFlags = 0; + rid.hwndTarget = hWnd; + RegisterRawInputDevices(&rid, 1, sizeof(rid)); +} + +void KeyboardMouseInput::Tick() +{ + // Snapshot accumulated mouse deltas + m_mouseDeltaX = m_mouseDeltaXAccum; + m_mouseDeltaY = m_mouseDeltaYAccum; + m_mouseDeltaXAccum = 0.0f; + m_mouseDeltaYAccum = 0.0f; + + // Snapshot scroll delta + m_scrollDelta = m_scrollDeltaAccum; + m_scrollDeltaAccum = 0; + + // Keep cursor pinned to center while captured + if (m_captured) + CenterCursor(); +} + +void KeyboardMouseInput::EndFrame() +{ + // Advance previous state for next frame's edge detection. + // Must be called AFTER all consumers have read IsKeyPressed/Released etc. + memcpy(m_keyStatePrev, m_keyState, sizeof(m_keyState)); + memcpy(m_mouseButtonsPrev, m_mouseButtons, sizeof(m_mouseButtons)); +} + +void KeyboardMouseInput::OnKeyDown(WPARAM vk) +{ + if (vk < 256) + { + m_keyState[vk] = true; + } +} + +void KeyboardMouseInput::OnKeyUp(WPARAM vk) +{ + if (vk < 256) + { + m_keyState[vk] = false; + } +} + +void KeyboardMouseInput::OnRawMouseInput(LPARAM lParam) +{ + if (!m_captured) return; + + UINT dwSize = 0; + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); + + BYTE* lpb = (BYTE*)alloca(dwSize); + if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) + return; + + RAWINPUT* raw = (RAWINPUT*)lpb; + if (raw->header.dwType == RIM_TYPEMOUSE) + { + if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) + { + m_mouseDeltaXAccum += (float)raw->data.mouse.lLastX; + m_mouseDeltaYAccum += (float)raw->data.mouse.lLastY; + } + } +} + +void KeyboardMouseInput::OnMouseButton(int button, bool down) +{ + if (button >= 0 && button < 3) + { + m_mouseButtons[button] = down; + } +} + +void KeyboardMouseInput::OnMouseWheel(int delta) +{ + m_scrollDeltaAccum += delta; +} + +void KeyboardMouseInput::ClearAllState() +{ + memset(m_keyState, 0, sizeof(m_keyState)); + memset(m_mouseButtons, 0, sizeof(m_mouseButtons)); + m_mouseDeltaXAccum = 0.0f; + m_mouseDeltaYAccum = 0.0f; + m_scrollDeltaAccum = 0; +} + +// Key queries +bool KeyboardMouseInput::IsKeyDown(int vk) const +{ + if (vk < 0 || vk >= 256) return false; + return m_keyState[vk]; +} + +bool KeyboardMouseInput::IsKeyPressed(int vk) const +{ + if (vk < 0 || vk >= 256) return false; + return m_keyState[vk] && !m_keyStatePrev[vk]; +} + +bool KeyboardMouseInput::IsKeyReleased(int vk) const +{ + if (vk < 0 || vk >= 256) return false; + return !m_keyState[vk] && m_keyStatePrev[vk]; +} + +// Mouse button queries +bool KeyboardMouseInput::IsMouseDown(int btn) const +{ + if (btn < 0 || btn >= 3) return false; + return m_mouseButtons[btn]; +} + +bool KeyboardMouseInput::IsMousePressed(int btn) const +{ + if (btn < 0 || btn >= 3) return false; + return m_mouseButtons[btn] && !m_mouseButtonsPrev[btn]; +} + +bool KeyboardMouseInput::IsMouseReleased(int btn) const +{ + if (btn < 0 || btn >= 3) return false; + return !m_mouseButtons[btn] && m_mouseButtonsPrev[btn]; +} + +// Delta queries +float KeyboardMouseInput::GetMouseDeltaX() const { return m_mouseDeltaX; } +float KeyboardMouseInput::GetMouseDeltaY() const { return m_mouseDeltaY; } +int KeyboardMouseInput::GetScrollDelta() const { return m_scrollDelta; } + +// Mouse capture +void KeyboardMouseInput::SetCapture(bool capture) +{ + if (capture == m_captured) return; + m_captured = capture; + + if (capture) + { + ShowCursor(FALSE); + RECT rect; + GetClientRect(m_hWnd, &rect); + POINT topLeft = { rect.left, rect.top }; + POINT bottomRight = { rect.right, rect.bottom }; + ClientToScreen(m_hWnd, &topLeft); + ClientToScreen(m_hWnd, &bottomRight); + RECT screenRect = { topLeft.x, topLeft.y, bottomRight.x, bottomRight.y }; + ClipCursor(&screenRect); + CenterCursor(); + + // Flush accumulated deltas so the snap-to-center doesn't cause a jump + m_mouseDeltaXAccum = 0.0f; + m_mouseDeltaYAccum = 0.0f; + } + else + { + ShowCursor(TRUE); + ClipCursor(NULL); + } +} + +bool KeyboardMouseInput::IsCaptured() const { return m_captured; } + +void KeyboardMouseInput::CenterCursor() +{ + RECT rect; + GetClientRect(m_hWnd, &rect); + POINT center = { (rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2 }; + ClientToScreen(m_hWnd, ¢er); + SetCursorPos(center.x, center.y); +} + +#endif // _WINDOWS64 diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.h b/Minecraft.Client/Windows64/KeyboardMouseInput.h new file mode 100644 index 00000000..722c9f3d --- /dev/null +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.h @@ -0,0 +1,76 @@ +#pragma once + +#ifdef _WINDOWS64 + +#include + +// HID usage page and usage for raw input registration +#ifndef HID_USAGE_PAGE_GENERIC +#define HID_USAGE_PAGE_GENERIC ((USHORT)0x01) +#endif +#ifndef HID_USAGE_GENERIC_MOUSE +#define HID_USAGE_GENERIC_MOUSE ((USHORT)0x02) +#endif + +class KeyboardMouseInput +{ +public: + KeyboardMouseInput(); + ~KeyboardMouseInput(); + + void Init(HWND hWnd); + void Tick(); + void EndFrame(); + + // Called from WndProc + void OnKeyDown(WPARAM vk); + void OnKeyUp(WPARAM vk); + void OnRawMouseInput(LPARAM lParam); + void OnMouseButton(int button, bool down); + void OnMouseWheel(int delta); + void ClearAllState(); + + // Key state queries (call after Tick) + bool IsKeyDown(int vk) const; + bool IsKeyPressed(int vk) const; + bool IsKeyReleased(int vk) const; + + // Mouse button queries: 0=left, 1=right, 2=middle + bool IsMouseDown(int btn) const; + bool IsMousePressed(int btn) const; + bool IsMouseReleased(int btn) const; + + // Mouse deltas (consumed each Tick) + float GetMouseDeltaX() const; + float GetMouseDeltaY() const; + int GetScrollDelta() const; + + // Mouse capture for FPS look + void SetCapture(bool capture); + bool IsCaptured() const; + +private: + void CenterCursor(); + + bool m_keyState[256]; + bool m_keyStatePrev[256]; + + bool m_mouseButtons[3]; + bool m_mouseButtonsPrev[3]; + + float m_mouseDeltaX; + float m_mouseDeltaY; + float m_mouseDeltaXAccum; + float m_mouseDeltaYAccum; + + int m_scrollDelta; + int m_scrollDeltaAccum; + + bool m_captured; + HWND m_hWnd; + bool m_initialized; +}; + +extern KeyboardMouseInput KMInput; + +#endif // _WINDOWS64 diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index d2251e4e..4002fc3b 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -339,6 +339,63 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_DESTROY: PostQuitMessage(0); break; + + // Keyboard/Mouse input handling + case WM_KEYDOWN: + if (!(lParam & 0x40000000)) // ignore auto-repeat + KMInput.OnKeyDown(wParam); + break; + case WM_KEYUP: + KMInput.OnKeyUp(wParam); + break; + case WM_SYSKEYDOWN: + if (wParam == VK_MENU) // Alt key + { + if (!(lParam & 0x40000000)) + KMInput.OnKeyDown(wParam); + return 0; // prevent default Alt behavior + } + return DefWindowProc(hWnd, message, wParam, lParam); + case WM_SYSKEYUP: + if (wParam == VK_MENU) + { + KMInput.OnKeyUp(wParam); + return 0; + } + return DefWindowProc(hWnd, message, wParam, lParam); + case WM_INPUT: + KMInput.OnRawMouseInput(lParam); + break; + case WM_LBUTTONDOWN: + KMInput.OnMouseButton(0, true); + break; + case WM_LBUTTONUP: + KMInput.OnMouseButton(0, false); + break; + case WM_RBUTTONDOWN: + KMInput.OnMouseButton(1, true); + break; + case WM_RBUTTONUP: + KMInput.OnMouseButton(1, false); + break; + case WM_MBUTTONDOWN: + KMInput.OnMouseButton(2, true); + break; + case WM_MBUTTONUP: + KMInput.OnMouseButton(2, false); + break; + case WM_MOUSEWHEEL: + KMInput.OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + break; + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_INACTIVE) + KMInput.SetCapture(false); + break; + case WM_KILLFOCUS: + KMInput.SetCapture(false); + KMInput.ClearAllState(); + break; + default: return DefWindowProc(hWnd, message, wParam, lParam); } @@ -715,6 +772,9 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // Set the number of possible joypad layouts that the user can switch between, and the number of actions InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU); + // Initialize keyboard/mouse input + KMInput.Init(g_hWnd); + // Set the default joypad action mappings for Minecraft DefineActions(); InputManager.SetJoypadMapVal(0,0); @@ -940,6 +1000,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, app.UpdateTime(); PIXBeginNamedEvent(0,"Input manager tick"); InputManager.Tick(); + KMInput.Tick(); PIXEndNamedEvent(); PIXBeginNamedEvent(0,"Profile manager tick"); // ProfileManager.Tick(); @@ -1067,6 +1128,27 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, RenderManager.Present(); ui.CheckMenuDisplayed(); + + // Update mouse capture: capture when in-game and no menu is open + { + static bool altToggleSuppressCapture = false; + bool shouldCapture = app.GetGameStarted() && !ui.GetMenuDisplayed(0); + // Left Alt key toggles capture on/off for debugging + if (KMInput.IsKeyPressed(VK_MENU)) + { + if (KMInput.IsCaptured()) { KMInput.SetCapture(false); altToggleSuppressCapture = true; } + else if (shouldCapture) { KMInput.SetCapture(true); altToggleSuppressCapture = false; } + } + else if (!shouldCapture) + { + if (KMInput.IsCaptured()) KMInput.SetCapture(false); + altToggleSuppressCapture = false; + } + else if (shouldCapture && !KMInput.IsCaptured() && GetFocus() == g_hWnd && !altToggleSuppressCapture) + { + KMInput.SetCapture(true); + } + } #if 0 PIXBeginNamedEvent(0,"Profile load check"); // has the game defined profile data been changed (by a profile load) @@ -1158,6 +1240,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // Fix for #7318 - Title crashes after short soak in the leaderboards menu // A memory leak was caused because the icon renderer kept creating new Vec3's because the pool wasn't reset Vec3::resetPool(); + + KMInput.EndFrame(); } // Free resources, unregister custom classes, and exit. -- cgit v1.2.3