/*
|
|
* Copyright (C) 2023 Christopher J. Howard
|
|
*
|
|
* This file is part of Antkeeper source code.
|
|
*
|
|
* Antkeeper source code is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Antkeeper source code is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "app/sdl/sdl-input-manager.hpp"
|
|
#include "debug/log.hpp"
|
|
#include "math/map.hpp"
|
|
#include <SDL2/SDL.h>
|
|
#include <stdexcept>
|
|
|
|
namespace app {
|
|
|
|
sdl_input_manager::sdl_input_manager()
|
|
{
|
|
// Init SDL joystick and controller subsystems
|
|
debug::log::trace("Initializing SDL joystick and controller subsystems...");
|
|
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
|
|
{
|
|
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
|
|
throw std::runtime_error("Failed to initialize SDL joystick and controller subsytems");
|
|
}
|
|
else
|
|
{
|
|
debug::log::trace("Initialized SDL joystick and controller subsystems");
|
|
}
|
|
|
|
// Register keyboard and mouse
|
|
register_keyboard(keyboard);
|
|
register_mouse(mouse);
|
|
|
|
// Generate keyboard and mouse device connected events
|
|
keyboard.connect();
|
|
mouse.connect();
|
|
}
|
|
|
|
sdl_input_manager::~sdl_input_manager()
|
|
{
|
|
// Quit SDL joystick and controller subsystems
|
|
debug::log::trace("Quitting SDL joystick and controller subsystems...");
|
|
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
|
|
debug::log::trace("Quit SDL joystick and controller subsystems...");
|
|
}
|
|
|
|
void sdl_input_manager::update()
|
|
{
|
|
// Active modifier keys
|
|
std::uint16_t sdl_key_mod = KMOD_NONE;
|
|
std::uint16_t modifier_keys = input::modifier_key::none;
|
|
|
|
// Gather SDL events from event queue
|
|
SDL_PumpEvents();
|
|
|
|
// Handle OS events
|
|
for (;;)
|
|
{
|
|
// Get next display or window event
|
|
SDL_Event event;
|
|
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED);
|
|
|
|
if (!status)
|
|
{
|
|
break;
|
|
}
|
|
else if (status < 0)
|
|
{
|
|
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
|
|
throw std::runtime_error("Failed to peep SDL events");
|
|
}
|
|
|
|
if (event.type == SDL_QUIT)
|
|
{
|
|
//...
|
|
}
|
|
}
|
|
|
|
// Handle keyboard, mouse, and gamepad events
|
|
for (;;)
|
|
{
|
|
// Get next display or window event
|
|
SDL_Event event;
|
|
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT);
|
|
|
|
if (!status)
|
|
{
|
|
break;
|
|
}
|
|
else if (status < 0)
|
|
{
|
|
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
|
|
throw std::runtime_error("Failed to peep SDL events");
|
|
}
|
|
|
|
switch (event.type)
|
|
{
|
|
[[likely]] case SDL_MOUSEMOTION:
|
|
{
|
|
mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel});
|
|
break;
|
|
}
|
|
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP:
|
|
{
|
|
// Get scancode of key
|
|
const input::scancode scancode = static_cast<input::scancode>(event.key.keysym.scancode);
|
|
|
|
// Rebuild modifier keys bit mask
|
|
if (event.key.keysym.mod != sdl_key_mod)
|
|
{
|
|
sdl_key_mod = event.key.keysym.mod;
|
|
|
|
modifier_keys = input::modifier_key::none;
|
|
if (sdl_key_mod & KMOD_LSHIFT)
|
|
modifier_keys |= input::modifier_key::left_shift;
|
|
if (sdl_key_mod & KMOD_RSHIFT)
|
|
modifier_keys |= input::modifier_key::right_shift;
|
|
if (sdl_key_mod & KMOD_LCTRL)
|
|
modifier_keys |= input::modifier_key::left_ctrl;
|
|
if (sdl_key_mod & KMOD_RCTRL)
|
|
modifier_keys |= input::modifier_key::right_ctrl;
|
|
if (sdl_key_mod & KMOD_LGUI)
|
|
modifier_keys |= input::modifier_key::left_gui;
|
|
if (sdl_key_mod & KMOD_RGUI)
|
|
modifier_keys |= input::modifier_key::right_gui;
|
|
if (sdl_key_mod & KMOD_NUM)
|
|
modifier_keys |= input::modifier_key::num_lock;
|
|
if (sdl_key_mod & KMOD_CAPS)
|
|
modifier_keys |= input::modifier_key::caps_lock;
|
|
if (sdl_key_mod & KMOD_SCROLL)
|
|
modifier_keys |= input::modifier_key::scroll_lock;
|
|
if (sdl_key_mod & KMOD_MODE)
|
|
modifier_keys |= input::modifier_key::alt_gr;
|
|
}
|
|
|
|
// Determine if event was generated from a key repeat
|
|
const bool repeat = event.key.repeat > 0;
|
|
|
|
if (event.type == SDL_KEYDOWN)
|
|
{
|
|
keyboard.press(scancode, repeat, modifier_keys);
|
|
}
|
|
else
|
|
{
|
|
keyboard.release(scancode, repeat, modifier_keys);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SDL_MOUSEWHEEL:
|
|
{
|
|
const float flip = (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f;
|
|
mouse.scroll({event.wheel.preciseX * flip, event.wheel.preciseY * flip});
|
|
break;
|
|
}
|
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
{
|
|
mouse.press(static_cast<input::mouse_button>(event.button.button));
|
|
break;
|
|
}
|
|
|
|
case SDL_MOUSEBUTTONUP:
|
|
{
|
|
mouse.release(static_cast<input::mouse_button>(event.button.button));
|
|
break;
|
|
}
|
|
|
|
[[likely]] case SDL_CONTROLLERAXISMOTION:
|
|
{
|
|
if (event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
|
|
{
|
|
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
|
|
{
|
|
// Map axis position onto `[-1, 1]`.
|
|
const float position = math::map
|
|
(
|
|
static_cast<float>(event.caxis.value),
|
|
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::min()),
|
|
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::max()),
|
|
-1.0f,
|
|
1.0f
|
|
);
|
|
|
|
// Generate gamepad axis moved event
|
|
it->second->move(static_cast<input::gamepad_axis>(event.caxis.axis), position);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
{
|
|
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
|
|
{
|
|
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
|
|
{
|
|
it->second->press(static_cast<input::gamepad_button>(event.cbutton.button));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
{
|
|
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
|
|
{
|
|
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
|
|
{
|
|
it->second->release(static_cast<input::gamepad_button>(event.cbutton.button));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
[[unlikely]] case SDL_CONTROLLERDEVICEADDED:
|
|
{
|
|
if (SDL_IsGameController(event.cdevice.which))
|
|
{
|
|
SDL_GameController* sdl_controller = SDL_GameControllerOpen(event.cdevice.which);
|
|
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which);
|
|
|
|
if (sdl_controller)
|
|
{
|
|
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
|
|
{
|
|
// Gamepad reconnected
|
|
debug::log::info("Reconnected gamepad \"{}\"", controller_name);
|
|
it->second->connect();
|
|
}
|
|
else
|
|
{
|
|
// Get gamepad GUID
|
|
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
|
|
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
|
|
|
|
// Copy into UUID struct
|
|
::uuid gamepad_uuid;
|
|
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
|
|
|
|
debug::log::info("Connected gamepad \"{}\" with UUID {}", controller_name, gamepad_uuid.string());
|
|
|
|
// Create new gamepad
|
|
input::gamepad* gamepad = new input::gamepad();
|
|
gamepad->set_uuid(gamepad_uuid);
|
|
|
|
// Add gamepad to gamepad map
|
|
gamepad_map[event.cdevice.which] = gamepad;
|
|
|
|
// Register gamepad
|
|
register_device(*gamepad);
|
|
|
|
// Generate gamepad connected event
|
|
gamepad->connect();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug::log::error("Failed to connected gamepad \"{}\": {}", controller_name, SDL_GetError());
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
|
|
{
|
|
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(event.cdevice.which);
|
|
|
|
if (sdl_controller)
|
|
{
|
|
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which);
|
|
|
|
SDL_GameControllerClose(sdl_controller);
|
|
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
|
|
{
|
|
it->second->disconnect();
|
|
}
|
|
|
|
debug::log::info("Disconnected gamepad \"{}\"", controller_name);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Flush event queue
|
|
this->event_queue.flush();
|
|
}
|
|
|
|
} // namespace app
|