1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #include "Input.h"
5 #include "AnimationCurves.h"
6 #include "GameConfig.h"
7 #include "InputBindings.h"
8 #include "Pi.h"
9 #include "SDL.h"
10 #include "SDL_events.h"
11 #include "SDL_joystick.h"
12 
13 #include <array>
14 #include <regex>
15 #include <sstream>
16 #include <type_traits>
17 
18 using namespace Input;
19 
20 namespace Input {
21 	std::vector<sigc::slot<void, Input::Manager *>> *m_registrations;
22 
GetBindingRegistration()23 	std::vector<sigc::slot<void, Input::Manager *>> &GetBindingRegistration()
24 	{
25 		return *m_registrations;
26 	}
27 
AddBindingRegistrar(sigc::slot<void,Input::Manager * > && fn)28 	bool AddBindingRegistrar(sigc::slot<void, Input::Manager *> &&fn)
29 	{
30 		static std::vector<sigc::slot<void, Input::Manager *>> registrations;
31 		m_registrations = &registrations;
32 
33 		registrations.push_back(fn);
34 		return true;
35 	}
36 } // namespace Input
37 
38 /*
39 
40 	STATIC JOYSTICK HANDLING
41 
42 */
43 
44 namespace Input {
45 	std::map<SDL_JoystickID, JoystickInfo> m_joysticks;
46 
47 	InputBindings::Action nullAction;
48 	InputBindings::Axis nullAxis;
49 } // namespace Input
50 
JoystickName(int joystick)51 std::string Input::JoystickName(int joystick)
52 {
53 	return m_joysticks[joystick].name;
54 }
55 
JoystickGUIDString(int joystick)56 std::string Input::JoystickGUIDString(int joystick)
57 {
58 	const int guidBufferLen = 33; // as documented by SDL
59 	char guidBuffer[guidBufferLen];
60 
61 	SDL_JoystickGetGUIDString(m_joysticks[joystick].guid, guidBuffer, guidBufferLen);
62 	return std::string(guidBuffer);
63 }
64 
65 // conveniance version of JoystickFromGUID below that handles the string mangling.
JoystickFromGUIDString(const std::string & guid)66 int Input::JoystickFromGUIDString(const std::string &guid)
67 {
68 	return JoystickFromGUIDString(guid.c_str());
69 }
70 
71 // conveniance version of JoystickFromGUID below that handles the string mangling.
JoystickFromGUIDString(const char * guid)72 int Input::JoystickFromGUIDString(const char *guid)
73 {
74 	return JoystickFromGUID(SDL_JoystickGetGUIDFromString(guid));
75 }
76 
77 // return the internal ID of the stated joystick guid.
78 // returns -1 if we couldn't find the joystick in question.
JoystickFromGUID(SDL_JoystickGUID guid)79 int Input::JoystickFromGUID(SDL_JoystickGUID guid)
80 {
81 	const int guidLength = 16; // as defined
82 	for (auto pair : m_joysticks) {
83 		JoystickInfo &state = pair.second;
84 		if (0 == memcmp(state.guid.data, guid.data, guidLength)) {
85 			return static_cast<int>(pair.first);
86 		}
87 	}
88 	return -1;
89 }
90 
JoystickGUID(int joystick)91 SDL_JoystickGUID Input::JoystickGUID(int joystick)
92 {
93 	return m_joysticks[joystick].guid;
94 }
95 
saveAxisConfig(const Input::JoystickInfo::Axis & axis)96 static std::string saveAxisConfig(const Input::JoystickInfo::Axis &axis)
97 {
98 	return fmt::format("DZ{:.1f} CV{:.1f}{}", axis.deadzone, axis.curve, (axis.zeroToOne ? " Half" : ""));
99 }
100 
loadAxisConfig(const std::string & str,Input::JoystickInfo::Axis & outAxis)101 static void loadAxisConfig(const std::string &str, Input::JoystickInfo::Axis &outAxis)
102 {
103 	std::regex matcher("DZ([\\d\\.]+)\\s*(?:CV(-?[\\d\\.]+))?\\s*(Half)?", std::regex::icase);
104 	std::smatch match_results;
105 	if (std::regex_search(str, match_results, matcher)) {
106 		outAxis.deadzone = std::stof(match_results[1].str());
107 		outAxis.curve = match_results[2].matched ? std::stof(match_results[2].str()) : 1.0;
108 		outAxis.zeroToOne = match_results[3].matched;
109 	}
110 	outAxis.value = 0;
111 }
112 
InitJoysticks(IniConfig * config)113 void Input::InitJoysticks(IniConfig *config)
114 {
115 	PROFILE_SCOPED()
116 	SDL_Init(SDL_INIT_JOYSTICK);
117 
118 	int joy_count = SDL_NumJoysticks();
119 	Output("Initializing joystick subsystem.\n");
120 
121 	for (int n = 0; n < joy_count; n++) {
122 		JoystickInfo state;
123 
124 		state.joystick = SDL_JoystickOpen(n);
125 		if (!state.joystick) {
126 			Warning("SDL_JoystickOpen(%i): %s\n", n, SDL_GetError());
127 			continue;
128 		}
129 
130 		state.name = SDL_JoystickName(state.joystick);
131 		state.guid = SDL_JoystickGetGUID(state.joystick);
132 		state.axes.resize(SDL_JoystickNumAxes(state.joystick));
133 		state.buttons.resize(SDL_JoystickNumButtons(state.joystick));
134 		state.hats.resize(SDL_JoystickNumHats(state.joystick));
135 
136 		std::array<char, 33> joystickGUIDName;
137 		SDL_JoystickGetGUIDString(state.guid, joystickGUIDName.data(), joystickGUIDName.size());
138 		Output("Found joystick '%s' (GUID: %s)\n", SDL_JoystickName(state.joystick), joystickGUIDName.data());
139 		Output("  - %ld axes, %ld buttons, %ld hats\n", state.axes.size(), state.buttons.size(), state.hats.size());
140 
141 		std::string joystickName = "Joystick." + std::string(joystickGUIDName.data());
142 		config->SetString(joystickName, "Name", state.name);
143 
144 		for (size_t i = 0; i < state.axes.size(); i++) {
145 			std::string axisName = "Axis" + std::to_string(i);
146 			if (!config->HasEntry(joystickName, axisName)) {
147 				config->SetString(joystickName, axisName, saveAxisConfig(state.axes[i]));
148 				continue;
149 			}
150 
151 			loadAxisConfig(config->String(joystickName, axisName, ""), state.axes[i]);
152 			Output("  - axis %ld: deadzone %.2f, curve: %.2f, half-axis mode: %b\n",
153 				i, state.axes[i].deadzone, state.axes[i].curve, state.axes[i].zeroToOne);
154 		}
155 
156 		SDL_JoystickID joyID = SDL_JoystickInstanceID(state.joystick);
157 		m_joysticks[joyID] = state;
158 	}
159 
160 	config->Save();
161 }
162 
GetJoysticks()163 std::map<SDL_JoystickID, JoystickInfo> &Input::GetJoysticks()
164 {
165 	return m_joysticks;
166 }
167 
168 /*
169 
170 	INPUT MANAGER INITIALIZATION
171 
172 */
173 
Manager(IniConfig * config,SDL_Window * window)174 Manager::Manager(IniConfig *config, SDL_Window *window) :
175 	m_window(window),
176 	m_config(config),
177 	keyModState(0),
178 	mouseButton(),
179 	mouseMotion(),
180 	m_capturingMouse(false),
181 	joystickEnabled(true),
182 	mouseYInvert(false),
183 	m_enableBindings(true)
184 {
185 	joystickEnabled = (m_config->Int("EnableJoystick")) ? true : false;
186 	mouseYInvert = (m_config->Int("InvertMouseY")) ? true : false;
187 
188 	Input::InitJoysticks(m_config);
189 }
190 
InitGame()191 void Manager::InitGame()
192 {
193 	//reset input states
194 	keyState.clear();
195 	keyModState = 0;
196 	mouseButton.fill(0);
197 	mouseMotion.fill(0);
198 
199 	// Force a rebuild of key chords and modifier state
200 	m_frameListChanged = true;
201 
202 	for (auto &pair : Input::GetJoysticks()) {
203 		JoystickInfo &state = pair.second;
204 		std::fill(state.buttons.begin(), state.buttons.end(), false);
205 		std::fill(state.hats.begin(), state.hats.end(), 0);
206 		for (auto &ax : state.axes) {
207 			ax.value = 0.0;
208 		}
209 	}
210 }
211 
212 /*
213 
214 	BINDING AND INPUT FRAME HANDLING
215 
216 */
217 
AddAction(std::string id)218 InputBindings::Action *InputFrame::AddAction(std::string id)
219 {
220 	auto *action = manager->GetActionBinding(id);
221 	if (!action)
222 		throw std::runtime_error("Adding unknown action binding to InputFrame, id: " + id);
223 
224 	actions.push_back(action);
225 	return action;
226 }
227 
AddAxis(std::string id)228 InputBindings::Axis *InputFrame::AddAxis(std::string id)
229 {
230 	auto *axis = manager->GetAxisBinding(id);
231 	if (!axis)
232 		throw std::runtime_error("Adding unknown axis binding to an InputFrame, id: " + id);
233 
234 	axes.push_back(axis);
235 	return axis;
236 }
237 
AddInputFrame(InputFrame * frame)238 bool Manager::AddInputFrame(InputFrame *frame)
239 {
240 	auto iter = std::find(m_inputFrames.begin(), m_inputFrames.end(), frame);
241 	if (iter != m_inputFrames.end()) {
242 		m_inputFrames.erase(iter);
243 	}
244 
245 	m_inputFrames.push_back(frame);
246 	frame->active = true;
247 	frame->onFrameAdded.emit(frame);
248 	m_frameListChanged = true;
249 
250 	return true;
251 }
252 
253 // When an input frame is removed or masked by a modal frame,
254 // its actions and axes are no longer active and bindings need
255 // to be cleared to avoid orphaned state.
ClearInputFrameState(InputFrame * frame)256 void ClearInputFrameState(InputFrame *frame)
257 {
258 	for (auto *action : frame->actions) {
259 		action->binding.m_active = false;
260 		action->binding.m_queuedEvents = 0;
261 		action->binding2.m_active = false;
262 		action->binding2.m_queuedEvents = 0;
263 
264 		if (action->m_active) {
265 			action->m_active = false;
266 			action->onReleased.emit();
267 		}
268 	}
269 
270 	for (auto *axis : frame->axes) {
271 		axis->negative.m_active = false;
272 		axis->negative.m_queuedEvents = false;
273 		axis->positive.m_active = false;
274 		axis->positive.m_queuedEvents = false;
275 
276 		if (axis->m_value != 0.0) {
277 			axis->m_value = 0.0;
278 			axis->onAxisValue.emit(0.0);
279 		}
280 	}
281 }
282 
RemoveInputFrame(InputFrame * frame)283 void Manager::RemoveInputFrame(InputFrame *frame)
284 {
285 	auto it = std::find(m_inputFrames.begin(), m_inputFrames.end(), frame);
286 	if (it != m_inputFrames.end()) {
287 		m_inputFrames.erase(it);
288 
289 		ClearInputFrameState(frame);
290 		frame->active = false;
291 		frame->onFrameRemoved.emit(frame);
292 		m_frameListChanged = true;
293 	}
294 }
295 
AddActionBinding(std::string id,BindingGroup * group,InputBindings::Action && binding)296 InputBindings::Action *Manager::AddActionBinding(std::string id, BindingGroup *group, InputBindings::Action &&binding)
297 {
298 	// throw an error if we attempt to bind an action onto an already-bound axis in the same group.
299 	if (group->bindings.count(id) && group->bindings[id] != BindingGroup::ENTRY_ACTION)
300 		Error("Attempt to bind already-registered axis %s as an action.\n", id.c_str());
301 
302 	group->bindings[id] = BindingGroup::ENTRY_ACTION;
303 
304 	// Load from the config
305 	std::string config_str = m_config->String(id.c_str());
306 	if (!config_str.empty()) {
307 		std::string_view str(config_str);
308 		str >> binding;
309 	}
310 
311 	return &(actionBindings[id] = binding);
312 }
313 
AddAxisBinding(std::string id,BindingGroup * group,InputBindings::Axis && binding)314 InputBindings::Axis *Manager::AddAxisBinding(std::string id, BindingGroup *group, InputBindings::Axis &&binding)
315 {
316 	// throw an error if we attempt to bind an axis onto an already-bound action in the same group.
317 	if (group->bindings.count(id) && group->bindings[id] != BindingGroup::ENTRY_AXIS)
318 		Error("Attempt to bind already-registered action %s as an axis.\n", id.c_str());
319 
320 	group->bindings[id] = BindingGroup::ENTRY_AXIS;
321 
322 	// Load from the config
323 	std::string config_str = m_config->String(id.c_str());
324 	if (!config_str.empty()) {
325 		std::string_view str(config_str);
326 		str >> binding;
327 	}
328 
329 	return &(axisBindings[id] = binding);
330 }
331 
GetActionBinding(std::string id)332 InputBindings::Action *Manager::GetActionBinding(std::string id)
333 {
334 	return actionBindings.count(id) ? &actionBindings[id] : &Input::nullAction;
335 }
336 
GetAxisBinding(std::string id)337 InputBindings::Axis *Manager::GetAxisBinding(std::string id)
338 {
339 	return axisBindings.count(id) ? &axisBindings[id] : &Input::nullAxis;
340 }
341 
342 /*
343 
344 	STATE MANAGEMENT
345 
346 */
347 
GetBindingState(InputBindings::KeyBinding & key)348 bool Manager::GetBindingState(InputBindings::KeyBinding &key)
349 {
350 	using Type = InputBindings::KeyBinding::Type;
351 
352 	switch (key.type) {
353 	case Type::Disabled:
354 		return false;
355 	case Type::KeyboardKey:
356 		return KeyState(key.keycode);
357 	case Type::JoystickButton:
358 		return JoystickButtonState(key.joystick.id, key.joystick.button);
359 	case Type::JoystickHat:
360 		return (JoystickHatState(key.joystick.id, key.joystick.hat) & key.joystick.button) == key.joystick.button;
361 	case Type::MouseButton:
362 		return MouseButtonState(key.mouse.button);
363 	default:
364 		return false;
365 	}
366 }
367 
GetAxisState(InputBindings::JoyAxis & axis)368 float Manager::GetAxisState(InputBindings::JoyAxis &axis)
369 {
370 	if (axis.direction == 0)
371 		return 0.0; // disabled
372 
373 	return JoystickAxisState(axis.joystickId, axis.axis) * float(axis.direction);
374 }
375 
GetModifierState(InputBindings::KeyChord * chord)376 bool Manager::GetModifierState(InputBindings::KeyChord *chord)
377 {
378 	bool mod1 = chord->modifier1.Enabled() ? m_modifiers[chord->modifier1] : true;
379 	bool mod2 = chord->modifier2.Enabled() ? m_modifiers[chord->modifier2] : true;
380 	return mod1 && mod2;
381 }
382 
JoystickButtonState(int joystick,int button)383 int Manager::JoystickButtonState(int joystick, int button)
384 {
385 	if (!joystickEnabled) return 0;
386 	if (joystick < 0 || joystick >= int(GetJoysticks().size()))
387 		return 0;
388 
389 	if (button < 0 || button >= int(GetJoysticks()[joystick].buttons.size()))
390 		return 0;
391 
392 	return GetJoysticks()[joystick].buttons[button];
393 }
394 
JoystickHatState(int joystick,int hat)395 int Manager::JoystickHatState(int joystick, int hat)
396 {
397 	if (!joystickEnabled) return 0;
398 	if (joystick < 0 || joystick >= int(GetJoysticks().size()))
399 		return 0;
400 
401 	if (hat < 0 || hat >= int(GetJoysticks()[joystick].hats.size()))
402 		return 0;
403 
404 	return GetJoysticks()[joystick].hats[hat];
405 }
406 
JoystickAxisState(int joystick,int axis)407 float Manager::JoystickAxisState(int joystick, int axis)
408 {
409 	if (!joystickEnabled) return 0;
410 	if (joystick < 0 || joystick >= int(GetJoysticks().size()))
411 		return 0;
412 
413 	if (axis < 0 || axis >= int(GetJoysticks()[joystick].axes.size()))
414 		return 0;
415 
416 	return GetJoysticks()[joystick].axes[axis].value;
417 }
418 
SetJoystickEnabled(bool state)419 void Manager::SetJoystickEnabled(bool state)
420 {
421 	joystickEnabled = state;
422 	if (m_enableConfigSaving) {
423 		m_config->SetInt("EnableJoystick", joystickEnabled);
424 		m_config->Save();
425 	}
426 }
427 
SetMouseYInvert(bool state)428 void Manager::SetMouseYInvert(bool state)
429 {
430 	mouseYInvert = state;
431 	if (m_enableConfigSaving) {
432 		m_config->SetInt("InvertMouseY", mouseYInvert);
433 		m_config->Save();
434 	}
435 }
436 
437 /*
438 
439 	FRAME AND EVENT HANDLING
440 
441 */
442 
NewFrame()443 void Manager::NewFrame()
444 {
445 	mouseMotion.fill(0);
446 	mouseWheel = 0;
447 	for (auto &k : keyState) {
448 		auto &val = keyState[k.first];
449 		switch (k.second) {
450 		case 1: // if we were just pressed last frame, migrate to held state
451 			val = 2;
452 			break;
453 		case 4: // if we were just released last frame, migrate to empty state
454 			val = 0;
455 			break;
456 		default: // otherwise, no need to do anything
457 			break;
458 		}
459 	}
460 
461 	if (m_frameListChanged) {
462 		RebuildInputFrames();
463 	}
464 }
465 
RebuildInputFrames()466 void Manager::RebuildInputFrames()
467 {
468 	// Reset the list of active chords.
469 	m_chords.clear();
470 	m_activeActions.clear();
471 	m_activeAxes.clear();
472 
473 	bool hasModal = false;
474 	for (auto *frame : reverse_container(m_inputFrames)) {
475 		// Disable all frames that are masked by a modal frame
476 		// We reset all binding state here, because otherwise state is orphaned when events come in
477 		// while the modal is active and binding state no longer matches the actual hardware state
478 		// TODO: track when a frame is activated / deactivated and update state there?
479 		frame->active = !hasModal;
480 		if (hasModal) {
481 			ClearInputFrameState(frame);
482 			continue;
483 		}
484 
485 		// Push all enabled key chords onto the key chord stack.
486 		for (auto *action : frame->actions) {
487 			if (!action->Enabled())
488 				continue;
489 
490 			m_activeActions.push_back(action);
491 			assert(m_activeActions.back() == action);
492 			if (action->binding.Enabled())
493 				m_chords.push_back(&action->binding);
494 
495 			if (action->binding2.Enabled())
496 				m_chords.push_back(&action->binding2);
497 		}
498 
499 		for (auto *axis : frame->axes) {
500 			if (!axis->Enabled())
501 				continue;
502 
503 			m_activeAxes.push_back(axis);
504 			if (axis->positive.Enabled())
505 				m_chords.push_back(&axis->positive);
506 
507 			if (axis->negative.Enabled())
508 				m_chords.push_back(&axis->negative);
509 		}
510 
511 		// If we have a modal frame, it prevents input from passing through it to frames below
512 		if (frame->modal) { // modal frame blocks all inputs below it
513 			hasModal = true;
514 		}
515 	}
516 
517 	// Group all chords with the same number of modifiers together, in descending order.
518 	std::sort(m_chords.begin(), m_chords.end(), [](const InputBindings::KeyChord *a, const InputBindings::KeyChord *b) { return *a < *b; });
519 
520 	// Reinitialize the modifier list, preserving key state.
521 	m_modifiers.clear();
522 	for (auto *chord : m_chords) {
523 		if (chord->modifier1.Enabled())
524 			m_modifiers.emplace(chord->modifier1, GetBindingState(chord->modifier1));
525 		if (chord->modifier2.Enabled())
526 			m_modifiers.emplace(chord->modifier2, GetBindingState(chord->modifier2));
527 	}
528 }
529 
keys_in_chord(InputBindings::KeyChord * chord)530 static int8_t keys_in_chord(InputBindings::KeyChord *chord)
531 {
532 	return chord->activator.Enabled() + chord->modifier1.Enabled() + chord->modifier2.Enabled();
533 }
534 
applyDeadzoneAndCurve(const JoystickInfo::Axis & axis,float value)535 static float applyDeadzoneAndCurve(const JoystickInfo::Axis &axis, float value)
536 {
537 	float absVal = std::fabs(value);
538 	float sign = value < 0.0 ? 1.0 : -1.0;
539 	if (absVal < axis.deadzone) return 0.0f;
540 	// renormalize value to 0..1 after deadzone
541 	absVal = (absVal - axis.deadzone) * (1.0 / (1.0 - axis.deadzone));
542 	return AnimationCurves::SmoothEasing(absVal, axis.curve) * sign;
543 }
544 
HandleSDLEvent(SDL_Event & event)545 void Manager::HandleSDLEvent(SDL_Event &event)
546 {
547 	using namespace InputBindings;
548 
549 	switch (event.type) {
550 	case SDL_KEYDOWN:
551 		// Set key state to "just pressed"
552 		keyState[event.key.keysym.sym] = 1;
553 		keyModState = event.key.keysym.mod;
554 		onKeyPress.emit(&event.key.keysym);
555 		break;
556 	case SDL_KEYUP:
557 		// Set key state to "just released"
558 		keyState[event.key.keysym.sym] = 4;
559 		keyModState = event.key.keysym.mod;
560 		onKeyRelease.emit(&event.key.keysym);
561 		break;
562 	case SDL_MOUSEBUTTONDOWN:
563 		if (event.button.button < mouseButton.size()) {
564 			mouseButton[event.button.button] = 1;
565 			onMouseButtonDown.emit(event.button.button,
566 				event.button.x, event.button.y);
567 		}
568 		break;
569 	case SDL_MOUSEBUTTONUP:
570 		if (event.button.button < mouseButton.size()) {
571 			mouseButton[event.button.button] = 0;
572 			onMouseButtonUp.emit(event.button.button,
573 				event.button.x, event.button.y);
574 		}
575 		break;
576 	case SDL_MOUSEWHEEL:
577 		mouseWheel = event.wheel.y;
578 		onMouseWheel.emit(event.wheel.y > 0); // true = up
579 		break;
580 	case SDL_MOUSEMOTION:
581 		mouseMotion[0] += event.motion.xrel;
582 		mouseMotion[1] += event.motion.yrel;
583 		break;
584 	case SDL_JOYAXISMOTION: {
585 		if (!GetJoysticks()[event.jaxis.which].joystick)
586 			break;
587 		auto &axis = GetJoysticks()[event.jaxis.which].axes[event.jaxis.axis];
588 		if (axis.zeroToOne)
589 			// assume -32768 == 0.0 in half-axis mode (this is true for most controllers)
590 			axis.value = applyDeadzoneAndCurve(axis, (event.jaxis.value + 32768) / 65535.f);
591 		else
592 			axis.value = applyDeadzoneAndCurve(axis, (event.jaxis.value == -32768 ? -1.f : event.jaxis.value / 32767.f));
593 	} break;
594 	case SDL_JOYBUTTONUP:
595 	case SDL_JOYBUTTONDOWN:
596 		if (!GetJoysticks()[event.jaxis.which].joystick)
597 			break;
598 		GetJoysticks()[event.jbutton.which].buttons[event.jbutton.button] = event.jbutton.state != 0;
599 		break;
600 	case SDL_JOYHATMOTION:
601 		if (!GetJoysticks()[event.jaxis.which].joystick)
602 			break;
603 		GetJoysticks()[event.jhat.which].hats[event.jhat.hat] = event.jhat.value;
604 		break;
605 	default:
606 		// Don't process non-input events any further.
607 		return;
608 	}
609 
610 	// if bindings are disabled, don't process the event any further
611 	if (!m_enableBindings)
612 		return;
613 
614 	// Update the modifier status from this event
615 	for (auto &pair : m_modifiers) {
616 		auto r = pair.first.Matches(event);
617 		if (r != Response::Ignored) {
618 			pair.second = r == Response::Pressed ? true : false;
619 		}
620 	}
621 
622 	// If the event matches one of the key chords we care about, update that chord
623 	int num_keys_in_chord = 0;
624 	for (auto *chord : m_chords) {
625 		Response activator = chord->activator.Matches(event);
626 		if (activator == Response::Ignored)
627 			continue;
628 
629 		if (chord->IsActive()) {
630 			// Another press event came in for a key that's currently pressed right now.
631 			// This should be sufficiently rare that it won't be happening.
632 			if (activator == Response::Pressed)
633 				break;
634 			else { // clear the active state, continue processing the release event
635 				chord->m_active = false;
636 				// in case of a press-release sequence in the same frame, make sure to properly send updates.
637 				chord->m_queuedEvents |= 2;
638 			}
639 		} else {
640 			// Key-release event for a non-active chord. Don't handle it, but pass it on so
641 			// another key chord (with fewer / different modifiers) can handle it.
642 			if (activator == Response::Released)
643 				continue;
644 
645 			// Break here to prevent CTRL+ALT+X from activating <CTRL>+X / <ALT>+X / <NONE>+X
646 			// when there's a CTRL+ALT+X binding
647 			if (keys_in_chord(chord) < num_keys_in_chord)
648 				break;
649 
650 			bool mod1 = chord->modifier1.Enabled() ? m_modifiers[chord->modifier1] : true;
651 			bool mod2 = chord->modifier2.Enabled() ? m_modifiers[chord->modifier2] : true;
652 			if (mod1 && mod2) {
653 				// Modifiers are pressed, we can activate the chord.
654 				chord->m_active = true;
655 				// in the case of a press-release in the same frame, make sure to properly send updates
656 				chord->m_queuedEvents |= 1;
657 				// all copies of this chord should be notified, but don't propagate to chords with fewer modifiers
658 				num_keys_in_chord = keys_in_chord(chord);
659 			}
660 		}
661 	}
662 }
663 
DispatchEvents()664 void Manager::DispatchEvents()
665 {
666 	// Chords which have had their modifier keys released this frame get updated all at once
667 	for (auto *chord : m_chords) {
668 		if (chord->IsActive() && !GetModifierState(chord)) {
669 			chord->m_active = false;
670 			chord->m_queuedEvents |= 2;
671 		}
672 	}
673 
674 	for (auto *action : m_activeActions) {
675 		// if we have queued events for this binding, make sure to
676 		uint8_t queued = action->binding.m_queuedEvents | action->binding2.m_queuedEvents;
677 		if (queued) {
678 			bool wasActive = action->m_active;
679 			bool nowActive = action->binding.IsActive() || action->binding2.IsActive();
680 			action->m_active = nowActive;
681 
682 			// if at least one of the bindings was pressed this frame and the action was not
683 			// previously active, call the pressed event
684 			if (queued & 1 && !wasActive)
685 				action->onPressed.emit();
686 
687 			// if at least one of the bindings was released this frame but are not pressed currently,
688 			// call the released event
689 			if (queued & 2 && !nowActive)
690 				action->onReleased.emit();
691 
692 			// clear queued events
693 			action->binding.m_queuedEvents = action->binding2.m_queuedEvents = 0;
694 		}
695 	}
696 
697 	for (auto *axis : m_activeAxes) {
698 		float value = GetAxisState(axis->axis);
699 
700 		value += axis->positive.IsActive();
701 		value -= axis->negative.IsActive();
702 
703 		value = Clamp(value, -1.0f, 1.0f);
704 		if (value != 0.0 || axis->m_value != 0.0) {
705 			axis->m_value = value;
706 			axis->onAxisValue.emit(value);
707 		}
708 	}
709 }
710 
711 /*
712 
713 	INPUT DEVICE MANAGEMENT ROUTINES
714 
715 */
716 
SetCapturingMouse(bool grabbed)717 void Manager::SetCapturingMouse(bool grabbed)
718 {
719 	// early-out to avoid changing (possibly) expensive WM state
720 	if (grabbed == m_capturingMouse)
721 		return;
722 
723 	SDL_SetWindowGrab(m_window, SDL_bool(grabbed));
724 	SDL_SetRelativeMouseMode(SDL_bool(grabbed));
725 	m_capturingMouse = grabbed;
726 }
727