#include #include #include "devilution.h" #include "stubs.h" /** @file * * * Windows message handling and keyboard event conversion for SDL. */ namespace dvl { static std::deque message_queue; static int translate_sdl_key(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; 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; 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); } DUMMY_PRINT("unknown key: name=%s sym=0x%X scan=%d mod=0x%X", SDL_GetKeyName(sym), sym, key.scancode, key.mod); return -1; } } static WPARAM keystate_for_mouse(WPARAM ret) { ret |= (SDL_GetModState() & KMOD_SHIFT) ? DVL_MK_SHIFT : 0; // XXX: other DVL_MK_* codes not implemented return ret; } static WINBOOL false_avail() { DUMMY_PRINT("return %s although event available", "false"); return false; } WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) { if (wMsgFilterMin != 0) UNIMPLEMENTED(); if (wMsgFilterMax != 0) UNIMPLEMENTED(); if (hWnd != NULL) UNIMPLEMENTED(); if (wRemoveMsg == DVL_PM_NOREMOVE) { // This does not actually fill out lpMsg, but this is ok // since the engine never uses it in this case return !message_queue.empty() || SDL_PollEvent(NULL); } if (wRemoveMsg != DVL_PM_REMOVE) { UNIMPLEMENTED(); } if (!message_queue.empty()) { *lpMsg = message_queue.front(); message_queue.pop_front(); return true; } SDL_Event e; if (!SDL_PollEvent(&e)) { return false; } lpMsg->hwnd = hWnd; lpMsg->lParam = 0; lpMsg->wParam = 0; switch (e.type) { case SDL_QUIT: lpMsg->message = DVL_WM_QUIT; break; case SDL_KEYDOWN: case SDL_KEYUP: { int key = translate_sdl_key(e.key.keysym); if (key == -1) return false_avail(); lpMsg->message = e.type == SDL_KEYDOWN ? DVL_WM_KEYDOWN : DVL_WM_KEYUP; lpMsg->wParam = (DWORD)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 = (e.motion.y << 16) | (e.motion.x & 0xFFFF); lpMsg->wParam = keystate_for_mouse(0); break; case SDL_MOUSEBUTTONDOWN: { int button = e.button.button; if (button == SDL_BUTTON_LEFT) { lpMsg->message = DVL_WM_LBUTTONDOWN; lpMsg->lParam = (e.button.y << 16) | (e.button.x & 0xFFFF); lpMsg->wParam = keystate_for_mouse(DVL_MK_LBUTTON); } else if (button == SDL_BUTTON_RIGHT) { lpMsg->message = DVL_WM_RBUTTONDOWN; lpMsg->lParam = (e.button.y << 16) | (e.button.x & 0xFFFF); lpMsg->wParam = keystate_for_mouse(DVL_MK_RBUTTON); } else { return false_avail(); } } break; case SDL_MOUSEBUTTONUP: { int button = e.button.button; if (button == SDL_BUTTON_LEFT) { lpMsg->message = DVL_WM_LBUTTONUP; lpMsg->lParam = (e.button.y << 16) | (e.button.x & 0xFFFF); lpMsg->wParam = keystate_for_mouse(0); } else if (button == SDL_BUTTON_RIGHT) { lpMsg->message = DVL_WM_RBUTTONUP; lpMsg->lParam = (e.button.y << 16) | (e.button.x & 0xFFFF); lpMsg->wParam = keystate_for_mouse(0); } else { return false_avail(); } } break; #ifndef USE_SDL1 case SDL_AUDIODEVICEADDED: case SDL_AUDIODEVICEREMOVED: case SDL_TEXTEDITING: return false_avail(); case SDL_TEXTINPUT: case SDL_WINDOWEVENT: if (e.window.event == SDL_WINDOWEVENT_CLOSE) { lpMsg->message = DVL_WM_QUERYENDSESSION; } else { return false_avail(); } break; #endif default: DUMMY_PRINT("unknown SDL message 0x%X", e.type); return false_avail(); } return true; } WINBOOL TranslateMessage(const MSG *lpMsg) { assert(lpMsg->hwnd == 0); if (lpMsg->message == DVL_WM_KEYDOWN) { int key = lpMsg->wParam; unsigned mod = (DWORD)lpMsg->lParam >> 16; bool shift = (mod & KMOD_SHIFT) != 0; bool upper = shift != (mod & KMOD_CAPS); bool is_alpha = (key >= 'A' && key <= 'Z'); bool is_numeric = (key >= '0' && key <= '9'); bool is_control = key == DVL_VK_SPACE || key == DVL_VK_BACK || key == DVL_VK_ESCAPE || key == DVL_VK_TAB || key == DVL_VK_RETURN; bool is_oem = (key >= DVL_VK_OEM_1 && key <= DVL_VK_OEM_7); if (is_control || is_alpha || is_numeric || is_oem) { if (!upper && is_alpha) { key = tolower(key); } else if (shift && is_numeric) { key = key == '0' ? ')' : key - 0x10; } else if (is_oem) { // 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) { DUMMY_PRINT("char: %c", key); } #endif // XXX: This does not add extended info to lParam PostMessageA(lpMsg->hwnd, DVL_WM_CHAR, key, 0); } } return true; } SHORT GetAsyncKeyState(int vKey) { #ifndef USE_SDL1 const Uint8 *state = SDL_GetKeyboardState(NULL); if (vKey == DVL_VK_SHIFT) { if (state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT]) { return 0x8000; } } if (vKey == DVL_VK_MENU && state[SDL_SCANCODE_M]) { return 0x8000; } #endif return 0; } LRESULT DispatchMessageA(const MSG *lpMsg) { DUMMY_ONCE(); assert(lpMsg->hwnd == 0); assert(CurrentProc); // assert(CurrentProc == GM_Game); return CurrentProc(lpMsg->hwnd, lpMsg->message, lpMsg->wParam, lpMsg->lParam); } WINBOOL PostMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { assert(hWnd == 0); MSG msg; msg.hwnd = hWnd; msg.message = Msg; msg.wParam = wParam; msg.lParam = lParam; message_queue.push_back(msg); return true; } }