// // Lol Engine // // Copyright © 2010—2016 Sam Hocevar <sam@hocevar.net> // // Lol Engine is free software. It comes without any warranty, to // the extent permitted by applicable law. You can redistribute it // and/or modify it under the terms of the Do What the Fuck You Want // to Public License, Version 2, as published by the WTFPL Task Force. // See http://www.wtfpl.net/ for more details. // #include <lol/engine-internal.h> #if LOL_USE_SDL || LOL_USE_OLD_SDL # if HAVE_SDL2_SDL_H # include <SDL2/SDL.h> # elif HAVE_SDL_SDL_H # include <SDL/SDL.h> # else # include <SDL.h> # endif #endif #include "sdlinput.h" #include "input/input_internal.h" /* We force joystick polling because no events are received when * there is no SDL display (eg. on the Raspberry Pi). */ #define SDL_FORCE_POLL_JOYSTICK 1 #if EMSCRIPTEN # define MOUSE_SPEED_MOD 10.f #else # define MOUSE_SPEED_MOD 100.f #endif namespace lol { #if LOL_USE_OLD_SDL /* Quick and dirty for now... This is deprecated anyway. */ static int sdl12_to_scancode(int ch, int sc) { if (ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A'; # define _SC(id, str, name) if (ch == str[0]) return id; # include "input/keys.h" return 0; } #else //------------------------------------------------------------------------- # define _SC(id, str, name) static const uint16_t SDLOL_##name = id; # include "input/keys.h" //------------------------------------------------------------------------- static bool ScanCodeIsValid(int sc) { switch (sc) { # define _SC(id, str, name) \ case id: return true; # include "input/keys.h" default: return false; } return false; } //------------------------------------------------------------------------- static String ScanCodeToText(int sc) { switch (sc) { # define _SC(id, str, name) \ case id: return String(str); # include "input/keys.h" default: msg::error("ScanCodeToText unknown scancode %0d\n", sc); } return String(); } //------------------------------------------------------------------------- static String ScanCodeToName(int sc) { switch (sc) { # define _SC(id, str, name) \ case id: return String(#name); # include "input/keys.h" default: msg::error("ScanCodeToText unknown scancode %0d\n", sc); } return String(); } #endif /* * SDL Input implementation class */ class SdlInputData { friend class SdlInput; private: void Tick(float seconds); static ivec2 GetMousePos(); static void SetMousePos(ivec2 position); SdlInputData(int app_w, int app_h, int screen_w, int screen_h) : m_mouse(nullptr), m_keyboard(nullptr), m_prevmouse(ivec2::zero), m_app(vec2((float)app_w, (float)app_h)), m_screen(vec2((float)screen_w, (float)screen_h)), m_mousecapture(false), m_tick_in_draw_thread(false) { } #if LOL_USE_SDL || LOL_USE_OLD_SDL array<SDL_Joystick *, InputDeviceInternal *> m_joysticks; InputDeviceInternal *m_mouse; InputDeviceInternal *m_keyboard; #endif // LOL_USE_SDL ivec2 m_prevmouse; vec2 m_app; vec2 m_screen; bool m_mousecapture; bool m_tick_in_draw_thread; }; /* * Public SdlInput class */ SdlInput::SdlInput(int app_w, int app_h, int screen_w, int screen_h) : m_data(new SdlInputData(app_w, app_h, screen_w, screen_h)) { #if _WIN32 m_data->m_tick_in_draw_thread = true; #endif #if LOL_USE_OLD_SDL /* Enable Unicode translation of keyboard events */ SDL_EnableUNICODE(1); #endif #if LOL_USE_SDL || LOL_USE_OLD_SDL SDL_Init(SDL_INIT_TIMER | SDL_INIT_JOYSTICK); #endif m_data->m_keyboard = InputDeviceInternal::CreateStandardKeyboard(); m_data->m_mouse = InputDeviceInternal::CreateStandardMouse(); #if LOL_USE_SDL || LOL_USE_OLD_SDL # if !EMSCRIPTEN # if SDL_FORCE_POLL_JOYSTICK SDL_JoystickEventState(SDL_QUERY); # else SDL_JoystickEventState(SDL_ENABLE); # endif //SDL_FORCE_POLL_JOYSTICK /* Register all the joysticks we can find, and let the input * system decide what it wants to track. */ for (int i = 0; i < SDL_NumJoysticks(); i++) { SDL_Joystick *sdlstick = SDL_JoystickOpen(i); /* Blacklist some devices: * - HDAPS, it's not a real joystick. * - X360 controllers, Xinput handles them better since * it won't think there is only one trigger axis. */ # if LOL_USE_SDL char const *name = SDL_JoystickName(sdlstick); # elif LOL_USE_OLD_SDL char const *name = SDL_JoystickName(i); # endif if (strstr(name, "HDAPS") # if LOL_USE_XINPUT || strstr(name, "XBOX 360 For Windows") # endif //LOL_USE_XINPUT || false) { SDL_JoystickClose(sdlstick); continue; } //String::format("Joystick%d", i + 1).C() InputDeviceInternal* stick = new InputDeviceInternal(g_name_joystick(i + 1)); for (int j = 0; j < SDL_JoystickNumAxes(sdlstick); ++j) stick->AddAxis(String::format("Axis%d", j + 1).C()); for (int j = 0; j < SDL_JoystickNumButtons(sdlstick); ++j) stick->AddKey(String::format("Button%d", j + 1).C()); m_data->m_joysticks.push(sdlstick, stick); } # endif //EMSCRIPTEN #endif m_gamegroup = GAMEGROUP_INPUT; } SdlInput::~SdlInput() { #if (LOL_USE_SDL || LOL_USE_OLD_SDL) && !EMSCRIPTEN /* Unregister all the joysticks we added */ while (m_data->m_joysticks.count()) { SDL_JoystickClose(m_data->m_joysticks[0].m1); delete m_data->m_joysticks[0].m2; m_data->m_joysticks.remove(0); } #endif delete m_data; } void SdlInput::TickGame(float seconds) { Entity::TickGame(seconds); if (!m_data->m_tick_in_draw_thread) m_data->Tick(seconds); } void SdlInput::TickDraw(float seconds, Scene &scene) { Entity::TickDraw(seconds, scene); if (m_data->m_tick_in_draw_thread) m_data->Tick(seconds); } void SdlInputData::Tick(float seconds) { #if LOL_USE_SDL || LOL_USE_OLD_SDL /* Pump all joystick events because no event is coming to us. */ # if SDL_FORCE_POLL_JOYSTICK && !EMSCRIPTEN SDL_JoystickUpdate(); for (int j = 0; j < m_joysticks.count(); j++) { for (int i = 0; i < SDL_JoystickNumButtons(m_joysticks[j].m1); i++) m_joysticks[j].m2->SetKey(i, SDL_JoystickGetButton(m_joysticks[j].m1, i) != 0); for (int i = 0; i < SDL_JoystickNumAxes(m_joysticks[j].m1); i++) m_joysticks[j].m2->SetAxis(i, (float)SDL_JoystickGetAxis(m_joysticks[j].m1, i) / 32768.f); } # endif m_mouse->SetAxis(4, 0); # if !LOL_USE_OLD_SDL if (m_keyboard->IsTextInputActive()) SDL_StartTextInput(); else SDL_StopTextInput(); # endif /* Handle keyboard and WM events */ SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: Ticker::Shutdown(); break; case SDL_KEYDOWN: case SDL_KEYUP: # if LOL_USE_OLD_SDL switch (int sc = sdl12_to_scancode(event.key.keysym.sym, event.key.keysym.scancode)) # else switch (int sc = event.key.keysym.scancode) # endif { //Lock management case SDLOL_CapsLock: case SDLOL_ScrollLock: case SDLOL_NumLockClear: # if defined SDLOL_CapsLock && defined SDLOL_ScrollLock && defined SDLOL_NumLockClear //Update status on key down only if (event.type == SDL_KEYDOWN) { int sc2 = sc; switch (sc) { case SDLOL_CapsLock: sc2 = SDLOL_CapsLockStatus; break; case SDLOL_ScrollLock: sc2 = SDLOL_ScrollLockStatus; break; case SDLOL_NumLockClear: sc2 = SDLOL_NumLockClearStatus; break; } m_keyboard->SetKey(sc2, !m_keyboard->GetKey(sc2)); /* DEBUG STUFF msg::info("Repeat: 0x%02x : %s/%s/%s/%i\n", (int)m_keyboard, ScanCodeToText(sc2).C(), ScanCodeToName(sc2).C(), m_keyboard->GetKey(sc2) ? "up" : "down", event.key.repeat); */ } # endif default: # if LOL_USE_OLD_SDL m_keyboard->SetKey(sc ? sc : event.key.keysym.scancode, event.type == SDL_KEYDOWN); # else if (ScanCodeIsValid(sc)) { //Set key updates the corresponding key m_keyboard->SetKey(sc, event.type == SDL_KEYDOWN); /* DEBUG STUFF msg::info("Repeat: 0x%02x : %s/%s/%s/%i\n", (int)m_keyboard, ScanCodeToText(sc).C(), ScanCodeToName(sc).C(), event.type == SDL_KEYDOWN ? "up" : "down", event.key.repeat); */ } /* DEBUG STUFF else msg::error("unknown keypress (sym 0x%02x, scancode %0d)\n", event.key.keysym.sym, event.key.keysym.scancode); */ # endif } break; # if !LOL_USE_OLD_SDL //case SDL_TEXTEDITING: //TODO: handle that ? case SDL_TEXTINPUT: m_keyboard->AddText(event.text.text); break; # endif # if LOL_USE_OLD_SDL case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if (event.button.button != SDL_BUTTON_WHEELUP && event.button.button != SDL_BUTTON_WHEELDOWN) m_mouse->SetKey(event.button.button - 1, event.type == SDL_MOUSEBUTTONDOWN); else m_mouse->SetAxis(4, (event.button.button != SDL_BUTTON_WHEELUP) ? (1) : (-1)); break; # else case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: //event.button.which m_mouse->SetKey(event.button.button - 1, event.type == SDL_MOUSEBUTTONDOWN); break; case SDL_MOUSEWHEEL: m_mouse->SetAxis(4, (float)event.button.y); break; case SDL_WINDOWEVENT: { switch (event.window.event) { case SDL_WINDOWEVENT_ENTER: case SDL_WINDOWEVENT_FOCUS_GAINED: m_mouse->SetKey(3, true); break; case SDL_WINDOWEVENT_LEAVE: case SDL_WINDOWEVENT_FOCUS_LOST: m_mouse->SetKey(3, false); break; } break; } # endif # if !SDL_FORCE_POLL_JOYSTICK case SDL_JOYAXISMOTION: m_joysticks[event.jaxis.which].m2->SetAxis(event.jaxis.axis, (float)event.jaxis.value / 32768.f); break; case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: m_joysticks[event.jbutton.which].m2->SetKey(event.jbutton.button, event.jbutton.state); break; # endif } } /* Handle mouse input */ ivec2 mouse = SdlInputData::GetMousePos(); if (InputDeviceInternal::GetMouseCapture() != m_mousecapture) { m_mousecapture = InputDeviceInternal::GetMouseCapture(); # if LOL_USE_SDL SDL_SetRelativeMouseMode(m_mousecapture ? SDL_TRUE : SDL_FALSE); # elif LOL_USE_OLD_SDL SDL_WM_GrabInput(m_mousecapture ? SDL_GRAB_ON : SDL_GRAB_OFF); # endif mouse = (ivec2)m_app / 2; SdlInputData::SetMousePos(mouse); //SDL_ShowCursor(m_mousecapture ? SDL_DISABLE : SDL_ENABLE); } if (mouse.x >= 0 && mouse.x < m_app.x && mouse.y >= 0 && mouse.y < m_app.y) { //We need the max if we want coherent mouse speed between axis float max_screen_size = lol::max(m_screen.x, m_screen.y); vec2 vmouse = vec2(mouse); vec2 vprevmouse = vec2(m_prevmouse); m_mouse->SetCursor(0, vmouse / m_app, mouse); // Note: 100.0f is an arbitrary value that makes it feel about the same than an xbox controller joystick m_mouse->SetAxis(0, (mouse.x - vprevmouse.x) * MOUSE_SPEED_MOD / max_screen_size); // Y Axis is also negated to match the usual joystick Y axis (negatives values are for the upper direction) m_mouse->SetAxis(1,-(mouse.y - vprevmouse.y) * MOUSE_SPEED_MOD / max_screen_size); //Pixel movement m_mouse->SetAxis(2, (mouse.x - vprevmouse.x)); m_mouse->SetAxis(3,-(mouse.y - vprevmouse.y)); } //Mouse is focused, Validate the InScreen Key //Hardcoded 3, not very nice. # if !EMSCRIPTEN && LOL_USE_OLD_SDL m_mouse->SetKey(3, !!(SDL_GetAppState() & SDL_APPMOUSEFOCUS)); # else //Handled in PollEvent # endif if (m_mousecapture) { mouse = ivec2(m_app * .5f); SdlInputData::SetMousePos(mouse); } m_prevmouse = mouse; #else UNUSED(seconds); #endif //LOL_USE_SDL } // NOTE: these two functions are pointless now and could be inlined directly ivec2 SdlInputData::GetMousePos() { ivec2 ret(-1, -1); #if LOL_USE_SDL || LOL_USE_OLD_SDL # if !EMSCRIPTEN && LOL_USE_OLD_SDL if (SDL_GetAppState() & SDL_APPMOUSEFOCUS) # endif { SDL_GetMouseState(&ret.x, &ret.y); ret.y = Video::GetSize().y - 1 - ret.y; } #endif return ret; } void SdlInputData::SetMousePos(ivec2 position) { #if LOL_USE_SDL // FIXME: how do I warped mouse? #elif LOL_USE_OLD_SDL SDL_WarpMouse((uint16_t)position.x, (uint16_t)position.y); #else UNUSED(position); #endif } } /* namespace lol */