diff --git a/src/pc/controller/controller_keyboard.c b/src/pc/controller/controller_keyboard.c index eca9f2561..efa6f9e63 100644 --- a/src/pc/controller/controller_keyboard.c +++ b/src/pc/controller/controller_keyboard.c @@ -10,6 +10,16 @@ #include "../configfile.h" #include "controller_keyboard.h" +#include "pc/gfx/gfx_window_manager_api.h" +#include "pc/pc_main.h"" +#include "engine/math_util.h" + +#define SCANCODE_BACKSPACE 0x0E +#define SCANCODE_ESCAPE 0x01 +#define SCANCODE_ENTER 0x1C +#define SCANCODE_V 0x2F +#define SCANCODE_INSERT 0x152 + static int keyboard_buttons_down; #define MAX_KEYBINDS 64 @@ -18,6 +28,14 @@ static int num_keybinds = 0; static u32 keyboard_lastkey = VK_INVALID; +char textInput[MAX_TEXT_INPUT]; +static bool inTextInput = false; + +u8 held_ctrl, held_shift, held_alt; +static enum TextInputMode textInputMode; +void (*textInputOnEscape)(void) = NULL; +void (*textInputOnEnter)(void) = NULL; + static int keyboard_map_scancode(int scancode) { int ret = 0; for (int i = 0; i < num_keybinds; i++) { @@ -28,7 +46,56 @@ static int keyboard_map_scancode(int scancode) { return ret; } +static void keyboard_alter_text_input_modifier(int scancode, bool down) { + if (down) { + switch (scancode) { + case 0x1D: held_ctrl |= (1 << 0); break; + case 0x11D: held_ctrl |= (1 << 1); break; + case 0x2A: held_shift |= (1 << 0); break; + case 0x36: held_shift |= (1 << 1); break; + case 0x38: held_alt |= (1 << 0); break; + case 0x138: held_alt |= (1 << 1); break; + } + } else { + switch (scancode) { + case 0x1D: held_ctrl &= ~(1 << 0); break; + case 0x11D: held_ctrl &= ~(1 << 1); break; + case 0x2A: held_shift &= ~(1 << 0); break; + case 0x36: held_shift &= ~(1 << 1); break; + case 0x38: held_alt &= ~(1 << 0); break; + case 0x138: held_alt &= ~(1 << 1); break; + } + } +} + bool keyboard_on_key_down(int scancode) { + if (inTextInput) { + // alter the held value of modifier keys + keyboard_alter_text_input_modifier(scancode, true); + + // perform text-input-specific actions + switch (scancode) { + case SCANCODE_BACKSPACE: + textInput[max(strlen(textInput) - 1, 0)] = '\0'; + break; + case SCANCODE_ESCAPE: + if (textInputOnEscape != NULL) { textInputOnEscape(); } + break; + case SCANCODE_ENTER: + if (textInputOnEnter != NULL) { textInputOnEnter(); } + break; + case SCANCODE_V: + if (held_ctrl) { keyboard_on_text_input(wm_api->get_clipboard_text()); } + break; + case SCANCODE_INSERT: + if (held_shift) { keyboard_on_text_input(wm_api->get_clipboard_text()); } + break; + } + + // ignore any normal key down event if we're in text-input mode + return FALSE; + } + int mapped = keyboard_map_scancode(scancode); keyboard_buttons_down |= mapped; keyboard_lastkey = scancode; @@ -36,6 +103,14 @@ bool keyboard_on_key_down(int scancode) { } bool keyboard_on_key_up(int scancode) { + if (inTextInput) { + // alter the held value of modifier keys + keyboard_alter_text_input_modifier(scancode, false); + + // ignore any key up event if we're in text-input mode + return FALSE; + } + int mapped = keyboard_map_scancode(scancode); keyboard_buttons_down &= ~mapped; if (keyboard_lastkey == (u32) scancode) @@ -47,6 +122,79 @@ void keyboard_on_all_keys_up(void) { keyboard_buttons_down = 0; } +char* keyboard_start_text_input(enum TextInputMode inInputMode, void (*onEscape)(void), void (*onEnter)(void)) { + // set text-input events + textInputOnEscape = onEscape; + textInputOnEnter = onEnter; + + // clear buffer + for (int i = 0; i < MAX_TEXT_INPUT; i++) { textInput[i] = '\0'; } + + // clear held-value for modifiers + held_ctrl = 0; + held_shift = 0; + held_alt = 0; + + // start allowing text input + wm_api->start_text_input(); + textInputMode = inInputMode; + inTextInput = true; +} + +void keyboard_stop_text_input(void) { + // stop allowing text input + wm_api->stop_text_input(); + inTextInput = false; +} + +static bool keyboard_allow_character_input(char c) { + switch (textInputMode) { + case TIM_IP: + // IP only allows numbers, periods, and spaces + return (c >= '0' && c <= '9') + || (c == '.') + || (c == ' '); + + case TIM_MULTI_LINE: + // multi-line allows new-line character + if (c == '\n') { return true; } + // intentional fall-through + + case TIM_SINGLE_LINE: + // allow all characters that we can display in-game + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c == '\'') || (c == '.') + || (c == ',') || (c == '-') + || (c == '(') || (c == ')') + || (c == '&') || (c == '!') + || (c == '%') || (c == '?') + || (c == '"') || (c == '~') + || (c == '*') || (c == ' '); + } + + return false; +} + +void keyboard_on_text_input(char* text) { + // sanity check input + if (text == NULL) { return; } + + int i = strlen(textInput); + while (*text != NULL) { + // make sure we don't overrun the buffer + if (i >= MAX_TEXT_INPUT) { break; } + + // copy over character if we're allowed to input it + if (keyboard_allow_character_input(*text)) { + textInput[i++] = *text; + } + + text++; + } +} + static void keyboard_add_binds(int mask, unsigned int *scancode) { for (int i = 0; i < MAX_BINDS && num_keybinds < MAX_KEYBINDS; ++i) { if (scancode[i] < VK_BASE_KEYBOARD + VK_SIZE) { diff --git a/src/pc/controller/controller_keyboard.h b/src/pc/controller/controller_keyboard.h index 028d2e856..f148ab384 100644 --- a/src/pc/controller/controller_keyboard.h +++ b/src/pc/controller/controller_keyboard.h @@ -9,9 +9,23 @@ #ifdef __cplusplus extern "C" { #endif + +#define MAX_TEXT_INPUT 256 +extern char textInput[]; + +enum TextInputMode { + TIM_IP, + TIM_MULTI_LINE, + TIM_SINGLE_LINE, +}; + bool keyboard_on_key_down(int scancode); bool keyboard_on_key_up(int scancode); void keyboard_on_all_keys_up(void); +void keyboard_on_text_input(char* text); +char* keyboard_start_text_input(enum TextInputMode, void (*onEscape)(void), void (*onEnter)(void)); +void keyboard_stop_text_input(void); + #ifdef __cplusplus } #endif diff --git a/src/pc/gfx/gfx_dxgi.cpp b/src/pc/gfx/gfx_dxgi.cpp index fa4eb33ce..516360d71 100644 --- a/src/pc/gfx/gfx_dxgi.cpp +++ b/src/pc/gfx/gfx_dxgi.cpp @@ -44,6 +44,8 @@ using namespace Microsoft::WRL; // For ComPtr +static bool inTextInput = false; + static struct { HWND h_wnd; bool showing_error; @@ -74,6 +76,7 @@ static struct { bool (*on_key_down)(int scancode); bool (*on_key_up)(int scancode); void (*on_all_keys_up)(void); + void (*on_text_input)(char*); } dxgi; static void load_dxgi_library(void) { @@ -225,6 +228,19 @@ static void gfx_dxgi_on_resize(void) { static void onkeydown(WPARAM w_param, LPARAM l_param) { int key = ((l_param >> 16) & 0x1ff); + if (inTextInput) { + const int keyboardScanCode = (l_param >> 16) & 0x00ff; + const int virtualKey = w_param; + + BYTE keyboardState[256]; + GetKeyboardState(keyboardState); + + WORD ascii = 0; + const int len = ToAscii(virtualKey, keyboardScanCode, keyboardState, &ascii, 0); + if (len > 0) { + dxgi.on_text_input((char*)&ascii); + } + } if (dxgi.on_key_down != nullptr) { dxgi.on_key_down(key); } @@ -345,10 +361,11 @@ static void gfx_dxgi_init(const char *window_title) { update_screen_settings(); } -static void gfx_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) { +static void gfx_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void), void (*on_text_input)(char*)) { dxgi.on_key_down = on_key_down; dxgi.on_key_up = on_key_up; dxgi.on_all_keys_up = on_all_keys_up; + dxgi.on_text_input = on_text_input; } static void gfx_dxgi_main_loop(void (*run_one_game_iter)(void)) { @@ -608,6 +625,18 @@ HWND gfx_dxgi_get_h_wnd(void) { void gfx_dxgi_shutdown(void) { } +void gfx_dxgi_start_text_input(void) { inTextInput = TRUE; } +void gfx_dxgi_stop_text_input(void) { inTextInput = FALSE; } + +static char* gfx_dxgi_get_clipboard_text(void) { + if (OpenClipboard(NULL)) { + HANDLE clip = GetClipboardData(CF_TEXT); + CloseClipboard(); + return (char*)clip; + } + return NULL; +} + void ThrowIfFailed(HRESULT res) { if (FAILED(res)) { fprintf(stderr, "Error: 0x%08X\n", res); @@ -636,6 +665,9 @@ struct GfxWindowManagerAPI gfx_dxgi = { gfx_dxgi_swap_buffers_end, gfx_dxgi_get_time, gfx_dxgi_shutdown, + gfx_dxgi_start_text_input, + gfx_dxgi_stop_text_input, + gfx_dxgi_get_clipboard_text, }; #endif diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index 0b21e230c..053de7e4e 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -49,6 +49,7 @@ static int inverted_scancode_table[512]; static kb_callback_t kb_key_down = NULL; static kb_callback_t kb_key_up = NULL; static void (*kb_all_keys_up)(void) = NULL; +static void (*kb_text_input)(char*) = NULL; // whether to use timer for frame control static bool use_timer = true; @@ -279,11 +280,15 @@ static void gfx_sdl_onkeyup(int scancode) { } static void gfx_sdl_handle_events(void) { + SDL_StartTextInput(); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { #ifndef TARGET_WEB // Scancodes are broken in Emscripten SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3259 + case SDL_TEXTINPUT: + kb_text_input(event.text.text); + break; case SDL_KEYDOWN: gfx_sdl_onkeydown(event.key.keysym.scancode); break; @@ -320,10 +325,11 @@ static void gfx_sdl_handle_events(void) { } } -static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void)) { +static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void), void (*on_text_input)(char*)) { kb_key_down = on_key_down; kb_key_up = on_key_up; kb_all_keys_up = on_all_keys_up; + kb_text_input = on_text_input; } static bool gfx_sdl_start_frame(void) { @@ -361,6 +367,10 @@ static void gfx_sdl_shutdown(void) { } } +static void gfx_sdl_start_text_input(void) { SDL_StartTextInput(); } +static void gfx_sdl_stop_text_input(void) { SDL_StopTextInput(); } +static char* gfx_sdl_get_clipboard_text(void) { SDL_GetClipboardText(); } + struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_init, gfx_sdl_set_keyboard_callbacks, @@ -371,7 +381,10 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_swap_buffers_begin, gfx_sdl_swap_buffers_end, gfx_sdl_get_time, - gfx_sdl_shutdown + gfx_sdl_shutdown, + gfx_sdl_start_text_input, + gfx_sdl_stop_text_input, + gfx_sdl_get_clipboard_text, }; #endif // BACKEND_WM diff --git a/src/pc/gfx/gfx_window_manager_api.h b/src/pc/gfx/gfx_window_manager_api.h index 67ce5d1a0..bf4639c28 100644 --- a/src/pc/gfx/gfx_window_manager_api.h +++ b/src/pc/gfx/gfx_window_manager_api.h @@ -11,7 +11,7 @@ typedef bool (*kb_callback_t)(int code); struct GfxWindowManagerAPI { void (*init)(const char *window_title); - void (*set_keyboard_callbacks)(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void)); + void (*set_keyboard_callbacks)(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void), void (*on_text_input)(char*)); void (*main_loop)(void (*run_one_game_iter)(void)); void (*get_dimensions)(uint32_t *width, uint32_t *height); void (*handle_events)(void); @@ -20,6 +20,9 @@ struct GfxWindowManagerAPI { void (*swap_buffers_end)(void); double (*get_time)(void); // For debug void (*shutdown)(void); + void (*start_text_input)(void); + void (*stop_text_input)(void); + char* (*get_clipboard_text)(void); }; #endif diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index a757a8863..796b18dd5 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -55,7 +55,7 @@ struct RumbleData gRumbleDataQueue[3]; struct StructSH8031D9B0 gCurrRumbleSettings; static struct AudioAPI *audio_api; -static struct GfxWindowManagerAPI *wm_api; +struct GfxWindowManagerAPI *wm_api; static struct GfxRenderingAPI *rendering_api; extern void gfx_run(Gfx *commands); @@ -241,14 +241,14 @@ void main_func(void) { #endif char window_title[96] = - "Super Mario 64 EX (" RAPI_NAME ")" + "Super Mario 64 coop EX (" RAPI_NAME ")" #ifdef NIGHTLY " nightly " GIT_HASH #endif ; gfx_init(wm_api, rendering_api, window_title); - wm_api->set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up); + wm_api->set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up, keyboard_on_text_input); if (audio_api == NULL && audio_sdl.init()) audio_api = &audio_sdl; diff --git a/src/pc/pc_main.h b/src/pc/pc_main.h index 79bff5493..7f6e16241 100644 --- a/src/pc/pc_main.h +++ b/src/pc/pc_main.h @@ -5,6 +5,7 @@ extern "C" { #endif +extern struct GfxWindowManagerAPI* wm_api; void game_deinit(void); void game_exit(void);