diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index bf29b37f6..980c75e38 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -20,8 +20,328 @@ namespace Frontend { -WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, - std::string_view window_title) +bool KeyBinding::operator<(const KeyBinding& other) const { + return std::tie(key, modifier) < std::tie(other.key, other.modifier); +} + +// modifiers are bitwise or-d together, so we need to check if ours is in that +template +typename std::map::const_iterator FindKeyAllowingPartialModifiers(const std::map& map, KeyBinding binding) { + for (typename std::map::const_iterator it = map.cbegin(); it != map.cend(); it++) { + if ((it->first.key == binding.key) && (it->first.modifier & binding.modifier) != 0) { + return it; + } + } + return map.end(); // Return end if no match is found +} +template +typename std::map::const_iterator FindKeyAllowingOnlyNoModifiers(const std::map& map, KeyBinding binding) { + for (typename std::map::const_iterator it = map.cbegin(); it != map.cend(); it++) { + if (it->first.key == binding.key && it->first.modifier == SDL_KMOD_NONE) { + return it; + } + } + return map.end(); // Return end if no match is found +} + +// Axis map: maps key+modifier to controller axis and axis value +struct AxisMapping { + Input::Axis axis; + int value; // Value to set for key press (+127 or -127 for movement) +}; + + +// i strongly suggest you collapse these maps +std::map string_to_cbutton_map = { + {"triangle", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE}, + {"circle", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE}, + {"cross", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS}, + {"square", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE}, + {"l1", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1}, + {"l2", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L2}, + {"r1", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1}, + {"r2", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2}, + {"l3", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3}, + {"r3", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3}, + {"options", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS}, + {"touchpad", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD}, + {"up", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP}, + {"down", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN}, + {"left", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT}, + {"right", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT} +}; +std::map string_to_axis_map = { + {"axis_left_x_plus", {Input::Axis::LeftX, 127}}, + {"axis_left_x_minus", {Input::Axis::LeftX, -127}}, + {"axis_left_y_plus", {Input::Axis::LeftY, 127}}, + {"axis_left_y_minus", {Input::Axis::LeftY, -127}}, + {"axis_right_x_plus", {Input::Axis::RightX, 127}}, + {"axis_right_x_minus", {Input::Axis::RightX, -127}}, + {"axis_right_y_plus", {Input::Axis::RightY, 127}}, + {"axis_right_y_minus", {Input::Axis::RightY, -127}}, +}; +std::map string_to_keyboard_key_map = { + {"a", SDLK_A}, + {"b", SDLK_B}, + {"c", SDLK_C}, + {"d", SDLK_D}, + {"e", SDLK_E}, + {"f", SDLK_F}, + {"g", SDLK_G}, + {"h", SDLK_H}, + {"i", SDLK_I}, + {"j", SDLK_J}, + {"k", SDLK_K}, + {"l", SDLK_L}, + {"m", SDLK_M}, + {"n", SDLK_N}, + {"o", SDLK_O}, + {"p", SDLK_P}, + {"q", SDLK_Q}, + {"r", SDLK_R}, + {"s", SDLK_S}, + {"t", SDLK_T}, + {"u", SDLK_U}, + {"v", SDLK_V}, + {"w", SDLK_W}, + {"x", SDLK_X}, + {"y", SDLK_Y}, + {"z", SDLK_Z}, + {"0", SDLK_0}, + {"1", SDLK_1}, + {"2", SDLK_2}, + {"3", SDLK_3}, + {"4", SDLK_4}, + {"5", SDLK_5}, + {"6", SDLK_6}, + {"7", SDLK_7}, + {"8", SDLK_8}, + {"9", SDLK_9}, + {",", SDLK_COMMA}, + {".", SDLK_PERIOD}, + {"?", SDLK_QUESTION}, + {";", SDLK_SEMICOLON}, + {"-", SDLK_MINUS}, + {"_", SDLK_UNDERSCORE}, + {"(", SDLK_LEFTPAREN}, + {")", SDLK_RIGHTPAREN}, + {"[", SDLK_LEFTBRACKET}, + {"]", SDLK_RIGHTBRACKET}, + {"{", SDLK_LEFTBRACE}, + {"}", SDLK_RIGHTBRACE}, + {"\\", SDLK_BACKSLASH}, + {"/", SDLK_SLASH}, + {"enter", SDLK_RETURN}, + {"space", SDLK_SPACE}, + {"tab", SDLK_TAB}, + {"backspace", SDLK_BACKSPACE}, + {"escape", SDLK_ESCAPE}, + {"left", SDLK_LEFT}, + {"right", SDLK_RIGHT}, + {"up", SDLK_UP}, + {"down", SDLK_DOWN}, + {"lctrl", SDLK_LCTRL}, + {"rctrl", SDLK_RCTRL}, + {"lshift", SDLK_LSHIFT}, + {"rshift", SDLK_RSHIFT}, + {"lalt", SDLK_LALT}, + {"ralt", SDLK_RALT}, + {"lmeta", SDLK_LGUI}, + {"rmeta", SDLK_RGUI}, + {"lwin", SDLK_LGUI}, + {"rwin", SDLK_RGUI}, + {"leftbutton", SDL_BUTTON_LEFT}, + {"rightbutton", SDL_BUTTON_RIGHT}, + {"middlebutton", SDL_BUTTON_MIDDLE}, +}; +std::map string_to_keyboard_mod_key_map = { + {"lshift", SDL_KMOD_LSHIFT}, + {"rshift", SDL_KMOD_RSHIFT}, + {"lctrl", SDL_KMOD_LCTRL}, + {"rctrl", SDL_KMOD_RCTRL}, + {"lalt", SDL_KMOD_LALT}, + {"ralt", SDL_KMOD_RALT}, + {"shift", SDL_KMOD_SHIFT}, + {"ctrl", SDL_KMOD_CTRL}, + {"alt", SDL_KMOD_ALT}, + {"l_meta", SDL_KMOD_LGUI}, + {"r_meta", SDL_KMOD_RGUI}, + {"meta", SDL_KMOD_GUI}, + {"lwin", SDL_KMOD_LGUI}, + {"rwin", SDL_KMOD_RGUI}, + {"win", SDL_KMOD_GUI}, + {"none", SDL_KMOD_NONE}, // if you want to be fancy +}; + + +// Button map: maps key+modifier to controller button +std::map button_map = { + /* + //Taken keys: + //F11: fullscreen + //F10: Fps counter + //F9: toggle mouse capture + // use an other item (healing), change status in inv + {{SDLK_F, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE}, + // dodge, back in inv + {{SDLK_SPACE, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE}, + // interact, select item in inv + {{SDLK_E, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS}, + // use quick item, remove equipped item of change item desc in inv + {{SDLK_R, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE}, + + // arrow keys, but triggers normal wasd too (pls fix) + // emergency extra bullets + {{SDLK_W, SDL_KMOD_LALT}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP}, + // change quick item + {{SDLK_S, SDL_KMOD_LALT}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN}, + // change weapon in left + {{SDLK_a, SDL_KMOD_LALT}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT}, + // change weapon in right + {{SDLK_D, SDL_KMOD_LALT}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT}, + + // menu + {{SDLK_ESCAPE, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS}, + // gestures + {{SDLK_G, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD}, + + // transform + {{SDL_BUTTON_RIGHT, SDL_KMOD_LSHIFT}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1}, + // light attack + {{SDL_BUTTON_LEFT, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1}, + // shoot + {{SDL_BUTTON_RIGHT, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L2}, + // heavy attack + {{SDL_BUTTON_LEFT, SDL_KMOD_LSHIFT}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2}, + + // does nothing + {{SDLK_X, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3}, + // center cam, lock on enemy + {{SDLK_Q, SDL_KMOD_NONE}, OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3}, +*/ +}; +std::map axis_map = { + /* + // move + {{SDLK_A, SDL_KMOD_NONE}, {Input::Axis::LeftX, -127}}, + {{SDLK_D, SDL_KMOD_NONE}, {Input::Axis::LeftX, 127}}, + {{SDLK_W, SDL_KMOD_NONE}, {Input::Axis::LeftY, -127}}, + {{SDLK_S, SDL_KMOD_NONE}, {Input::Axis::LeftY, 127}}, + + //this is now using the mouse + {{SDLK_J, SDL_KMOD_NONE}, {Input::Axis::RightX, -127}}, + {{SDLK_L, SDL_KMOD_NONE}, {Input::Axis::RightX, 127}}, + {{SDLK_I, SDL_KMOD_NONE}, {Input::Axis::RightY, -127}}, + {{SDLK_K, SDL_KMOD_NONE}, {Input::Axis::RightY, 127}}, + */ +}; + +void WindowSDL::parseInputConfig(const std::string& filename) { + std::ifstream file(filename); + if (!file.is_open()) { + std::cerr << "Error opening file: " << filename << std::endl; + return; + } + + button_map.clear(); + axis_map.clear(); + int lineCount = 0; + std::string line; + while (std::getline(file, line)) { + lineCount++; + // Ignore comment lines + if (line.empty() || line[0] == '#') { + continue; + } + // Split the line by '=' + std::size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) { + std::cerr << "Invalid line format: " << line << std::endl; + continue; + } + + std::string controller_input = line.substr(0, equal_pos); + std::string kbm_input = line.substr(equal_pos + 1); + KeyBinding binding = {0, SDL_KMOD_NONE}; // Initialize KeyBinding + + // first we parse the binding, and if its wrong, we skip to the next line + std::size_t comma_pos = kbm_input.find(','); + if (comma_pos != std::string::npos) { + // Handle key + modifier + std::string key = kbm_input.substr(0, comma_pos); + std::string mod = kbm_input.substr(comma_pos + 1); + + auto key_it = string_to_keyboard_key_map.find(key); + auto mod_it = string_to_keyboard_mod_key_map.find(mod); + + if (key_it != string_to_keyboard_key_map.end() && mod_it != string_to_keyboard_mod_key_map.end()) { + binding.key = key_it->second; + binding.modifier = mod_it->second; + } else { + std::cerr << "Syntax error while parsing kbm inputs at line " << lineCount << "\n"; + continue; // skip + } + } else { + // Just a key without modifier + auto key_it = string_to_keyboard_key_map.find(kbm_input); + if (key_it != string_to_keyboard_key_map.end()) { + binding.key = key_it->second; + } else { + std::cerr << "Syntax error while parsing kbm inputs at line " << lineCount << "\n"; + continue; // skip + } + } + + // Check for axis mapping (example: axis_left_x_plus) + auto axis_it = string_to_axis_map.find(controller_input); + auto button_it = string_to_cbutton_map.find(controller_input); + if (axis_it != string_to_axis_map.end()) { + axis_map[binding] = axis_it->second; + } else if(button_it != string_to_cbutton_map.end()) { + button_map[binding] = button_it->second; + } else { + std::cerr << "Syntax error while parsing controller inputs at line " << lineCount << "\n"; + continue; // skip + } + } + file.close(); +} + +Uint32 WindowSDL::keyRepeatCallback(void* param, Uint32 id, Uint32 interval) { + auto* data = (std::pair*)param; + KeyBinding binding = { data->second->key.key, SDL_GetModState() }; + data->first->updateModKeyedInputsManually(binding); + //data->first->onKeyPress(data->second); + delete data->second; + delete data; + return 0; // Return 0 to stop the timer after firing once +} +Uint32 WindowSDL::mousePolling(void* param, Uint32 id, Uint32 interval) { + auto* data = (WindowSDL*)param; + data->updateMouse(); + return 33; // Return 0 to stop the timer after firing once +} + +void WindowSDL::updateMouse() { + float d_x = 0, d_y = 0; + SDL_GetRelativeMouseState(&d_x, &d_y); + //std::cout << "mouse polling yay!\n" << d_x << " " << d_y <<"\n"; + + float angle = atan2(d_y, d_x); + float a_x = cos(angle) * 128.0, a_y = sin(angle) * 128.0; + + if (d_x != 0 && d_y != 0) { + controller->Axis(0, Input::Axis::RightX, Input::GetAxis(-0x80, 0x80, a_x)); + controller->Axis(0, Input::Axis::RightY, Input::GetAxis(-0x80, 0x80, a_y)); + } else { + controller->Axis(0, Input::Axis::RightX, Input::GetAxis(-0x80, 0x80, 0)); + controller->Axis(0, Input::Axis::RightY, Input::GetAxis(-0x80, 0x80, 0)); + } +} + + + +WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, std::string_view window_title) : width{width_}, height{height_}, controller{controller_} { if (!SDL_Init(SDL_INIT_VIDEO)) { UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError()); @@ -70,6 +390,11 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ window_info.type = WindowSystemType::Metal; window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window)); #endif +<<<<<<< HEAD +======= + // initialize kbm controls + parseInputConfig("user/keyboardInputConfig.ini"); +>>>>>>> f031c7e1 (added support for loading keyboard config from file) } WindowSDL::~WindowSDL() = default; @@ -143,6 +468,7 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { u32 button = 0; Input::Axis axis = Input::Axis::AxisMax; +<<<<<<< HEAD int axisvalue = 0; int ax = 0; std::string backButtonBehavior = Config::getBackButtonBehavior(); @@ -313,6 +639,98 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { if (axis != Input::Axis::AxisMax) { controller->Axis(0, axis, ax); } +======= + int axis_value = 0; + + // Handle window controls outside of the input maps + if (event->type == SDL_EVENT_KEY_DOWN) { + // Toggle capture of the mouse + if (binding.key == SDLK_F9) { + SDL_SetWindowRelativeMouseMode(this->GetSdlWindow(), !SDL_GetWindowRelativeMouseMode(this->GetSdlWindow())); + } + // Reparse kbm inputs + if (binding.key == SDLK_F8) { + parseInputConfig("user/keyboardInputConfig.ini"); + } + // Toggle fullscreen + if (binding.key == SDLK_F11) { + SDL_WindowFlags flag = SDL_GetWindowFlags(window); + bool is_fullscreen = flag & SDL_WINDOW_FULLSCREEN; + SDL_SetWindowFullscreen(window, !is_fullscreen); + } + // Trigger rdoc capture + if (binding.key == SDLK_F12) { + VideoCore::TriggerCapture(); + } + } + + + // Check if the current key+modifier is a button mapping + bool button_found = false; + auto button_it = FindKeyAllowingPartialModifiers(button_map, binding); + if (button_it == button_map.end()) { + button_it = FindKeyAllowingOnlyNoModifiers(button_map, binding); + } + if (button_it != button_map.end()) { + button_found = true; + button = button_it->second; + WindowSDL::updateButton(binding, button, event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_MOUSE_BUTTON_DOWN); + } + // Check if the current key+modifier is an axis mapping + auto axis_it = FindKeyAllowingPartialModifiers(axis_map, binding); + if (axis_it == axis_map.end() && !button_found) { + axis_it = FindKeyAllowingOnlyNoModifiers(axis_map, binding); + } + if (axis_it != axis_map.end()) { + axis = axis_it->second.axis; + axis_value = (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? axis_it->second.value : 0; + int ax = Input::GetAxis(-0x80, 0x80, axis_value); + controller->Axis(0, axis, ax); + } + + +} + +// if we don't do this, then if we activate a mod keyed input and let go of the mod key first, +// the button will be stuck on the "on" state becuse the "turn off" signal would only come from +// the other key being unpressed +void WindowSDL::updateModKeyedInputsManually(Frontend::KeyBinding& binding) { + bool mod_keyed_input_found = false; + for (auto input : button_map) { + if (input.first.modifier != SDL_KMOD_NONE) { + if ((input.first.modifier & binding.modifier) == 0) { + WindowSDL::updateButton(binding, input.second, false); + } else { + mod_keyed_input_found = true; + } + } + } + for (auto input : axis_map) { + if (input.first.modifier != SDL_KMOD_NONE) { + if((input.first.modifier & binding.modifier) == 0) { + controller->Axis(0, input.second.axis, Input::GetAxis(-0x80, 0x80, 0)); + } else { + mod_keyed_input_found = true; + } + } + } + // if both non mod keyed and mod keyed inputs are used and you press the key and then the mod key in a single frame, + // both will activate but the simple one will not deactivate, unless i use this stupid looking workaround + //return; + if(!mod_keyed_input_found) return; // in this case the fix for the fix for the wrong update order is not needed + for(auto input : button_map) { + if(input.first.modifier == SDL_KMOD_NONE) { + WindowSDL::updateButton(binding, input.second, false); + } + } + for(auto input : axis_map) { + if(input.first.modifier == SDL_KMOD_NONE) { + controller->Axis(0, input.second.axis, Input::GetAxis(-0x80, 0x80, 0)); + } + } + // also this sometimes leads to janky inputs but whoever decides to intentionally create a state where this is needed + // should not deserve a smooth experience anyway +>>>>>>> f031c7e1 (added support for loading keyboard config from file) } void WindowSDL::onGamepadEvent(const SDL_Event* event) { diff --git a/src/sdl_window.h b/src/sdl_window.h index 2a5aeb38c..a178ac87f 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -75,6 +75,8 @@ private: int sdlGamepadToOrbisButton(u8 button); + void parseInputConfig(const std::string& filename); + private: s32 width; s32 height; diff --git a/user/keyboardInputConfig.ini b/user/keyboardInputConfig.ini new file mode 100644 index 000000000..60b29f5d1 --- /dev/null +++ b/user/keyboardInputConfig.ini @@ -0,0 +1,57 @@ +# controller button mappings + +# taken keys: +# f11: fullscreen +# f10: fps counter +# f9: toggle mouse capture +# f8: reparse keyboard input (this) + +# use another item (healing), change status in inventory +triangle=f +# dodge, back in inv +circle=space +# interact, select item in inv +cross=e +# use quick item, remove item in inv +square=r + +# arrow keys, but triggers normal wasd too (please fix) +# emergency extra bullets +up=w,lalt +# change quick item +down=s,lalt +# change weapon in left hand +left=a,lalt +# change weapon in right hand +right=d,lalt + +# menu +options=escape +# gestures +touchpad=g + +# transform +l1=rightbutton,lshift +# shoot +r1=leftbutton +# light attack +l2=rightbutton +# heavy attack +r2=leftbutton,lshift + +# does nothing +l3=x +# center cam, lock on +r3=q + +# axis mappings +# move +axis_left_x_minus=a +axis_left_x_plus=d +axis_left_y_minus=w +axis_left_y_plus=s +# og cam input +# axis_right_x_minus=j +# axis_right_x_plus=l +# axis_right_y_minus=i +# axis_right_y_plus=k