#include #include #include #include #include #include "control.h" #include "controls/controller.h" #include "controls/controller_motion.h" #include "controls/game_controls.h" #include "controls/plrctrls.h" #include "controls/remap_keyboard.h" #include "controls/touch.h" #include "cursor.h" #include "engine/rectangle.hpp" #include "hwcursor.hpp" #include "inv.h" #include "menu.h" #include "miniwin/miniwin.h" #include "movie.h" #include "nthread.h" #include "storm/storm.h" #include "utils/display.h" #include "utils/log.hpp" #include "utils/paths.h" #include "utils/sdl_compat.h" #include "utils/stubs.h" #ifdef __SWITCH__ #include "platform/switch/docking.h" #include #endif /** @file * * * Windows message handling and keyboard event conversion for SDL. */ namespace devilution { static std::deque message_queue; bool mouseWarping = false; Point mousePositionWarping; void SetCursorPos(int x, int y) { mousePositionWarping = { x, y }; mouseWarping = true; LogicalToOutput(&x, &y); SDL_WarpMouseInWindow(ghMainWnd, x, y); } // Moves the mouse to the first attribute "+" button. void FocusOnCharInfo() { auto &myPlayer = Players[MyPlayerId]; if (invflag || myPlayer._pStatPts <= 0) return; // Find the first incrementable stat. int stat = -1; for (auto attribute : enum_values()) { int max = myPlayer.GetMaximumAttributeValue(attribute); switch (attribute) { case CharacterAttribute::Strength: if (myPlayer._pBaseStr >= max) continue; break; case CharacterAttribute::Magic: if (myPlayer._pBaseMag >= max) continue; break; case CharacterAttribute::Dexterity: if (myPlayer._pBaseDex >= max) continue; break; case CharacterAttribute::Vitality: if (myPlayer._pBaseVit >= max) continue; break; } stat = static_cast(attribute); } if (stat == -1) return; const Rectangle &rect = ChrBtnsRect[stat]; SetCursorPos(rect.position.x + (rect.size.width / 2), rect.position.y + (rect.size.height / 2)); } static int TranslateSdlKey(SDL_Keysym key) { // ref: https://wiki.libsdl.org/SDL_Keycode // ref: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes SDL_Keycode sym = key.sym; remap_keyboard_key(&sym); switch (sym) { case SDLK_BACKSPACE: return DVL_VK_BACK; case SDLK_TAB: return DVL_VK_TAB; case SDLK_RETURN: return DVL_VK_RETURN; case SDLK_ESCAPE: return DVL_VK_ESCAPE; case SDLK_SPACE: return DVL_VK_SPACE; case SDLK_QUOTE: return DVL_VK_OEM_7; case SDLK_COMMA: return DVL_VK_OEM_COMMA; case SDLK_MINUS: return DVL_VK_OEM_MINUS; case SDLK_PERIOD: return DVL_VK_OEM_PERIOD; case SDLK_SLASH: return DVL_VK_OEM_2; case SDLK_SEMICOLON: return DVL_VK_OEM_1; case SDLK_EQUALS: return DVL_VK_OEM_PLUS; case SDLK_LEFTBRACKET: return DVL_VK_OEM_4; case SDLK_BACKSLASH: return DVL_VK_OEM_5; case SDLK_RIGHTBRACKET: return DVL_VK_OEM_6; case SDLK_BACKQUOTE: return DVL_VK_OEM_3; case SDLK_DELETE: return DVL_VK_DELETE; case SDLK_CAPSLOCK: return DVL_VK_CAPITAL; case SDLK_F1: return DVL_VK_F1; case SDLK_F2: return DVL_VK_F2; case SDLK_F3: return DVL_VK_F3; case SDLK_F4: return DVL_VK_F4; case SDLK_F5: return DVL_VK_F5; case SDLK_F6: return DVL_VK_F6; case SDLK_F7: return DVL_VK_F7; case SDLK_F8: return DVL_VK_F8; case SDLK_F9: return DVL_VK_F9; case SDLK_F10: return DVL_VK_F10; case SDLK_F11: return DVL_VK_F11; case SDLK_F12: return DVL_VK_F12; case SDLK_PRINTSCREEN: return DVL_VK_SNAPSHOT; case SDLK_SCROLLLOCK: return DVL_VK_SCROLL; case SDLK_PAUSE: return DVL_VK_PAUSE; case SDLK_INSERT: return DVL_VK_INSERT; case SDLK_HOME: return DVL_VK_HOME; case SDLK_PAGEUP: return DVL_VK_PRIOR; case SDLK_END: return DVL_VK_END; case SDLK_PAGEDOWN: return DVL_VK_NEXT; case SDLK_RIGHT: return DVL_VK_RIGHT; case SDLK_LEFT: return DVL_VK_LEFT; case SDLK_DOWN: return DVL_VK_DOWN; case SDLK_UP: return DVL_VK_UP; case SDLK_NUMLOCKCLEAR: return DVL_VK_NUMLOCK; case SDLK_KP_DIVIDE: return DVL_VK_DIVIDE; case SDLK_KP_MULTIPLY: return DVL_VK_MULTIPLY; case SDLK_KP_MINUS: // Returning DVL_VK_OEM_MINUS to play nice with Devilution automap zoom. // // For a distinct keypad key-code, DVL_VK_SUBTRACT should be returned. return DVL_VK_OEM_MINUS; case SDLK_KP_PLUS: // Returning DVL_VK_OEM_PLUS to play nice with Devilution automap zoom. // // For a distinct keypad key-code, DVL_VK_ADD should be returned. return DVL_VK_OEM_PLUS; case SDLK_KP_ENTER: return DVL_VK_RETURN; case SDLK_KP_1: return DVL_VK_NUMPAD1; case SDLK_KP_2: return DVL_VK_NUMPAD2; case SDLK_KP_3: return DVL_VK_NUMPAD3; case SDLK_KP_4: return DVL_VK_NUMPAD4; case SDLK_KP_5: return DVL_VK_NUMPAD5; case SDLK_KP_6: return DVL_VK_NUMPAD6; case SDLK_KP_7: return DVL_VK_NUMPAD7; case SDLK_KP_8: return DVL_VK_NUMPAD8; case SDLK_KP_9: return DVL_VK_NUMPAD9; #ifndef USE_SDL1 case SDLK_KP_000: case SDLK_KP_00: #endif case SDLK_KP_0: return DVL_VK_NUMPAD0; case SDLK_KP_PERIOD: return DVL_VK_DECIMAL; case SDLK_MENU: return DVL_VK_MENU; #ifndef USE_SDL1 case SDLK_KP_COMMA: return DVL_VK_OEM_COMMA; #endif case SDLK_LCTRL: return DVL_VK_LCONTROL; case SDLK_LSHIFT: return DVL_VK_LSHIFT; case SDLK_LALT: return DVL_VK_LMENU; case SDLK_LGUI: return DVL_VK_LWIN; case SDLK_RCTRL: return DVL_VK_RCONTROL; case SDLK_RSHIFT: return DVL_VK_RSHIFT; case SDLK_RALT: return DVL_VK_RMENU; case SDLK_RGUI: return DVL_VK_RWIN; default: if (sym >= SDLK_a && sym <= SDLK_z) { return 'A' + (sym - SDLK_a); } else if (sym >= SDLK_0 && sym <= SDLK_9) { return '0' + (sym - SDLK_0); } else if (sym >= SDLK_F1 && sym <= SDLK_F12) { return DVL_VK_F1 + (sym - SDLK_F1); } Log("unknown key: name={} sym=0x{:X} scan={} mod=0x{:X}", SDL_GetKeyName(sym), sym, key.scancode, key.mod); return -1; } } namespace { int32_t PositionForMouse(int16_t x, int16_t y) { return (((uint16_t)(y & 0xFFFF)) << 16) | (uint16_t)(x & 0xFFFF); } int32_t KeystateForMouse(int32_t ret) { ret |= (SDL_GetModState() & KMOD_SHIFT) != 0 ? DVL_MK_SHIFT : 0; // XXX: other DVL_MK_* codes not implemented return ret; } bool FalseAvail(const char *name, int value) { LogDebug("Unhandled SDL event: {} {}", name, value); return true; } } // namespace /** * @brief Try to clean the inventory related cursor states. * @return True if it is safe to close the inventory */ bool BlurInventory() { if (pcurs >= CURSOR_FIRSTITEM) { if (!TryDropItem()) { Players[MyPlayerId].Say(HeroSpeech::WhereWouldIPutThis); return false; } } invflag = false; if (pcurs > CURSOR_HAND) NewCursor(CURSOR_HAND); if (chrflag) FocusOnCharInfo(); return true; } bool FetchMessage_Real(tagMSG *lpMsg) { #ifdef __SWITCH__ HandleDocking(); #endif if (!message_queue.empty()) { *lpMsg = message_queue.front(); message_queue.pop_front(); return true; } SDL_Event e; if (SDL_PollEvent(&e) == 0) { return false; } lpMsg->message = 0; lpMsg->lParam = 0; lpMsg->wParam = 0; if (e.type == SDL_QUIT) { lpMsg->message = DVL_WM_QUIT; return true; } #ifndef USE_SDL1 handle_touch(&e, MousePosition.x, MousePosition.y); #endif #ifdef USE_SDL1 if (e.type == SDL_MOUSEMOTION) { OutputToLogical(&e.motion.x, &e.motion.y); } else if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP) { OutputToLogical(&e.button.x, &e.button.y); } #endif if ((e.type == SDL_KEYUP || e.type == SDL_KEYDOWN) && e.key.keysym.sym == SDLK_UNKNOWN) { // Erroneous events generated by RG350 kernel. return true; } if (HandleControllerAddedOrRemovedEvent(e)) return true; const ControllerButtonEvent ctrlEvent = ToControllerButtonEvent(e); if (ProcessControllerMotion(e, ctrlEvent)) return true; GameAction action; if (GetGameAction(e, ctrlEvent, &action)) { if (action.type != GameActionType_NONE) { sgbControllerActive = true; if (movie_playing) { lpMsg->message = DVL_WM_KEYDOWN; if (action.type == GameActionType_SEND_KEY) lpMsg->wParam = action.send_key.vk_code; return true; } } switch (action.type) { case GameActionType_NONE: break; case GameActionType_USE_HEALTH_POTION: UseBeltItem(BLT_HEALING); break; case GameActionType_USE_MANA_POTION: UseBeltItem(BLT_MANA); break; case GameActionType_PRIMARY_ACTION: PerformPrimaryAction(); break; case GameActionType_SECONDARY_ACTION: PerformSecondaryAction(); break; case GameActionType_CAST_SPELL: PerformSpellAction(); break; case GameActionType_TOGGLE_QUICK_SPELL_MENU: if (!invflag || BlurInventory()) { if (!spselflag) DoSpeedBook(); else spselflag = false; chrflag = false; QuestLogIsOpen = false; sbookflag = false; StoreSpellCoords(); } break; case GameActionType_TOGGLE_CHARACTER_INFO: chrflag = !chrflag; if (chrflag) { QuestLogIsOpen = false; spselflag = false; if (pcurs == CURSOR_DISARM) NewCursor(CURSOR_HAND); FocusOnCharInfo(); } break; case GameActionType_TOGGLE_QUEST_LOG: if (!QuestLogIsOpen) { StartQuestlog(); chrflag = false; spselflag = false; } else { QuestLogIsOpen = false; } break; case GameActionType_TOGGLE_INVENTORY: if (invflag) { BlurInventory(); } else { sbookflag = false; spselflag = false; invflag = true; if (pcurs == CURSOR_DISARM) NewCursor(CURSOR_HAND); FocusOnInventory(); } break; case GameActionType_TOGGLE_SPELL_BOOK: if (BlurInventory()) { invflag = false; spselflag = false; sbookflag = !sbookflag; } break; case GameActionType_SEND_KEY: lpMsg->message = action.send_key.up ? DVL_WM_KEYUP : DVL_WM_KEYDOWN; lpMsg->wParam = action.send_key.vk_code; return true; case GameActionType_SEND_MOUSE_CLICK: sgbControllerActive = false; switch (action.send_mouse_click.button) { case GameActionSendMouseClick::LEFT: lpMsg->message = action.send_mouse_click.up ? DVL_WM_LBUTTONUP : DVL_WM_LBUTTONDOWN; break; case GameActionSendMouseClick::RIGHT: lpMsg->message = action.send_mouse_click.up ? DVL_WM_RBUTTONUP : DVL_WM_RBUTTONDOWN; break; } lpMsg->lParam = PositionForMouse(MousePosition.x, MousePosition.y); break; } return true; #ifndef USE_SDL1 } if (e.type < SDL_JOYAXISMOTION || (e.type >= SDL_FINGERDOWN && e.type < SDL_DOLLARGESTURE)) { #else } else if (e.type < SDL_JOYAXISMOTION) { #endif if (!mouseWarping || e.type != SDL_MOUSEMOTION) sgbControllerActive = false; if (mouseWarping && e.type == SDL_MOUSEMOTION) mouseWarping = false; } switch (e.type) { case SDL_QUIT: lpMsg->message = DVL_WM_QUIT; break; case SDL_KEYDOWN: case SDL_KEYUP: { int key = TranslateSdlKey(e.key.keysym); if (key == -1) return FalseAvail(e.type == SDL_KEYDOWN ? "SDL_KEYDOWN" : "SDL_KEYUP", e.key.keysym.sym); lpMsg->message = e.type == SDL_KEYDOWN ? DVL_WM_KEYDOWN : DVL_WM_KEYUP; lpMsg->wParam = (uint32_t)key; // HACK: Encode modifier in lParam for TranslateMessage later lpMsg->lParam = e.key.keysym.mod << 16; } break; case SDL_MOUSEMOTION: lpMsg->message = DVL_WM_MOUSEMOVE; lpMsg->lParam = PositionForMouse(e.motion.x, e.motion.y); lpMsg->wParam = KeystateForMouse(0); break; case SDL_MOUSEBUTTONDOWN: { int button = e.button.button; if (button == SDL_BUTTON_LEFT) { lpMsg->message = DVL_WM_LBUTTONDOWN; lpMsg->lParam = PositionForMouse(e.button.x, e.button.y); lpMsg->wParam = KeystateForMouse(DVL_MK_LBUTTON); } else if (button == SDL_BUTTON_RIGHT) { lpMsg->message = DVL_WM_RBUTTONDOWN; lpMsg->lParam = PositionForMouse(e.button.x, e.button.y); lpMsg->wParam = KeystateForMouse(DVL_MK_RBUTTON); } } break; case SDL_MOUSEBUTTONUP: { int button = e.button.button; if (button == SDL_BUTTON_LEFT) { lpMsg->message = DVL_WM_LBUTTONUP; lpMsg->lParam = PositionForMouse(e.button.x, e.button.y); lpMsg->wParam = KeystateForMouse(0); } else if (button == SDL_BUTTON_RIGHT) { lpMsg->message = DVL_WM_RBUTTONUP; lpMsg->lParam = PositionForMouse(e.button.x, e.button.y); lpMsg->wParam = KeystateForMouse(0); } } break; #ifndef USE_SDL1 case SDL_MOUSEWHEEL: lpMsg->message = DVL_WM_KEYDOWN; if (e.wheel.y > 0) { lpMsg->wParam = GetAsyncKeyState(DVL_VK_CONTROL) ? DVL_VK_OEM_PLUS : DVL_VK_UP; } else if (e.wheel.y < 0) { lpMsg->wParam = GetAsyncKeyState(DVL_VK_CONTROL) ? DVL_VK_OEM_MINUS : DVL_VK_DOWN; } else if (e.wheel.x > 0) { lpMsg->wParam = DVL_VK_LEFT; } else if (e.wheel.x < 0) { lpMsg->wParam = DVL_VK_RIGHT; } break; #if SDL_VERSION_ATLEAST(2, 0, 4) case SDL_AUDIODEVICEADDED: return FalseAvail("SDL_AUDIODEVICEADDED", e.adevice.which); case SDL_AUDIODEVICEREMOVED: return FalseAvail("SDL_AUDIODEVICEREMOVED", e.adevice.which); case SDL_KEYMAPCHANGED: return FalseAvail("SDL_KEYMAPCHANGED", 0); #endif case SDL_TEXTEDITING: return FalseAvail("SDL_TEXTEDITING", e.edit.length); case SDL_TEXTINPUT: return FalseAvail("SDL_TEXTINPUT", e.text.windowID); case SDL_WINDOWEVENT: switch (e.window.event) { case SDL_WINDOWEVENT_SHOWN: gbActive = true; lpMsg->message = DVL_WM_PAINT; break; case SDL_WINDOWEVENT_HIDDEN: gbActive = false; break; case SDL_WINDOWEVENT_EXPOSED: lpMsg->message = DVL_WM_PAINT; break; case SDL_WINDOWEVENT_LEAVE: lpMsg->message = DVL_WM_CAPTURECHANGED; break; case SDL_WINDOWEVENT_SIZE_CHANGED: ReinitializeHardwareCursor(); break; case SDL_WINDOWEVENT_MOVED: case SDL_WINDOWEVENT_RESIZED: case SDL_WINDOWEVENT_MINIMIZED: case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_RESTORED: #if SDL_VERSION_ATLEAST(2, 0, 5) case SDL_WINDOWEVENT_TAKE_FOCUS: #endif break; case SDL_WINDOWEVENT_ENTER: // Bug in SDL, SDL_WarpMouseInWindow doesn't emit SDL_MOUSEMOTION // and SDL_GetMouseState gives previous location if mouse was // outside window (observed on Ubuntu 19.04) if (mouseWarping) { MousePosition = mousePositionWarping; mouseWarping = false; } break; case SDL_WINDOWEVENT_CLOSE: lpMsg->message = DVL_WM_QUERYENDSESSION; break; case SDL_WINDOWEVENT_FOCUS_LOST: diablo_focus_pause(); break; case SDL_WINDOWEVENT_FOCUS_GAINED: diablo_focus_unpause(); break; default: return FalseAvail("SDL_WINDOWEVENT", e.window.event); } break; #endif default: return FalseAvail("unknown", e.type); } return true; } std::ofstream demoRecording; static std::deque demo_message_queue; uint32_t demoModeLastTick = 0; void CreateDemoFile(int i) { char demoFilename[16]; snprintf(demoFilename, 15, "demo_%d.dmo", i); demoRecording.open(paths::PrefPath() + demoFilename, std::fstream::trunc); demoRecording << "0," << gSaveNumber << "," << gnScreenWidth << "," << gnScreenHeight << "\n"; } void SaveDemoMessage(tagMSG *lpMsg) { demoRecording << static_cast(DemoMsgType::Message) << "," << gfProgressToNextGameTick << "," << lpMsg->message << "," << lpMsg->wParam << "," << lpMsg->lParam << "\n"; } void PumpDemoMessage(DemoMsgType demoMsgType, uint32_t message, int32_t wParam, int32_t lParam, float progressToNextGameTick) { demoMsg msg; msg.type = demoMsgType; msg.message = message; msg.wParam = wParam; msg.lParam = lParam; msg.progressToNextGameTick = progressToNextGameTick; demo_message_queue.push_back(msg); } bool LoadDemoMessages(int i) { std::ifstream demofile; char demoFilename[16]; snprintf(demoFilename, 15, "demo_%d.dmo", i); demofile.open(paths::PrefPath() + demoFilename); if (!demofile.is_open()) { return false; } std::string line; std::getline(demofile, line); std::stringstream header(line); std::string number; std::getline(header, number, ','); // Demo version if (std::stoi(number) != 0) { return false; } std::getline(header, number, ','); gSaveNumber = std::stoi(number); std::getline(header, number, ','); uint32_t width = std::stoi(number); sgOptions.Graphics.nWidth = width; std::getline(header, number, ','); uint32_t height = std::stoi(number); sgOptions.Graphics.nHeight = height; while (std::getline(demofile, line)) { std::stringstream command(line); std::getline(command, number, ','); int typeNum = std::stoi(number); auto type = static_cast(typeNum); std::getline(command, number, ','); float progressToNextGameTick = std::stof(number); switch (type) { case DemoMsgType::Message: { std::getline(command, number, ','); uint32_t message = std::stoi(number); std::getline(command, number, ','); int32_t wParam = std::stoi(number); std::getline(command, number, ','); int32_t lParam = std::stoi(number); PumpDemoMessage(type, message, wParam, lParam, progressToNextGameTick); break; } default: PumpDemoMessage(type, 0, 0, 0, progressToNextGameTick); break; } } demofile.close(); demoModeLastTick = SDL_GetTicks(); return true; } bool DemoMessage(tagMSG *lpMsg) { SDL_Event e; if (SDL_PollEvent(&e) != 0) { if (e.type == SDL_QUIT) { lpMsg->message = DVL_WM_QUIT; lpMsg->lParam = 0; lpMsg->wParam = 0; return true; } if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { demo_message_queue.clear(); message_queue.clear(); demoMode = false; timedemo = false; last_tick = SDL_GetTicks(); } } if (!demo_message_queue.empty()) { demoMsg dmsg = demo_message_queue.front(); if (dmsg.type == DemoMsgType::Message) { lpMsg->message = dmsg.message; lpMsg->lParam = dmsg.lParam; lpMsg->wParam = dmsg.wParam; gfProgressToNextGameTick = dmsg.progressToNextGameTick; demo_message_queue.pop_front(); return true; } } lpMsg->message = 0; lpMsg->lParam = 0; lpMsg->wParam = 0; return false; } bool GetDemoRunGameLoop(bool &drawGame, bool &processInput) { if (demo_message_queue.empty()) app_fatal("Demo queue empty"); demoMsg dmsg = demo_message_queue.front(); if (dmsg.type == DemoMsgType::Message) app_fatal("Unexpected Message"); // disable additonal rendering to speedup replay drawGame = dmsg.type == DemoMsgType::GameTick; if (!timedemo) { int currentTickCount = SDL_GetTicks(); int ticksElapsed = currentTickCount - demoModeLastTick; bool tickDue = ticksElapsed >= gnTickDelay; if (tickDue) { if (dmsg.type == DemoMsgType::GameTick) { demoModeLastTick = currentTickCount; } } else { float progressToNextGameTick = clamp((float)ticksElapsed / (float)gnTickDelay, 0.F, 1.F); if (dmsg.progressToNextGameTick > progressToNextGameTick) { // we are ahead of the replay => add a additional rendering for smoothness gfProgressToNextGameTick = progressToNextGameTick; processInput = false; drawGame = true; return false; } } } gfProgressToNextGameTick = dmsg.progressToNextGameTick; demo_message_queue.pop_front(); return dmsg.type == DemoMsgType::GameTick; } bool FetchMessage(tagMSG *lpMsg) { if (demoMode) { return DemoMessage(lpMsg); } bool available = FetchMessage_Real(lpMsg); if (recordDemo != -1 && available && gbRunGame) SaveDemoMessage(lpMsg); return available; } bool TranslateMessage(const tagMSG *lpMsg) { if (lpMsg->message == DVL_WM_KEYDOWN) { int key = lpMsg->wParam; unsigned mod = (uint32_t)lpMsg->lParam >> 16; bool shift = (mod & KMOD_SHIFT) != 0; bool caps = (mod & KMOD_CAPS) != 0; bool upper = shift != caps; bool isAlpha = (key >= 'A' && key <= 'Z'); bool isNumeric = (key >= '0' && key <= '9'); bool isControl = key == DVL_VK_SPACE || key == DVL_VK_BACK || key == DVL_VK_ESCAPE || key == DVL_VK_TAB || key == DVL_VK_RETURN; bool isOem = (key >= DVL_VK_OEM_1 && key <= DVL_VK_OEM_7); if (isControl || isAlpha || isNumeric || isOem) { if (!upper && isAlpha) { key = tolower(key); } else if (shift && isNumeric) { switch (key) { case '1': key = '!'; break; case '2': key = '@'; break; case '3': key = '#'; break; case '4': key = '$'; break; case '5': key = '%'; break; case '6': key = '^'; break; case '7': key = '&'; break; case '8': key = '*'; break; case '9': key = '('; break; case '0': key = ')'; break; } } else if (isOem) { // XXX: This probably only supports US keyboard layout switch (key) { case DVL_VK_OEM_1: key = shift ? ':' : ';'; break; case DVL_VK_OEM_2: key = shift ? '?' : '/'; break; case DVL_VK_OEM_3: key = shift ? '~' : '`'; break; case DVL_VK_OEM_4: key = shift ? '{' : '['; break; case DVL_VK_OEM_5: key = shift ? '|' : '\\'; break; case DVL_VK_OEM_6: key = shift ? '}' : ']'; break; case DVL_VK_OEM_7: key = shift ? '"' : '\''; break; case DVL_VK_OEM_MINUS: key = shift ? '_' : '-'; break; case DVL_VK_OEM_PLUS: key = shift ? '+' : '='; break; case DVL_VK_OEM_PERIOD: key = shift ? '>' : '.'; break; case DVL_VK_OEM_COMMA: key = shift ? '<' : ','; break; default: UNIMPLEMENTED(); } } #ifdef _DEBUG if (key >= 32) { Log("char: {:c}", key); } #endif // XXX: This does not add extended info to lParam PostMessage(DVL_WM_CHAR, key, 0); } } return true; } bool GetAsyncKeyState(int vKey) { if (vKey == DVL_MK_LBUTTON) return (SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; if (vKey == DVL_MK_RBUTTON) return (SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; const Uint8 *state = SDLC_GetKeyState(); switch (vKey) { case DVL_VK_CONTROL: return state[SDLC_KEYSTATE_LEFTCTRL] != 0 || state[SDLC_KEYSTATE_RIGHTCTRL] != 0; case DVL_VK_SHIFT: return state[SDLC_KEYSTATE_LEFTSHIFT] != 0 || state[SDLC_KEYSTATE_RIGHTSHIFT] != 0; case DVL_VK_MENU: return state[SDLC_KEYSTATE_LALT] != 0 || state[SDLC_KEYSTATE_RALT] != 0; case DVL_VK_LEFT: return state[SDLC_KEYSTATE_LEFT] != 0; case DVL_VK_UP: return state[SDLC_KEYSTATE_UP] != 0; case DVL_VK_RIGHT: return state[SDLC_KEYSTATE_RIGHT] != 0; case DVL_VK_DOWN: return state[SDLC_KEYSTATE_DOWN] != 0; default: return false; } } void PushMessage(const tagMSG *lpMsg) { assert(CurrentProc); CurrentProc(lpMsg->message, lpMsg->wParam, lpMsg->lParam); } bool PostMessage(uint32_t type, int32_t wParam, int32_t lParam) { message_queue.push_back({ type, wParam, lParam }); return true; } } // namespace devilution