all repos — mgba @ 6a188289c209a3c905dc5209640904a91165c879

mGBA Game Boy Advance Emulator

src/platform/sdl/sdl-events.c (view raw)

  1/* Copyright (c) 2013-2014 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "sdl-events.h"
  7
  8#include <mgba/core/core.h>
  9#include <mgba/core/input.h>
 10#include <mgba/core/serialize.h>
 11#include <mgba/core/thread.h>
 12#include <mgba/internal/debugger/debugger.h>
 13#include <mgba/internal/gba/input.h>
 14#include <mgba-util/configuration.h>
 15#include <mgba-util/formatting.h>
 16#include <mgba-util/vfs.h>
 17
 18#if SDL_VERSION_ATLEAST(2, 0, 0) && defined(__APPLE__)
 19#define GUI_MOD KMOD_GUI
 20#else
 21#define GUI_MOD KMOD_CTRL
 22#endif
 23
 24#define GYRO_STEPS 100
 25#define RUMBLE_PWM 16
 26#define RUMBLE_STEPS 2
 27
 28mLOG_DEFINE_CATEGORY(SDL_EVENTS, "SDL Events");
 29
 30DEFINE_VECTOR(SDL_JoystickList, struct SDL_JoystickCombo);
 31
 32#if SDL_VERSION_ATLEAST(2, 0, 0)
 33static void _mSDLSetRumble(struct mRumble* rumble, int enable);
 34#endif
 35static int32_t _mSDLReadTiltX(struct mRotationSource* rumble);
 36static int32_t _mSDLReadTiltY(struct mRotationSource* rumble);
 37static int32_t _mSDLReadGyroZ(struct mRotationSource* rumble);
 38static void _mSDLRotationSample(struct mRotationSource* source);
 39
 40bool mSDLInitEvents(struct mSDLEvents* context) {
 41#if SDL_VERSION_ATLEAST(2, 0, 4)
 42	SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
 43#endif
 44	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) {
 45		mLOG(SDL_EVENTS, ERROR, "SDL joystick initialization failed: %s", SDL_GetError());
 46	}
 47
 48#if SDL_VERSION_ATLEAST(2, 0, 0)
 49	SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
 50	if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0) {
 51		mLOG(SDL_EVENTS, ERROR, "SDL haptic initialization failed: %s", SDL_GetError());
 52	}
 53	if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
 54		mLOG(SDL_EVENTS, ERROR, "SDL video initialization failed: %s", SDL_GetError());
 55	}
 56#endif
 57
 58	SDL_JoystickEventState(SDL_ENABLE);
 59	int nJoysticks = SDL_NumJoysticks();
 60	SDL_JoystickListInit(&context->joysticks, nJoysticks);
 61	if (nJoysticks > 0) {
 62		mSDLUpdateJoysticks(context);
 63		// Some OSes don't do hotplug detection
 64		if (!SDL_JoystickListSize(&context->joysticks)) {
 65			int i;
 66			for (i = 0; i < nJoysticks; ++i) {
 67				struct SDL_JoystickCombo* joystick = SDL_JoystickListAppend(&context->joysticks);
 68				joystick->joystick = SDL_JoystickOpen(i);
 69				joystick->index = SDL_JoystickListSize(&context->joysticks) - 1;
 70#if SDL_VERSION_ATLEAST(2, 0, 0)
 71				joystick->id = SDL_JoystickInstanceID(joystick->joystick);
 72				joystick->haptic = SDL_HapticOpenFromJoystick(joystick->joystick);
 73#else
 74				joystick->id = SDL_JoystickIndex(joystick->joystick);
 75#endif
 76			}
 77		}
 78	}
 79
 80	context->playersAttached = 0;
 81
 82	size_t i;
 83	for (i = 0; i < MAX_PLAYERS; ++i) {
 84		context->preferredJoysticks[i] = 0;
 85	}
 86
 87#if !SDL_VERSION_ATLEAST(2, 0, 0)
 88	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
 89#else
 90	context->screensaverSuspendDepth = 0;
 91#endif
 92	return true;
 93}
 94
 95void mSDLDeinitEvents(struct mSDLEvents* context) {
 96	size_t i;
 97	for (i = 0; i < SDL_JoystickListSize(&context->joysticks); ++i) {
 98		struct SDL_JoystickCombo* joystick = SDL_JoystickListGetPointer(&context->joysticks, i);
 99#if SDL_VERSION_ATLEAST(2, 0, 0)
100		SDL_HapticClose(joystick->haptic);
101#endif
102		SDL_JoystickClose(joystick->joystick);
103	}
104	SDL_JoystickListDeinit(&context->joysticks);
105	SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
106}
107
108void mSDLEventsLoadConfig(struct mSDLEvents* context, const struct Configuration* config) {
109	context->preferredJoysticks[0] = mInputGetPreferredDevice(config, "gba", SDL_BINDING_BUTTON, 0);
110	context->preferredJoysticks[1] = mInputGetPreferredDevice(config, "gba", SDL_BINDING_BUTTON, 1);
111	context->preferredJoysticks[2] = mInputGetPreferredDevice(config, "gba", SDL_BINDING_BUTTON, 2);
112	context->preferredJoysticks[3] = mInputGetPreferredDevice(config, "gba", SDL_BINDING_BUTTON, 3);
113}
114
115void mSDLInitBindingsGBA(struct mInputMap* inputMap) {
116#ifdef BUILD_PANDORA
117	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_PAGEDOWN, GBA_KEY_A);
118	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_END, GBA_KEY_B);
119	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RSHIFT, GBA_KEY_L);
120	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RCTRL, GBA_KEY_R);
121	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LALT, GBA_KEY_START);
122	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LCTRL, GBA_KEY_SELECT);
123	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_UP, GBA_KEY_UP);
124	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_DOWN, GBA_KEY_DOWN);
125	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LEFT, GBA_KEY_LEFT);
126	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RIGHT, GBA_KEY_RIGHT);
127#else
128	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_x, GBA_KEY_A);
129	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_z, GBA_KEY_B);
130	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_a, GBA_KEY_L);
131	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_s, GBA_KEY_R);
132	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RETURN, GBA_KEY_START);
133	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_BACKSPACE, GBA_KEY_SELECT);
134	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_UP, GBA_KEY_UP);
135	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_DOWN, GBA_KEY_DOWN);
136	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LEFT, GBA_KEY_LEFT);
137	mInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RIGHT, GBA_KEY_RIGHT);
138#endif
139
140	struct mInputAxis description = { GBA_KEY_RIGHT, GBA_KEY_LEFT, 0x4000, -0x4000 };
141	mInputBindAxis(inputMap, SDL_BINDING_BUTTON, 0, &description);
142	description = (struct mInputAxis) { GBA_KEY_DOWN, GBA_KEY_UP, 0x4000, -0x4000 };
143	mInputBindAxis(inputMap, SDL_BINDING_BUTTON, 1, &description);
144}
145
146bool mSDLAttachPlayer(struct mSDLEvents* events, struct mSDLPlayer* player) {
147	player->joystick = 0;
148
149	if (events->playersAttached >= MAX_PLAYERS) {
150		return false;
151	}
152
153#if SDL_VERSION_ATLEAST(2, 0, 0)
154	player->rumble.d.setRumble = _mSDLSetRumble;
155	CircleBufferInit(&player->rumble.history, RUMBLE_PWM);
156	player->rumble.level = 0;
157	player->rumble.activeLevel = 0;
158	player->rumble.p = player;
159#endif
160
161	player->rotation.d.readTiltX = _mSDLReadTiltX;
162	player->rotation.d.readTiltY = _mSDLReadTiltY;
163	player->rotation.d.readGyroZ = _mSDLReadGyroZ;
164	player->rotation.d.sample = _mSDLRotationSample;
165	player->rotation.axisX = 2;
166	player->rotation.axisY = 3;
167	player->rotation.gyroSensitivity = 2.2e9f;
168	player->rotation.gyroX = 0;
169	player->rotation.gyroY = 1;
170	player->rotation.zDelta = 0;
171	CircleBufferInit(&player->rotation.zHistory, sizeof(float) * GYRO_STEPS);
172	player->rotation.p = player;
173
174	player->playerId = events->playersAttached;
175	events->players[player->playerId] = player;
176	size_t firstUnclaimed = SIZE_MAX;
177	size_t index = SIZE_MAX;
178
179	size_t i;
180	for (i = 0; i < SDL_JoystickListSize(&events->joysticks); ++i) {
181		bool claimed = false;
182
183		int p;
184		for (p = 0; p < events->playersAttached; ++p) {
185			if (events->players[p]->joystick == SDL_JoystickListGetPointer(&events->joysticks, i)) {
186				claimed = true;
187				break;
188			}
189		}
190		if (claimed) {
191			continue;
192		}
193
194		if (firstUnclaimed == SIZE_MAX) {
195			firstUnclaimed = i;
196		}
197
198		const char* joystickName;
199#if SDL_VERSION_ATLEAST(2, 0, 0)
200		joystickName = SDL_JoystickName(SDL_JoystickListGetPointer(&events->joysticks, i)->joystick);
201#else
202		joystickName = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&events->joysticks, i)->joystick));
203#endif
204		if (events->preferredJoysticks[player->playerId] && strcmp(events->preferredJoysticks[player->playerId], joystickName) == 0) {
205			index = i;
206			break;
207		}
208	}
209
210	if (index == SIZE_MAX && firstUnclaimed != SIZE_MAX) {
211		index = firstUnclaimed;
212	}
213
214	if (index != SIZE_MAX) {
215		player->joystick = SDL_JoystickListGetPointer(&events->joysticks, index);
216
217#if SDL_VERSION_ATLEAST(2, 0, 0)
218		if (player->joystick->haptic) {
219			SDL_HapticRumbleInit(player->joystick->haptic);
220		}
221#endif
222	}
223
224	++events->playersAttached;
225	return true;
226}
227
228void mSDLDetachPlayer(struct mSDLEvents* events, struct mSDLPlayer* player) {
229	if (player != events->players[player->playerId]) {
230		return;
231	}
232	int i;
233	for (i = player->playerId; i < events->playersAttached; ++i) {
234		if (i + 1 < MAX_PLAYERS) {
235			events->players[i] = events->players[i + 1];
236		}
237		if (i < events->playersAttached - 1) {
238			events->players[i]->playerId = i;
239		}
240	}
241	--events->playersAttached;
242	CircleBufferDeinit(&player->rotation.zHistory);
243}
244
245void mSDLPlayerLoadConfig(struct mSDLPlayer* context, const struct Configuration* config) {
246	mInputMapLoad(context->bindings, SDL_BINDING_KEY, config);
247	if (context->joystick) {
248		mInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config);
249#if SDL_VERSION_ATLEAST(2, 0, 0)
250		const char* name = SDL_JoystickName(context->joystick->joystick);
251#else
252		const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick->joystick));
253#endif
254		mInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, name);
255
256		const char* value;
257		char* end;
258		int numAxes = SDL_JoystickNumAxes(context->joystick->joystick);
259		int axis;
260		value = mInputGetCustomValue(config, "gba", SDL_BINDING_BUTTON, "tiltAxisX", name);
261		if (value) {
262			axis = strtol(value, &end, 0);
263			if (axis >= 0 && axis < numAxes && end && !*end) {
264				context->rotation.axisX = axis;
265			}
266		}
267		value = mInputGetCustomValue(config, "gba", SDL_BINDING_BUTTON, "tiltAxisY", name);
268		if (value) {
269			axis = strtol(value, &end, 0);
270			if (axis >= 0 && axis < numAxes && end && !*end) {
271				context->rotation.axisY = axis;
272			}
273		}
274		value = mInputGetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisX", name);
275		if (value) {
276			axis = strtol(value, &end, 0);
277			if (axis >= 0 && axis < numAxes && end && !*end) {
278				context->rotation.gyroX = axis;
279			}
280		}
281		value = mInputGetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisY", name);
282		if (value) {
283			axis = strtol(value, &end, 0);
284			if (axis >= 0 && axis < numAxes && end && !*end) {
285				context->rotation.gyroY = axis;
286			}
287		}
288		value = mInputGetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroSensitivity", name);
289		if (value) {
290			float sensitivity = strtof_u(value, &end);
291			if (end && !*end) {
292				context->rotation.gyroSensitivity = sensitivity;
293			}
294		}
295	}
296}
297
298void mSDLPlayerSaveConfig(const struct mSDLPlayer* context, struct Configuration* config) {
299	if (context->joystick) {
300#if SDL_VERSION_ATLEAST(2, 0, 0)
301		const char* name = SDL_JoystickName(context->joystick->joystick);
302#else
303		const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick->joystick));
304#endif
305		char value[12];
306		snprintf(value, sizeof(value), "%i", context->rotation.axisX);
307		mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "tiltAxisX", value, name);
308		snprintf(value, sizeof(value), "%i", context->rotation.axisY);
309		mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "tiltAxisY", value, name);
310		snprintf(value, sizeof(value), "%i", context->rotation.gyroX);
311		mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisX", value, name);
312		snprintf(value, sizeof(value), "%i", context->rotation.gyroY);
313		mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisY", value, name);
314		snprintf(value, sizeof(value), "%g", context->rotation.gyroSensitivity);
315		mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroSensitivity", value, name);
316	}
317}
318
319void mSDLPlayerChangeJoystick(struct mSDLEvents* events, struct mSDLPlayer* player, size_t index) {
320	if (player->playerId >= MAX_PLAYERS || index >= SDL_JoystickListSize(&events->joysticks)) {
321		return;
322	}
323	player->joystick = SDL_JoystickListGetPointer(&events->joysticks, index);
324}
325
326void mSDLUpdateJoysticks(struct mSDLEvents* events) {
327	// Pump SDL joystick events without eating the rest of the events
328	SDL_JoystickUpdate();
329#if SDL_VERSION_ATLEAST(2, 0, 0)
330	SDL_Event event;
331	while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED) > 0) {
332		if (event.type == SDL_JOYDEVICEADDED) {
333			struct SDL_JoystickCombo* joystick = SDL_JoystickListAppend(&events->joysticks);
334			joystick->joystick = SDL_JoystickOpen(event.jdevice.which);
335			joystick->id = SDL_JoystickInstanceID(joystick->joystick);
336			joystick->index = SDL_JoystickListSize(&events->joysticks) - 1;
337#if SDL_VERSION_ATLEAST(2, 0, 0)
338			joystick->haptic = SDL_HapticOpenFromJoystick(joystick->joystick);
339#endif
340		} else if (event.type == SDL_JOYDEVICEREMOVED) {
341			SDL_JoystickID ids[MAX_PLAYERS];
342			size_t i;
343			for (i = 0; (int) i < events->playersAttached; ++i) {
344				if (events->players[i]->joystick) {
345					ids[i] = events->players[i]->joystick->id;
346					events->players[i]->joystick = 0;
347				} else {
348					ids[i] = -1;
349				}
350			}
351			for (i = 0; i < SDL_JoystickListSize(&events->joysticks);) {
352				struct SDL_JoystickCombo* joystick = SDL_JoystickListGetPointer(&events->joysticks, i);
353				if (joystick->id == event.jdevice.which) {
354					SDL_JoystickListShift(&events->joysticks, i, 1);
355					continue;
356				}
357				SDL_JoystickListGetPointer(&events->joysticks, i)->index = i;
358				int p;
359				for (p = 0; p < events->playersAttached; ++p) {
360					if (joystick->id == ids[p]) {
361						events->players[p]->joystick = SDL_JoystickListGetPointer(&events->joysticks, i);
362					}
363				}
364				++i;
365			}
366		}
367	}
368#endif
369}
370
371static void _pauseAfterFrame(struct mCoreThread* context) {
372	context->frameCallback = 0;
373	mCoreThreadPauseFromThread(context);
374}
375
376static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer* sdlContext, const struct SDL_KeyboardEvent* event) {
377	int key = -1;
378	if (!event->keysym.mod) {
379		key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym);
380	}
381	if (key != -1) {
382		if (event->type == SDL_KEYDOWN) {
383			context->core->addKeys(context->core, 1 << key);
384		} else {
385			context->core->clearKeys(context->core, 1 << key);
386		}
387		return;
388	}
389	if (event->keysym.sym == SDLK_TAB) {
390		context->sync.audioWait = event->type != SDL_KEYDOWN;
391		return;
392	}
393	if (event->keysym.sym == SDLK_BACKQUOTE) {
394		mCoreThreadSetRewinding(context, event->type == SDL_KEYDOWN);
395	}
396	if (event->type == SDL_KEYDOWN) {
397		switch (event->keysym.sym) {
398#ifdef USE_DEBUGGERS
399		case SDLK_F11:
400			if (context->core->debugger) {
401				mDebuggerEnter(context->core->debugger, DEBUGGER_ENTER_MANUAL, NULL);
402			}
403			return;
404#endif
405#ifdef USE_PNG
406		case SDLK_F12:
407			mCoreTakeScreenshot(context->core);
408			return;
409#endif
410		case SDLK_BACKSLASH:
411			mCoreThreadPause(context);
412			context->frameCallback = _pauseAfterFrame;
413			mCoreThreadUnpause(context);
414			return;
415#ifdef BUILD_PANDORA
416		case SDLK_ESCAPE:
417			mCoreThreadEnd(context);
418			return;
419#endif
420		default:
421			if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) {
422				switch (event->keysym.sym) {
423#if SDL_VERSION_ATLEAST(2, 0, 0)
424				case SDLK_f:
425					SDL_SetWindowFullscreen(sdlContext->window, sdlContext->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
426					sdlContext->fullscreen = !sdlContext->fullscreen;
427					sdlContext->windowUpdated = 1;
428					break;
429#endif
430				case SDLK_p:
431					mCoreThreadTogglePause(context);
432					break;
433				case SDLK_n:
434					mCoreThreadPause(context);
435					context->frameCallback = _pauseAfterFrame;
436					mCoreThreadUnpause(context);
437					break;
438				case SDLK_r:
439					mCoreThreadReset(context);
440					break;
441				default:
442					break;
443				}
444			}
445			if (event->keysym.mod & KMOD_SHIFT) {
446				switch (event->keysym.sym) {
447				case SDLK_F1:
448				case SDLK_F2:
449				case SDLK_F3:
450				case SDLK_F4:
451				case SDLK_F5:
452				case SDLK_F6:
453				case SDLK_F7:
454				case SDLK_F8:
455				case SDLK_F9:
456					mCoreThreadInterrupt(context);
457					mCoreSaveState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SAVEDATA | SAVESTATE_SCREENSHOT);
458					mCoreThreadContinue(context);
459					break;
460				default:
461					break;
462				}
463			} else {
464				switch (event->keysym.sym) {
465				case SDLK_F1:
466				case SDLK_F2:
467				case SDLK_F3:
468				case SDLK_F4:
469				case SDLK_F5:
470				case SDLK_F6:
471				case SDLK_F7:
472				case SDLK_F8:
473				case SDLK_F9:
474					mCoreThreadInterrupt(context);
475					mCoreLoadState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT);
476					mCoreThreadContinue(context);
477					break;
478				default:
479					break;
480				}
481			}
482			return;
483		}
484	}
485}
486
487static void _mSDLHandleJoyButton(struct mCore* core, struct mSDLPlayer* sdlContext, const struct SDL_JoyButtonEvent* event) {
488	int key = 0;
489	key = mInputMapKey(sdlContext->bindings, SDL_BINDING_BUTTON, event->button);
490	if (key == -1) {
491		return;
492	}
493
494	if (event->type == SDL_JOYBUTTONDOWN) {
495		core->addKeys(core, 1 << key);
496	} else {
497		core->clearKeys(core, 1 << key);
498	}
499}
500
501static void _mSDLHandleJoyAxis(struct mCore* core, struct mSDLPlayer* sdlContext, const struct SDL_JoyAxisEvent* event) {
502	int clearKeys = ~mInputClearAxis(sdlContext->bindings, SDL_BINDING_BUTTON, event->axis, -1);
503	int newKeys = 0;
504	int key = mInputMapAxis(sdlContext->bindings, SDL_BINDING_BUTTON, event->axis, event->value);
505	if (key != -1) {
506		newKeys |= 1 << key;
507	}
508	clearKeys &= ~newKeys;
509	core->clearKeys(core, clearKeys);
510	core->addKeys(core, newKeys);
511
512}
513
514#if SDL_VERSION_ATLEAST(2, 0, 0)
515static void _mSDLHandleWindowEvent(struct mSDLPlayer* sdlContext, const struct SDL_WindowEvent* event) {
516	switch (event->event) {
517	case SDL_WINDOWEVENT_SIZE_CHANGED:
518		sdlContext->windowUpdated = 1;
519		break;
520	}
521}
522#endif
523
524void mSDLHandleEvent(struct mCoreThread* context, struct mSDLPlayer* sdlContext, const union SDL_Event* event) {
525	switch (event->type) {
526	case SDL_QUIT:
527		mCoreThreadEnd(context);
528		break;
529#if SDL_VERSION_ATLEAST(2, 0, 0)
530	case SDL_WINDOWEVENT:
531		_mSDLHandleWindowEvent(sdlContext, &event->window);
532		break;
533#endif
534	case SDL_KEYDOWN:
535	case SDL_KEYUP:
536		_mSDLHandleKeypress(context, sdlContext, &event->key);
537		break;
538	case SDL_JOYBUTTONDOWN:
539	case SDL_JOYBUTTONUP:
540		_mSDLHandleJoyButton(context->core, sdlContext, &event->jbutton);
541		break;
542	case SDL_JOYHATMOTION:
543		// TODO
544		break;
545	case SDL_JOYAXISMOTION:
546		_mSDLHandleJoyAxis(context->core, sdlContext, &event->jaxis);
547		break;
548	}
549}
550
551#if SDL_VERSION_ATLEAST(2, 0, 0)
552static void _mSDLSetRumble(struct mRumble* rumble, int enable) {
553	struct mSDLRumble* sdlRumble = (struct mSDLRumble*) rumble;
554	if (!sdlRumble->p->joystick || !sdlRumble->p->joystick->haptic || !SDL_HapticRumbleSupported(sdlRumble->p->joystick->haptic)) {
555		return;
556	}
557	int8_t originalLevel = sdlRumble->level;
558	sdlRumble->level += enable;
559	if (CircleBufferSize(&sdlRumble->history) == RUMBLE_PWM) {
560		int8_t oldLevel;
561		CircleBufferRead8(&sdlRumble->history, &oldLevel);
562		sdlRumble->level -= oldLevel;
563	}
564	CircleBufferWrite8(&sdlRumble->history, enable);
565	if (sdlRumble->level == originalLevel) {
566		return;
567	}
568	float activeLevel = ceil(RUMBLE_STEPS * sdlRumble->level / (float) RUMBLE_PWM) / RUMBLE_STEPS;
569	if (fabsf(sdlRumble->activeLevel - activeLevel) < 0.75 / RUMBLE_STEPS) {
570		return;
571	}
572	sdlRumble->activeLevel = activeLevel;
573	if (sdlRumble->activeLevel > 0.5 / RUMBLE_STEPS) {
574		SDL_HapticRumbleStop(sdlRumble->p->joystick->haptic);
575		SDL_HapticRumblePlay(sdlRumble->p->joystick->haptic, activeLevel, 500);
576	} else {
577		SDL_HapticRumbleStop(sdlRumble->p->joystick->haptic);
578	}
579}
580#endif
581
582static int32_t _readTilt(struct mSDLPlayer* player, int axis) {
583	if (!player->joystick) {
584		return 0;
585	}
586	return SDL_JoystickGetAxis(player->joystick->joystick, axis) * 0x3800;
587}
588
589static int32_t _mSDLReadTiltX(struct mRotationSource* source) {
590	struct mSDLRotation* rotation = (struct mSDLRotation*) source;
591	return _readTilt(rotation->p, rotation->axisX);
592}
593
594static int32_t _mSDLReadTiltY(struct mRotationSource* source) {
595	struct mSDLRotation* rotation = (struct mSDLRotation*) source;
596	return _readTilt(rotation->p, rotation->axisY);
597}
598
599static int32_t _mSDLReadGyroZ(struct mRotationSource* source) {
600	struct mSDLRotation* rotation = (struct mSDLRotation*) source;
601	float z = rotation->zDelta;
602	return z * rotation->gyroSensitivity;
603}
604
605static void _mSDLRotationSample(struct mRotationSource* source) {
606	struct mSDLRotation* rotation = (struct mSDLRotation*) source;
607	SDL_JoystickUpdate();
608	if (!rotation->p->joystick) {
609		return;
610	}
611
612	int x = SDL_JoystickGetAxis(rotation->p->joystick->joystick, rotation->gyroX);
613	int y = SDL_JoystickGetAxis(rotation->p->joystick->joystick, rotation->gyroY);
614	union {
615		float f;
616		int32_t i;
617	} theta = { .f = atan2f(y, x) - atan2f(rotation->oldY, rotation->oldX) };
618	if (isnan(theta.f)) {
619		theta.f = 0.0f;
620	} else if (theta.f > M_PI) {
621		theta.f -= 2.0f * M_PI;
622	} else if (theta.f < -M_PI) {
623		theta.f += 2.0f * M_PI;
624	}
625	rotation->oldX = x;
626	rotation->oldY = y;
627
628	float oldZ = 0;
629	if (CircleBufferSize(&rotation->zHistory) == GYRO_STEPS * sizeof(float)) {
630		CircleBufferRead32(&rotation->zHistory, (int32_t*) &oldZ);
631	}
632	CircleBufferWrite32(&rotation->zHistory, theta.i);
633	rotation->zDelta += theta.f - oldZ;
634}
635
636#if SDL_VERSION_ATLEAST(2, 0, 0)
637void mSDLSuspendScreensaver(struct mSDLEvents* events) {
638	if (events->screensaverSuspendDepth == 0 && events->screensaverSuspendable) {
639		SDL_DisableScreenSaver();
640	}
641	++events->screensaverSuspendDepth;
642}
643
644void mSDLResumeScreensaver(struct mSDLEvents* events) {
645	--events->screensaverSuspendDepth;
646	if (events->screensaverSuspendDepth == 0 && events->screensaverSuspendable) {
647		SDL_EnableScreenSaver();
648	}
649}
650
651void mSDLSetScreensaverSuspendable(struct mSDLEvents* events, bool suspendable) {
652	bool wasSuspendable = events->screensaverSuspendable;
653	events->screensaverSuspendable = suspendable;
654	if (events->screensaverSuspendDepth > 0) {
655		if (suspendable && !wasSuspendable) {
656			SDL_DisableScreenSaver();
657		} else if (!suspendable && wasSuspendable) {
658			SDL_EnableScreenSaver();
659		}
660	} else {
661		SDL_EnableScreenSaver();
662	}
663}
664#endif