diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md index 349787f..e0008e6 100644 --- a/MIGRATION_PLAN.md +++ b/MIGRATION_PLAN.md @@ -361,7 +361,7 @@ $env:Path = "D:/Qt/6.8.3/mingw_64/bin;D:/Qt/Tools/mingw1310_64/bin;C:\Windows\Sy **目标**:先把所有"不依赖其他代码"的通用模块落地并单测,为输入识别打基础。 -- [ ] `src\core\common\Enums.h`:**全部枚举逐字迁移**(`FipKeys`/`FipRawKeys`/`KeyBoardCodeType`/`InputDevice`/`ElementType`/`FIPGamepadButtonflags`/`MoveState`/`TriggerMode` 等,数值一致) +- [x] `src\core\common\Enums.h`:**全部枚举逐字迁移**(`FipKeys`/`FipRawKeys`/`KeyBoardCodeType`/`InputDevice`/`ElementType`/`FIPGamepadButtonflags`/`MoveState`/`TriggerMode` 等,数值一致) - [ ] `src\core\common\InputEvent.h`:`InputEvent` 结构(对应 WPF `InputArgs`),Q_GADGET 便于 QML 消费 - [ ] `src\core\image\XImage.h/.cpp`:QImage 重写(Crop/ReSize/Scale/EraseCircle/Merge/PackImages/ConcatImages/通道分离合并/GIF 帧),**与 WPF XImage.cs API 一一对应** - [ ] `src\core\overlay\OverlayParser.h/.cpp`:overlay JSON 读写(QJsonDocument),**字段名与 WPF 完全一致**(snake_case) diff --git a/src/core/common/Enums.h b/src/core/common/Enums.h new file mode 100644 index 0000000..cb661f4 --- /dev/null +++ b/src/core/common/Enums.h @@ -0,0 +1,121 @@ +#pragma once + +#include + +namespace FancyInput { + +enum class GamepadBackend : std::uint8_t { SDL = 0, XInput = 1 }; +enum class MousMoveMode : std::uint8_t { Absolute = 0, Relative = 1 }; +enum class MouseWheelDirection : std::uint8_t { Up = 0, Down = 1 }; +enum class MacroAddDirection : std::uint8_t { Right = 0, Left = 1 }; + +enum class InputDevice : std::uint8_t { + Keyboard = 0, MouseButton, MouseWheel, MouseMove, + XInputButton, XInputTrigger, XInputStick, + SDLButton, SDLTrigger, SDLStick, SDLGuide +}; + +enum class MoveState : std::uint8_t { StartMove = 0, Moving = 1, StopMove = 2 }; + +enum class FIPGamepadButtonflags : std::uint16_t { + None = 0, Up = 1, Down = 2, Left = 4, Right = 8, + Start = 0x10, Select = 0x20, LS = 0x40, RS = 0x80, + LB = 0x100, RB = 0x200, A = 0x1000, B = 0x2000, + X = 0x4000, Y = 0x8000 +}; + +enum class ElementType : std::uint8_t { + Texture = 0, KeyboardButton, GamepadButton, MouseButton, MouseWheel, + AnalogStick, GamepadTrigger, GamepadPlayerId, DPad, MouseMovement +}; + +enum class GamepadCodeType : std::uint16_t { + A = 0, B = 1, X = 2, Y = 3, Select = 4, Start = 6, + LB = 9, RB = 10, Up = 11, Down = 12, Left = 13, Right = 14, + HitBox_B = 60416, HitBox_A = 60417, HitBox_Y = 60418, HitBox_X = 60419, + HitBox_LB = 60420, HitBox_RB = 60421, HitBox_Select = 60422, + HitBox_Start = 60423, HitBox_LS = 60425, HitBox_RS = 60426, + HitBox_Left = 60427, HitBox_Right = 60428, HitBox_Up = 60429, + HitBox_Down = 60430 +}; + +enum class GamepadTriggerType : std::uint8_t { Left = 0, Right = 1 }; +enum class GamepadThumbType : std::uint8_t { LeftThumb = 0, RightThumb = 1 }; +enum class GamepadUserIdx : std::uint8_t { One = 0, Two = 1, Three = 2, Four = 3 }; +enum class MouseCodeType : std::uint8_t { None = 0, Left = 1, Right = 2, Middle = 3, XButton1 = 4, XButton2 = 5 }; +enum class Direction : std::uint8_t { Up = 1, Down = 2, Left = 3, Right = 4 }; +enum class Side : std::uint8_t { Left = 0, Right = 1 }; +enum class ExportType : std::uint8_t { PngAndJson = 0, ProjectFile = 1 }; +enum class MainWindowPanelType : std::uint8_t { Read = 0, Create = 1, Manage = 2, Workshop = 3, Settings = 4 }; +enum class MouseMoveType : std::uint8_t { Dot = 0, Arrow = 1 }; +enum class WindowCloseType : std::uint8_t { Save = 0, Discard = 1 }; +enum class ButtonState : std::uint8_t { Released = 0, Pressed = 1 }; +enum class KeyBoardMappingType : std::uint8_t { Windows = 0, BIOS = 1 }; + +enum class KeyBoardCodeType : std::uint16_t { + None = 0, Escape = 1, D1 = 2, D2 = 3, D3 = 4, D4 = 5, D5 = 6, D6 = 7, D7 = 8, D8 = 9, D9 = 10, D0 = 11, + OemMinus = 12, Oemplus = 13, Back = 14, Tab = 15, Q = 16, W = 17, E = 18, R = 19, T = 20, Y = 21, U = 22, I = 23, O = 24, P = 25, + OemOpenBrackets = 26, OemCloseBrackets = 27, Enter = 28, LControlKey = 29, A = 30, S = 31, D = 32, F = 33, G = 34, H = 35, + J = 36, K = 37, L = 38, Oem1 = 39, Oem7 = 40, Oem3 = 41, LShiftKey = 42, Oem5 = 43, Z = 44, X = 45, C = 46, V = 47, B = 48, + N = 49, M = 50, Oemcomma = 51, OemPeriod = 52, Oem2 = 53, RShiftKey = 54, Divide = 3637, Multiply = 55, LMenu = 56, Space = 57, CapsLock = 58, + F1 = 59, F2 = 60, F3 = 61, F4 = 62, F5 = 63, F6 = 64, F7 = 65, F8 = 66, F9 = 67, F10 = 68, NumLock = 69, ScrollLock = 70, + NumPad7 = 71, NumPad8 = 72, NumPad9 = 73, Subtract = 74, NumPad4 = 75, NumPad5 = 76, NumPad6 = 77, Add = 78, NumPad1 = 79, NumPad2 = 80, + NumPad3 = 81, NumPad0 = 82, Decimal = 83, NumpadEquals = 3597, F11 = 87, F12 = 88, F13 = 91, F14 = 92, F15 = 93, + F16 = 99, F17 = 100, F18 = 101, F19 = 102, F20 = 103, F21 = 104, F22 = 105, F23 = 106, F24 = 107, + PrintScreen = 3639, Oem102 = 3654, RMenu = 3640, NumpadEnter = 3612, RControlKey = 3613, + Pause = 3653, Home = 3655, PageUp = 3657, End = 3663, PageDown = 3665, Insert = 3666, Delete = 3667, + Up = 57416, Left = 57419, Right = 57421, Down = 57424, LWin = 3675, RWin = 3676, Apps = 3677, + Power = 57438, Sleep = 57439, Wake = 57443, MediaPlay = 57378, MediaStop = 57380, + MediaPrevious = 57360, MediaNext = 57369, MediaSelect = 57453, MediaEject = 57388, + VolumeMute = 57376, VolumeUp = 57392, VolumeDown = 57390, AppMail = 57452, + AppCalculator = 57377, AppMusic = 57404, AppPictures = 57444, BrowserSearch = 57445, + BrowserHome = 57394, BrowserBack = 57450, BrowserForward = 57449, BrowserStop = 57448, + BrowserRefresh = 57447, BrowserFavorites = 57446, Katakana = 112, Underscore = 115, + Furigana = 119, Kanji = 121, Hiragana = 123, Yen = 125, NumpadComma = 126, + SunHelp = 65397, SunStop = 65400, SunProps = 65398, SunFront = 65399, SunOpen = 65396, + SunFind = 65406, SunAgain = 65401, SunUndo = 65402, SunCopy = 65404, SunInsert = 65405, SunCut = 65403 +}; + +enum class TriggerMode : std::uint8_t { Once = 0, Hold = 1, Toggle = 2 }; +enum class TriggerTiming : std::uint8_t { Press = 0, Release = 1 }; + +enum class FipKeys : std::uint16_t { + None = 0x00, Escape = 0x1B, D1 = 0x31, D2 = 0x32, D3 = 0x33, D4 = 0x34, D5 = 0x35, D6 = 0x36, D7 = 0x37, D8 = 0x38, D9 = 0x39, D0 = 0x30, + OemMinus = 0xBD, Oemplus = 0xBB, Back = 0x08, Tab = 0x09, Q = 0x51, W = 0x57, E = 0x45, R = 0x52, T = 0x54, Y = 0x59, U = 0x55, I = 0x49, O = 0x4F, P = 0x50, + OemOpenBrackets = 0xDB, OemCloseBrackets = 0xDD, Enter = 0x1000, LControlKey = 0xA2, A = 0x41, S = 0x53, D = 0x44, F = 0x46, G = 0x47, H = 0x48, + J = 0x4A, K = 0x4B, L = 0x4C, Oem1 = 0xBA, Oem7 = 0xDE, Oem3 = 0xC0, Oem102 = 0xE2, LShiftKey = 0xA0, Oem5 = 0xDC, Z = 0x5A, X = 0x58, C = 0x43, V = 0x56, B = 0x42, + N = 0x4E, M = 0x4D, Oemcomma = 0xBC, OemPeriod = 0xBE, Oem2 = 0xBF, RShiftKey = 0xA1, Divide = 0x6F, Multiply = 0x6A, LMenu = 0xA4, Space = 0x20, CapsLock = 0x14, + F1 = 0x70, F2 = 0x71, F3 = 0x72, F4 = 0x73, F5 = 0x74, F6 = 0x75, F7 = 0x76, F8 = 0x77, F9 = 0x78, F10 = 0x79, NumLock = 0x90, ScrollLock = 0x91, + NumPad7 = 0x67, NumPad8 = 0x68, NumPad9 = 0x69, Subtract = 0x6D, NumPad4 = 0x64, NumPad5 = 0x65, NumPad6 = 0x66, Add = 0x6B, NumPad1 = 0x61, NumPad2 = 0x62, + NumPad3 = 0x63, NumPad0 = 0x60, Decimal = 0x6E, NumpadEquals = 0x92, F11 = 0x7A, F12 = 0x7B, F13 = 0x7C, F14 = 0x7D, F15 = 0x7E, + F16 = 0x7F, F17 = 0x80, F18 = 0x81, F19 = 0x82, F20 = 0x83, F21 = 0x84, F22 = 0x85, F23 = 0x86, F24 = 0x87, + PrintScreen = 0x2C, RMenu = 0xA5, NumpadEnter = 0x1001, RControlKey = 0xA3, Pause = 0x13, + Home = 0x24, PageUp = 0x21, End = 0x23, PageDown = 0x22, Insert = 0x2D, Delete = 0x2E, Up = 0x26, Left = 0x25, Right = 0x27, Down = 0x28, + LWin = 0x5B, RWin = 0x5C, Apps = 0x5D, Sleep = 0x5F, Wake = 0xE3, MediaNext = 0xB0, MediaPrevious = 0xB1, + MediaStop = 0xB2, MediaPlay = 0xB3, MediaSelect = 0xB5, VolumeMute = 0xAD, VolumeUp = 0xAF, VolumeDown = 0xAE, + AppMail = 0xB4, AppMusic = 0xB6, AppPictures = 0xB7, AppCalculator = 0xB7, BrowserBack = 0xA6, BrowserForward = 0xA7, + BrowserRefresh = 0xA8, BrowserStop = 0xA9, BrowserSearch = 0xAA, BrowserFavorites = 0xAB, BrowserHome = 0xAC +}; + +enum class FipRawKeys : std::uint32_t { + None = 0x0000, Escape = 0x01, D1 = 0x02, D2 = 0x03, D3 = 0x04, D4 = 0x05, D5 = 0x06, D6 = 0x07, D7 = 0x08, D8 = 0x09, D9 = 0x0A, D0 = 0x0B, + OemMinus = 0x0C, Oemplus = 0x0D, Back = 0x0E, Tab = 0x0F, Q = 0x10, W = 0x11, E = 0x12, R = 0x13, T = 0x14, Y = 0x15, U = 0x16, I = 0x17, O = 0x18, P = 0x19, + OemOpenBrackets = 0x1A, OemCloseBrackets = 0x1B, Enter = 0x1C, LControlKey = 0x1D, A = 0x1E, S = 0x1F, D = 0x20, F = 0x21, G = 0x22, H = 0x23, + J = 0x24, K = 0x25, L = 0x26, Oem1 = 0x27, Oem7 = 0x28, Oem3 = 0x29, Oem102 = 0x56, LShiftKey = 0x2A, Oem5 = 0x2B, Z = 0x2C, X = 0x2D, C = 0x2E, V = 0x2F, B = 0x30, + N = 0x31, M = 0x32, Oemcomma = 0x33, OemPeriod = 0x34, Oem2 = 0x35, RShiftKey = 0x36, Divide = 0xE035, Multiply = 0x37, LMenu = 0x38, Space = 0x39, CapsLock = 0x3A, + F1 = 0x3B, F2 = 0x3C, F3 = 0x3D, F4 = 0x3E, F5 = 0x3F, F6 = 0x40, F7 = 0x41, F8 = 0x42, F9 = 0x43, F10 = 0x44, NumLock = 0x45, ScrollLock = 0x46, + NumPad7 = 0x47, NumPad8 = 0x48, NumPad9 = 0x49, Subtract = 0x4A, NumPad4 = 0x4B, NumPad5 = 0x4C, NumPad6 = 0x4D, Add = 0x4E, NumPad1 = 0x4F, NumPad2 = 0x50, + NumPad3 = 0x51, NumPad0 = 0x52, Decimal = 0x53, NumpadEquals = 0xE00D, F11 = 0x57, F12 = 0x58, F13 = 0x5B, F14 = 0x5C, F15 = 0x5D, + F16 = 0x63, F17 = 0x64, F18 = 0x65, F19 = 0x66, F20 = 0x67, F21 = 0x68, F22 = 0x69, F23 = 0x6A, F24 = 0x6B, + PrintScreen = 0xE037, RMenu = 0xE038, NumpadEnter = 0xE01C, RControlKey = 0xE01D, Pause = 0xE11D45, + Home = 0xE047, PageUp = 0xE049, End = 0xE04F, PageDown = 0xE051, Insert = 0xE052, Delete = 0xE053, Up = 0xE048, Left = 0xE04B, Right = 0xE04D, Down = 0xE050, + LWin = 0xE05B, RWin = 0xE05C, Apps = 0xE05D, Power = 0xE05E, Sleep = 0xE05F, Wake = 0xE063, + MediaPlay = 0xE022, MediaStop = 0xE024, MediaPrevious = 0xE010, MediaNext = 0xE019, MediaSelect = 0xE06D, MediaEject = 0xE02C, + VolumeMute = 0xE020, VolumeUp = 0xE030, VolumeDown = 0xE02E, AppMail = 0xE06C, AppCalculator = 0xE021, AppMusic = 0xE03C, AppPictures = 0xE064, + BrowserSearch = 0xE065, BrowserHome = 0xE032, BrowserBack = 0xE06A, BrowserForward = 0xE069, BrowserStop = 0xE068, BrowserRefresh = 0xE067, BrowserFavorites = 0xE066, + Katakana = 0x70, Underscore = 0x73, Furigana = 0x77, Kanji = 0x79, Hiragana = 0x7B, Yen = 0x7D, NumpadComma = 0x7E, + SunHelp = 0xFF75, SunStop = 0xFF78, SunProps = 0xFF76, SunFront = 0xFF77, SunOpen = 0xFF74, SunFind = 0xFF7E, + SunAgain = 0xFF79, SunUndo = 0xFF7A, SunCopy = 0xFF7C, SunInsert = 0xFF7D, SunCut = 0xFF7B +}; + +} // namespace FancyInput diff --git a/src/core/input/RawInputDetector.cpp b/src/core/input/RawInputDetector.cpp new file mode 100644 index 0000000..3274b4c --- /dev/null +++ b/src/core/input/RawInputDetector.cpp @@ -0,0 +1,243 @@ +#include "RawInputDetector.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#include + +namespace { +constexpr UINT kRidInput = 0x10000003; +constexpr USHORT kE0Flag = 0x02; +constexpr USHORT kMouseMoveAbsolute = 0x0001; +constexpr USHORT kMouseLeftDown = 0x0001; +constexpr USHORT kMouseLeftUp = 0x0002; +constexpr USHORT kMouseRightDown = 0x0004; +constexpr USHORT kMouseRightUp = 0x0008; +constexpr USHORT kMouseMiddleDown = 0x0010; +constexpr USHORT kMouseMiddleUp = 0x0020; +constexpr USHORT kMouseX1Down = 0x0040; +constexpr USHORT kMouseX1Up = 0x0080; +constexpr USHORT kMouseX2Down = 0x0100; +constexpr USHORT kMouseX2Up = 0x0200; +constexpr USHORT kMouseWheel = 0x0400; +} +#endif + +namespace FancyInput { + +RawInputDetector::RawInputDetector(QObject *parent) + : QObject(parent) +{ + m_rawBuffer.resize(64); +} + +RawInputDetector::~RawInputDetector() +{ + stop(); +} + +bool RawInputDetector::start(QWindow *targetWindow) +{ +#ifdef _WIN32 + if (m_running || targetWindow == nullptr || targetWindow->winId() == 0) + return false; + + if (!registerDevices(reinterpret_cast(targetWindow->winId()))) + return false; + + m_targetWindow = targetWindow; + m_pressedKeys.clear(); + QCoreApplication::instance()->installNativeEventFilter(this); + m_running = true; + return true; +#else + Q_UNUSED(targetWindow) + return false; +#endif +} + +void RawInputDetector::stop() +{ +#ifdef _WIN32 + if (!m_running) + return; + + QCoreApplication::instance()->removeNativeEventFilter(this); + unregisterDevices(); + m_pressedKeys.clear(); + m_targetWindow = nullptr; + m_running = false; +#endif +} + +bool RawInputDetector::nativeEventFilter(const QByteArray &eventType, + void *message, + qintptr *result) +{ + Q_UNUSED(result) +#ifdef _WIN32 + if (!m_running || eventType != "windows_generic_MSG" || message == nullptr) + return false; + + const MSG *msg = static_cast(message); + if (msg->message == WM_INPUT) + handleRawInput(reinterpret_cast(msg->lParam)); +#else + Q_UNUSED(eventType) + Q_UNUSED(message) +#endif + return false; +} + +#ifdef _WIN32 +bool RawInputDetector::registerDevices(HWND targetHandle) +{ + RAWINPUTDEVICE devices[2] = {}; + devices[0].usUsagePage = HID_USAGE_PAGE_GENERIC; + devices[0].usUsage = HID_USAGE_GENERIC_KEYBOARD; + devices[0].dwFlags = RIDEV_INPUTSINK; + devices[0].hwndTarget = targetHandle; + devices[1].usUsagePage = HID_USAGE_PAGE_GENERIC; + devices[1].usUsage = HID_USAGE_GENERIC_MOUSE; + devices[1].dwFlags = RIDEV_INPUTSINK; + devices[1].hwndTarget = targetHandle; + + return RegisterRawInputDevices(devices, 2, sizeof(RAWINPUTDEVICE)) != FALSE; +} + +void RawInputDetector::unregisterDevices() +{ + RAWINPUTDEVICE devices[2] = {}; + devices[0].usUsagePage = HID_USAGE_PAGE_GENERIC; + devices[0].usUsage = HID_USAGE_GENERIC_KEYBOARD; + devices[0].dwFlags = RIDEV_REMOVE; + devices[1].usUsagePage = HID_USAGE_PAGE_GENERIC; + devices[1].usUsage = HID_USAGE_GENERIC_MOUSE; + devices[1].dwFlags = RIDEV_REMOVE; + RegisterRawInputDevices(devices, 2, sizeof(RAWINPUTDEVICE)); +} + +void RawInputDetector::handleRawInput(HRAWINPUT rawInput) +{ + UINT size = 0; + if (GetRawInputData(rawInput, RID_INPUT, nullptr, &size, + sizeof(RAWINPUTHEADER)) == static_cast(-1) || size == 0) + return; + + if (m_rawBuffer.size() < static_cast(size)) + m_rawBuffer.resize(static_cast(size)); + if (GetRawInputData(rawInput, RID_INPUT, m_rawBuffer.data(), &size, + sizeof(RAWINPUTHEADER)) == static_cast(-1)) + return; + + const auto *input = reinterpret_cast(m_rawBuffer.constData()); + if (input->header.dwType == RIM_TYPEKEYBOARD) + handleKeyboard(input->data.keyboard); + else if (input->header.dwType == RIM_TYPEMOUSE) + handleMouse(input->data.mouse); +} + +void RawInputDetector::handleKeyboard(const RAWKEYBOARD &keyboard) +{ + const bool keyDown = keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN; + const bool keyUp = keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP; + if (!keyDown && !keyUp) + return; + + const bool extended = (keyboard.Flags & kE0Flag) != 0; + const quint16 virtualKey = keyboard.VKey; + const FipKeys key = mapKey(virtualKey, keyboard.MakeCode, extended); + const FipRawKeys rawKey = mapRawKey(virtualKey, keyboard.MakeCode, extended); + if (keyDown) { + if (m_pressedKeys.contains(virtualKey)) + return; + m_pressedKeys.insert(virtualKey); + } + if (keyUp) + m_pressedKeys.remove(virtualKey); + + const bool ctrl = (GetAsyncKeyState(VK_LCONTROL) & 0x8000) || (GetAsyncKeyState(VK_RCONTROL) & 0x8000); + const bool shift = (GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000); + const bool alt = (GetAsyncKeyState(VK_LMENU) & 0x8000) || (GetAsyncKeyState(VK_RMENU) & 0x8000); + const bool win = (GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000); + const bool numLock = (GetKeyState(VK_NUMLOCK) & 1) != 0; + const bool capsLock = (GetKeyState(VK_CAPITAL) & 1) != 0; + const bool scrollLock = (GetKeyState(VK_SCROLL) & 1) != 0; + + if (keyDown) + emit keyPressed(key, rawKey, virtualKey, keyboard.MakeCode, extended, ctrl, shift, alt, win, numLock, capsLock, scrollLock); + else + emit keyReleased(key, rawKey, virtualKey, keyboard.MakeCode, extended, ctrl, shift, alt, win, numLock, capsLock, scrollLock); +} + +void RawInputDetector::handleMouse(const RAWMOUSE &mouse) +{ + const USHORT flags = mouse.usButtonFlags; + if (flags & kMouseLeftDown) emit mouseButtonChanged(MouseCodeType::Left, true); + if (flags & kMouseLeftUp) emit mouseButtonChanged(MouseCodeType::Left, false); + if (flags & kMouseRightDown) emit mouseButtonChanged(MouseCodeType::Right, true); + if (flags & kMouseRightUp) emit mouseButtonChanged(MouseCodeType::Right, false); + if (flags & kMouseMiddleDown) emit mouseButtonChanged(MouseCodeType::Middle, true); + if (flags & kMouseMiddleUp) emit mouseButtonChanged(MouseCodeType::Middle, false); + if (flags & kMouseX1Down) emit mouseButtonChanged(MouseCodeType::XButton1, true); + if (flags & kMouseX1Up) emit mouseButtonChanged(MouseCodeType::XButton1, false); + if (flags & kMouseX2Down) emit mouseButtonChanged(MouseCodeType::XButton2, true); + if (flags & kMouseX2Up) emit mouseButtonChanged(MouseCodeType::XButton2, false); + if (flags & kMouseWheel) + emit mouseWheelMoved(static_cast(mouse.usButtonData)); + if ((mouse.usFlags & kMouseMoveAbsolute) == 0 && (mouse.lLastX != 0 || mouse.lLastY != 0)) + emit mouseMoved(mouse.lLastX, mouse.lLastY); +} +#endif + +FipKeys RawInputDetector::mapKey(quint16 virtualKey, quint16 scanCode, bool extended) +{ + if (virtualKey == VK_PAUSE) return FipKeys::Pause; + if (virtualKey == VK_SNAPSHOT) return FipKeys::PrintScreen; + if (virtualKey == VK_RETURN && scanCode == 0x1C) return extended ? FipKeys::NumpadEnter : FipKeys::Enter; + if (virtualKey == VK_CONTROL && scanCode == 0x1D) return extended ? FipKeys::RControlKey : FipKeys::LControlKey; + if (virtualKey == VK_MENU && scanCode == 0x38) return extended ? FipKeys::RMenu : FipKeys::LMenu; + if (virtualKey == VK_SHIFT) { + if (scanCode == 0x2A) return FipKeys::LShiftKey; + if (scanCode == 0x36) return FipKeys::RShiftKey; + } + if (virtualKey == VK_LWIN) return FipKeys::LWin; + if (virtualKey == VK_RWIN) return FipKeys::RWin; + if (virtualKey == VK_APPS) return FipKeys::Apps; + if (extended) { + switch (scanCode) { + case 0x48: return FipKeys::Up; + case 0x4B: return FipKeys::Left; + case 0x4D: return FipKeys::Right; + case 0x50: return FipKeys::Down; + case 0x47: return FipKeys::Home; + case 0x4F: return FipKeys::End; + case 0x49: return FipKeys::PageUp; + case 0x51: return FipKeys::PageDown; + case 0x52: return FipKeys::Insert; + case 0x53: return FipKeys::Delete; + case 0x35: return FipKeys::Divide; + default: break; + } + } + return static_cast(virtualKey); +} + +FipRawKeys RawInputDetector::mapRawKey(quint16 virtualKey, quint16 scanCode, bool extended) +{ + if (virtualKey == VK_PAUSE) return FipRawKeys::Pause; + if (virtualKey == VK_SNAPSHOT) return FipRawKeys::PrintScreen; + if (virtualKey == VK_CONTROL && scanCode == 0x1D) return extended ? FipRawKeys::RControlKey : FipRawKeys::LControlKey; + if (virtualKey == VK_MENU && scanCode == 0x38) return extended ? FipRawKeys::RMenu : FipRawKeys::LMenu; + if (virtualKey == VK_RETURN && scanCode == 0x1C) return extended ? FipRawKeys::NumpadEnter : FipRawKeys::Enter; + if (virtualKey == VK_LWIN) return FipRawKeys::LWin; + if (virtualKey == VK_RWIN) return FipRawKeys::RWin; + if (virtualKey == VK_APPS) return FipRawKeys::Apps; + const quint32 code = extended ? (0xE000u | scanCode) : scanCode; + return static_cast(code); +} + +} // namespace FancyInput diff --git a/src/core/input/RawInputDetector.h b/src/core/input/RawInputDetector.h new file mode 100644 index 0000000..67f795c --- /dev/null +++ b/src/core/input/RawInputDetector.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../common/Enums.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +namespace FancyInput { + +class RawInputDetector final : public QObject, public QAbstractNativeEventFilter +{ + Q_OBJECT + +public: + explicit RawInputDetector(QObject *parent = nullptr); + ~RawInputDetector() override; + + bool start(QWindow *targetWindow); + void stop(); + bool isRunning() const { return m_running; } + + bool nativeEventFilter(const QByteArray &eventType, void *message, + qintptr *result) override; + +signals: + void keyPressed(FipKeys key, FipRawKeys rawKey, quint16 virtualKey, + quint16 scanCode, bool extended, bool ctrlDown, + bool shiftDown, bool altDown, bool winDown, + bool numLockOn, bool capsLockOn, bool scrollLockOn); + void keyReleased(FipKeys key, FipRawKeys rawKey, quint16 virtualKey, + quint16 scanCode, bool extended, bool ctrlDown, + bool shiftDown, bool altDown, bool winDown, + bool numLockOn, bool capsLockOn, bool scrollLockOn); + void mouseButtonChanged(MouseCodeType button, bool pressed); + void mouseWheelMoved(int delta); + void mouseMoved(int deltaX, int deltaY); + +private: + bool registerDevices(HWND targetHandle); + void unregisterDevices(); + void handleRawInput(HRAWINPUT rawInput); + void handleKeyboard(const RAWKEYBOARD &keyboard); + void handleMouse(const RAWMOUSE &mouse); + + static FipKeys mapKey(quint16 virtualKey, quint16 scanCode, bool extended); + static FipRawKeys mapRawKey(quint16 virtualKey, quint16 scanCode, bool extended); + + QWindow *m_targetWindow = nullptr; + QSet m_pressedKeys; + bool m_running = false; + QByteArray m_rawBuffer; +}; + +} // namespace FancyInput diff --git a/src/main.cpp b/src/main.cpp index 43570e0..20fc044 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,13 @@ #include #include #include +#include +#include "core/common/Enums.h" +#include "core/input/RawInputDetector.h" + +static_assert(static_cast(FancyInput::FipKeys::Enter) == 0x1000); +static_assert(static_cast(FancyInput::FipRawKeys::NumpadEnter) == 0xE01C); +static_assert(static_cast(FancyInput::FIPGamepadButtonflags::Y) == 0x8000); int main(int argc, char *argv[]) { @@ -16,5 +23,46 @@ int main(int argc, char *argv[]) Qt::QueuedConnection); engine.loadFromModule("FancyInputQt", "MainWindow"); + FancyInput::RawInputDetector rawInputDetector(&app); + if (!engine.rootObjects().isEmpty()) { + if (auto *window = qobject_cast(engine.rootObjects().constFirst())) { + rawInputDetector.start(window); + QObject::connect(&rawInputDetector, &FancyInput::RawInputDetector::keyPressed, + [](FancyInput::FipKeys key, FancyInput::FipRawKeys rawKey, + quint16 virtualKey, quint16 scanCode, bool extended, + bool ctrl, bool shift, bool alt, bool win, + bool numLock, bool capsLock, bool scrollLock) { + qInfo().nospace() << "KEY DOWN key=" << static_cast(key) + << " raw=" << static_cast(rawKey) + << " vk=0x" << Qt::hex << virtualKey + << " scan=0x" << scanCode << Qt::dec + << " e0=" << extended << " modifiers(ctrl=" << ctrl + << ",shift=" << shift << ",alt=" << alt << ",win=" << win + << ") locks(num=" << numLock << ",caps=" << capsLock + << ",scroll=" << scrollLock << ')'; + }); + QObject::connect(&rawInputDetector, &FancyInput::RawInputDetector::keyReleased, + [](FancyInput::FipKeys key, FancyInput::FipRawKeys rawKey, + quint16 virtualKey, quint16 scanCode, bool extended, + bool, bool, bool, bool, bool, bool, bool) { + qInfo().nospace() << "KEY UP key=" << static_cast(key) + << " raw=" << static_cast(rawKey) + << " vk=0x" << Qt::hex << virtualKey + << " scan=0x" << scanCode << Qt::dec + << " e0=" << extended; + }); + QObject::connect(&rawInputDetector, &FancyInput::RawInputDetector::mouseButtonChanged, + [](FancyInput::MouseCodeType button, bool pressed) { + qInfo() << (pressed ? "MOUSE DOWN" : "MOUSE UP") + << static_cast(button); + }); + QObject::connect(&rawInputDetector, &FancyInput::RawInputDetector::mouseWheelMoved, + [](int delta) { qInfo() << "MOUSE WHEEL" << delta; }); + QObject::connect(&rawInputDetector, &FancyInput::RawInputDetector::mouseMoved, + [](int deltaX, int deltaY) { qInfo() << "MOUSE MOVE" << deltaX << deltaY; }); + qInfo() << "Raw Input detection enabled on the main window."; + } + } + return app.exec(); } diff --git a/src/qml/Main.qml b/src/qml/Main.qml index b862489..2516deb 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -1,58 +1,19 @@ -import QtQuick +import QtQuick import QtQuick.Controls import QtQuick.Layouts import FancyInput.Controls import FancyInput.Windows -// 主界面(根元素为 Rectangle —— Qt Creator 设计模式可可视化编辑) -// 由 main.cpp 用 QQuickWindow 包装成主窗口。 + Rectangle { id: root width: 600 height: 480 color: Theme.background property int clickCount: 0 - property Window settingsWindow: null - property Window overlayWindow: null - - function openSettingsWindow(modal) { - settingsWindow = settingsWindowComp.createObject(modal ? root : null, { modal: modal }) - if (settingsWindow) settingsWindow.show() - } - function openOverlayWindow() { - overlayWindow = overlayWindowComp.createObject(null, {}) - if (overlayWindow) overlayWindow.show() - } - // 设置窗口 - Component { - id: settingsWindowComp - FancyWindow { - width: 480 - height: 360 - windowTitle: qsTr("设置 · FancyInput") - SettingsWindow { - anchors.fill: parent - onCloseRequested: hostWindow.close() - } - } - } - - // 悬浮窗 - Component { - id: overlayWindowComp - FancyWindow { - width: 280 - height: 180 - frameless: true - transparent: true - alwaysOnTop: true - toolWindow: true - - OverlayWindow { - anchors.fill: parent - onCloseRequested: hostWindow.close() - } - } + PopupWindows { + id: popupWindows + owner: root } ColumnLayout { @@ -110,7 +71,7 @@ Rectangle { Layout.alignment: Qt.AlignHCenter text: qsTr("打开设置窗口") font.family: Theme.fontFamily - onClicked: root.openSettingsWindow(false) + onClicked: popupWindows.openSettingsWindow(false) } // 打开模态窗口(WPF Window.ShowDialog 等价) @@ -118,7 +79,7 @@ Rectangle { Layout.alignment: Qt.AlignHCenter text: qsTr("打开模态窗口") font.family: Theme.fontFamily - onClicked: root.openSettingsWindow(true) + onClicked: popupWindows.openSettingsWindow(true) } // 打开无边框透明悬浮窗(WPF 悬浮窗等价) @@ -126,7 +87,7 @@ Rectangle { Layout.alignment: Qt.AlignHCenter text: qsTr("打开悬浮窗") font.family: Theme.fontFamily - onClicked: root.openOverlayWindow() + onClicked: popupWindows.openOverlayWindow() } } } diff --git a/src/qml/windows/PopupWindows.qml b/src/qml/windows/PopupWindows.qml new file mode 100644 index 0000000..e57a68f --- /dev/null +++ b/src/qml/windows/PopupWindows.qml @@ -0,0 +1,50 @@ +import QtQuick + +QtObject { + id: manager + + property Item owner + property Window settingsWindow: null + property Window overlayWindow: null + + function openSettingsWindow(modal) { + settingsWindow = settingsWindowComp.createObject(modal ? owner : null, { modal: modal }) + if (settingsWindow) settingsWindow.show() + } + + function openOverlayWindow() { + overlayWindow = overlayWindowComp.createObject(null, {}) + if (overlayWindow) overlayWindow.show() + } + + property Component settingsWindowComp: Component { + FancyWindow { + id: settingsHostWindow + width: 480 + height: 360 + windowTitle: qsTr("设置 · FancyInput") + + SettingsWindow { + anchors.fill: parent + onCloseRequested: settingsHostWindow.hostWindow.close() + } + } + } + + property Component overlayWindowComp: Component { + FancyWindow { + id: overlayHostWindow + width: 280 + height: 180 + frameless: true + transparent: true + alwaysOnTop: true + toolWindow: true + + OverlayWindow { + anchors.fill: parent + onCloseRequested: overlayHostWindow.hostWindow.close() + } + } + } +}