Antkeeper  0.0.1
sdl-input-manager.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
23 #include <engine/debug/log.hpp>
24 #include <engine/math/map.hpp>
25 #include <SDL2/SDL.h>
26 #include <stdexcept>
27 
28 namespace app {
29 
31 {
32  // Init SDL joystick and controller subsystems
33  debug::log_trace("Initializing SDL joystick and controller subsystems...");
34  if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
35  {
36  debug::log_error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
37  throw std::runtime_error("Failed to initialize SDL joystick and controller subsytems");
38  }
39  else
40  {
41  debug::log_trace("Initialized SDL joystick and controller subsystems");
42  }
43 
44  // Register keyboard and mouse
45  register_keyboard(m_keyboard);
46  register_mouse(m_mouse);
47 
48  // Generate keyboard and mouse device connected events
49  m_keyboard.connect();
50  m_mouse.connect();
51 }
52 
54 {
55  // Quit SDL joystick and controller subsystems
56  debug::log_trace("Quitting SDL joystick and controller subsystems...");
57  SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
58  debug::log_trace("Quit SDL joystick and controller subsystems...");
59 }
60 
62 {
63  // Active modifier keys
64  std::uint16_t sdl_key_mod = KMOD_NONE;
65  std::uint16_t modifier_keys = input::modifier_key::none;
66 
67  // Gather SDL events from event queue
68  SDL_PumpEvents();
69 
70  // Handle OS events
71  for (;;)
72  {
73  // Get next display or window event
74  SDL_Event event;
75  const int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED);
76 
77  if (!status)
78  {
79  break;
80  }
81  else if (status < 0)
82  {
83  debug::log_error("Failed to peep SDL events: {}", SDL_GetError());
84  throw std::runtime_error("Failed to peep SDL events");
85  }
86 
87  switch (event.type)
88  {
89  case SDL_QUIT:
90  debug::log_debug("Application quit requested");
92  break;
93 
94  default:
95  break;
96  }
97  }
98 
99  // Handle keyboard, mouse, and gamepad events
100  for (;;)
101  {
102  // Get next display or window event
103  SDL_Event event;
104  const int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT);
105 
106  if (!status)
107  {
108  break;
109  }
110  else if (status < 0)
111  {
112  debug::log_error("Failed to peep SDL events: {}", SDL_GetError());
113  throw std::runtime_error("Failed to peep SDL events");
114  }
115 
116  switch (event.type)
117  {
118  [[likely]] case SDL_MOUSEMOTION:
119  {
120  m_mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel});
121  break;
122  }
123 
124  [[likely]] case SDL_KEYDOWN:
125  case SDL_KEYUP:
126  {
127  // Get scancode of key
128  const input::scancode scancode = static_cast<input::scancode>(event.key.keysym.scancode);
129 
130  // Rebuild modifier keys bit mask
131  if (sdl_key_mod != event.key.keysym.mod)
132  {
133  sdl_key_mod = event.key.keysym.mod;
134 
135  modifier_keys = input::modifier_key::none;
136  if (sdl_key_mod & KMOD_LSHIFT)
137  modifier_keys |= input::modifier_key::left_shift;
138  if (sdl_key_mod & KMOD_RSHIFT)
139  modifier_keys |= input::modifier_key::right_shift;
140  if (sdl_key_mod & KMOD_LCTRL)
141  modifier_keys |= input::modifier_key::left_ctrl;
142  if (sdl_key_mod & KMOD_RCTRL)
143  modifier_keys |= input::modifier_key::right_ctrl;
144  if (sdl_key_mod & KMOD_LALT)
145  modifier_keys |= input::modifier_key::left_alt;
146  if (sdl_key_mod & KMOD_RALT)
147  modifier_keys |= input::modifier_key::right_alt;
148  if (sdl_key_mod & KMOD_LGUI)
149  modifier_keys |= input::modifier_key::left_gui;
150  if (sdl_key_mod & KMOD_RGUI)
151  modifier_keys |= input::modifier_key::right_gui;
152  if (sdl_key_mod & KMOD_NUM)
153  modifier_keys |= input::modifier_key::num_lock;
154  if (sdl_key_mod & KMOD_CAPS)
155  modifier_keys |= input::modifier_key::caps_lock;
156  if (sdl_key_mod & KMOD_SCROLL)
157  modifier_keys |= input::modifier_key::scroll_lock;
158  if (sdl_key_mod & KMOD_MODE)
159  modifier_keys |= input::modifier_key::alt_gr;
160  }
161 
162  if (event.type == SDL_KEYDOWN)
163  {
164  m_keyboard.press(scancode, modifier_keys, (event.key.repeat > 0));
165  }
166  else
167  {
168  m_keyboard.release(scancode, modifier_keys);
169  }
170 
171  break;
172  }
173 
174  case SDL_MOUSEWHEEL:
175  {
176  const float flip = (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f;
177  m_mouse.scroll({event.wheel.preciseX * flip, event.wheel.preciseY * flip});
178  break;
179  }
180 
181  case SDL_MOUSEBUTTONDOWN:
182  {
183  m_mouse.press(static_cast<input::mouse_button>(event.button.button));
184  break;
185  }
186 
187  case SDL_MOUSEBUTTONUP:
188  {
189  m_mouse.release(static_cast<input::mouse_button>(event.button.button));
190  break;
191  }
192 
193  [[likely]] case SDL_CONTROLLERAXISMOTION:
194  {
195  if (event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
196  {
197  if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end())
198  {
199  // Map axis position onto `[-1, 1]`.
200  const float position = math::map
201  (
202  static_cast<float>(event.caxis.value),
203  static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::min()),
204  static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::max()),
205  -1.0f,
206  1.0f
207  );
208 
209  // Generate gamepad axis moved event
210  it->second->move(static_cast<input::gamepad_axis>(event.caxis.axis), position);
211  }
212  }
213  break;
214  }
215 
216  case SDL_CONTROLLERBUTTONDOWN:
217  {
218  if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
219  {
220  if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end())
221  {
222  it->second->press(static_cast<input::gamepad_button>(event.cbutton.button));
223  }
224  }
225  break;
226  }
227 
228  case SDL_CONTROLLERBUTTONUP:
229  {
230  if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
231  {
232  if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end())
233  {
234  it->second->release(static_cast<input::gamepad_button>(event.cbutton.button));
235  }
236  }
237  break;
238  }
239 
240  [[unlikely]] case SDL_CONTROLLERDEVICEADDED:
241  {
242  if (SDL_IsGameController(event.cdevice.which))
243  {
244  SDL_GameController* sdl_controller = SDL_GameControllerOpen(event.cdevice.which);
245  if (sdl_controller)
246  {
247  // Get gamepad name
248  const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which);
249  if (!controller_name)
250  {
251  controller_name = "";
252  }
253 
254  if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end())
255  {
256  // Gamepad reconnected
257  debug::log_info("Reconnected gamepad {}", event.cdevice.which);
258  it->second->connect();
259  }
260  else
261  {
262  // Get gamepad GUID
263  SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
264  SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
265 
266  // Copy into UUID struct
267  ::uuid gamepad_uuid;
268  std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
269 
270  debug::log_info("Connected gamepad {}; name: \"{}\"; UUID: {}", event.cdevice.which, controller_name, gamepad_uuid.string());
271 
272  // Allocate gamepad
273  auto& gamepad = m_gamepad_map[event.cdevice.which];
274  gamepad = std::make_unique<input::gamepad>();
275  gamepad->set_uuid(gamepad_uuid);
276 
277  // Register gamepad
278  register_device(*gamepad);
279 
280  // Generate gamepad connected event
281  gamepad->connect();
282  }
283  }
284  else
285  {
286  debug::log_error("Failed to connect gamepad {}: {}", event.cdevice.which, SDL_GetError());
287  SDL_ClearError();
288  }
289  }
290 
291  break;
292  }
293 
294  [[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
295  {
296  SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(event.cdevice.which);
297 
298  if (sdl_controller)
299  {
300  SDL_GameControllerClose(sdl_controller);
301  if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end())
302  {
303  it->second->disconnect();
304  }
305 
306  debug::log_info("Disconnected gamepad {}", event.cdevice.which);
307  }
308 
309  break;
310  }
311 
312  default:
313  break;
314  }
315  }
316 
317  // Dispatch input update event
319 }
320 
322 {
323  if (SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE) < 0)
324  {
325  debug::log_error("Failed to set cursor visibility: \"{}\"", SDL_GetError());
326  SDL_ClearError();
327  }
328 }
329 
331 {
332  if (SDL_SetRelativeMouseMode((enabled) ? SDL_TRUE : SDL_FALSE) < 0)
333  {
334  debug::log_error("Failed to set relative mouse mode: \"{}\"", SDL_GetError());
335  SDL_ClearError();
336  }
337 }
338 
339 } // namespace app
void register_device(input::device &device)
Registers an input device.
void register_mouse(input::mouse &device)
Registers an input device.
void register_keyboard(input::keyboard &device)
Registers an input device.
::event::dispatcher m_event_dispatcher
void set_cursor_visible(bool visible) override
Shows or hides the cursor.
void set_relative_mouse_mode(bool enabled) override
Enables or disables relative mouse mode.
sdl_input_manager()
Constructs an SDL input manager.
void update() override
Processes input events.
~sdl_input_manager() override
Destructs an SDL input manager.
void dispatch(const T &message) const
Dispatches a message to subscribers of the message type.
Definition: dispatcher.hpp:77
void connect()
Simulates the device being connected.
Definition: device.cpp:24
void release(scancode scancode, std::uint16_t modifiers=modifier_key::none)
Simulates a key release.
Definition: keyboard.cpp:30
void press(scancode scancode, std::uint16_t modifiers=modifier_key::none, bool repeat=false)
Simulates a key press.
Definition: keyboard.cpp:25
void move(const math::vec2< std::int32_t > &position, const math::vec2< std::int32_t > &difference)
Simulates mouse movement.
Definition: mouse.cpp:34
void press(mouse_button button)
Simulates a mouse button press.
Definition: mouse.cpp:24
void scroll(const math::fvec2 &velocity)
Simulates mouse scrolling.
Definition: mouse.cpp:40
void release(mouse_button button)
Simulates a mouse button release.
Definition: mouse.cpp:29
status
Behavior tree node return status enumerations.
Definition: status.hpp:28
constexpr T flip(T x, int i) noexcept
Flips a single bit in a value.
Definition: bit-math.hpp:301
log_message< log_message_severity::trace, Args... > log_trace
Formats and logs a trace message.
Definition: log.hpp:88
log_message< log_message_severity::debug, Args... > log_debug
Formats and logs a debug message.
Definition: log.hpp:102
log_message< log_message_severity::error, Args... > log_error
Formats and logs an error message.
Definition: log.hpp:144
log_message< log_message_severity::info, Args... > log_info
Formats and logs an info message.
Definition: log.hpp:116
Publish-subscribe messaging.
Definition: channel.hpp:32
@ right_alt
Right alt modifier key is pressed.
@ left_gui
Left gui modifier key is pressed.
@ right_shift
Right shift modifier key is pressed.
@ left_shift
Left shift modifier key is pressed.
@ left_alt
Left alt modifier key is pressed.
@ num_lock
Num lock modifier key is pressed.
@ scroll_lock
Scroll lock modifier key is pressed.
@ left_ctrl
Left ctrl modifier key is pressed.
@ none
No modifier key is pressed.
@ right_gui
Right gui modifier key is pressed.
@ right_ctrl
Right ctrl modifier key is pressed.
@ caps_lock
Caps lock modifier key is pressed.
@ alt_gr
AltGr modifier key is pressed.
gamepad_button
Gamepad buttons.
scancode
Keyboard scancodes.
Definition: scancode.hpp:33
mouse_button
Mouse buttons.
gamepad_axis
Gamepad axes.
constexpr T map(T x, T from_min, T from_max, T to_min, T to_max) noexcept
Remaps a number from one range to another.
Definition: map.hpp:37
Event generated when the application has been requested to quit.
Event generated after input events are polled.
128-bit universally unique identifier (UUID).
Definition: uuid.hpp:32
std::array< std::byte, 16 > data
UUID data.
Definition: uuid.hpp:37
std::string string() const
Returns a string representation of the UUID.
Definition: uuid.cpp:23