1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #define FORBIDDEN_SYMBOL_EXCEPTION_time_h
24 
25 #include "backends/platform/3ds/osystem.h"
26 
27 #include "backends/keymapper/action.h"
28 #include "backends/keymapper/keymapper-defaults.h"
29 #include "backends/keymapper/hardware-input.h"
30 #include "backends/keymapper/keymap.h"
31 #include "backends/keymapper/keymapper.h"
32 #include "backends/keymapper/standard-actions.h"
33 #include "backends/platform/3ds/config.h"
34 #include "backends/platform/3ds/options-dialog.h"
35 #include "backends/timer/default/default-timer.h"
36 #include "common/translation.h"
37 #include "engines/engine.h"
38 #include "gui/gui-manager.h"
39 
40 namespace N3DS {
41 
42 static Common::Mutex *eventMutex;
43 static InputMode inputMode = MODE_DRAG;
44 static InputMode savedInputMode = MODE_DRAG;
45 static aptHookCookie cookie;
46 
47 static const Common::HardwareInputTableEntry ctrJoystickButtons[] = {
48 	{ "JOY_A",              Common::JOYSTICK_BUTTON_A,              _s("A")           },
49 	{ "JOY_B",              Common::JOYSTICK_BUTTON_B,              _s("B")           },
50 	{ "JOY_X",              Common::JOYSTICK_BUTTON_X,              _s("X")           },
51 	{ "JOY_Y",              Common::JOYSTICK_BUTTON_Y,              _s("Y")           },
52 	{ "JOY_BACK",           Common::JOYSTICK_BUTTON_BACK,           _s("Select")      },
53 	{ "JOY_START",          Common::JOYSTICK_BUTTON_START,          _s("Start")       },
54 	{ "JOY_LEFT_STICK",     Common::JOYSTICK_BUTTON_LEFT_STICK,     _s("ZL")          },
55 	{ "JOY_RIGHT_STICK",    Common::JOYSTICK_BUTTON_RIGHT_STICK,    _s("ZR")          },
56 	{ "JOY_LEFT_SHOULDER",  Common::JOYSTICK_BUTTON_LEFT_SHOULDER,  _s("L")           },
57 	{ "JOY_RIGHT_SHOULDER", Common::JOYSTICK_BUTTON_RIGHT_SHOULDER, _s("R")           },
58 	{ "JOY_UP",             Common::JOYSTICK_BUTTON_DPAD_UP,        _s("D-pad Up")    },
59 	{ "JOY_DOWN",           Common::JOYSTICK_BUTTON_DPAD_DOWN,      _s("D-pad Down")  },
60 	{ "JOY_LEFT",           Common::JOYSTICK_BUTTON_DPAD_LEFT,      _s("D-pad Left")  },
61 	{ "JOY_RIGHT",          Common::JOYSTICK_BUTTON_DPAD_RIGHT,     _s("D-pad Right") },
62 	{ nullptr,              0,                                      nullptr           }
63 };
64 
65 static const Common::AxisTableEntry ctrJoystickAxes[] = {
66 	{ "JOY_LEFT_STICK_X", Common::JOYSTICK_AXIS_LEFT_STICK_X, Common::kAxisTypeFull, _s("C-Pad X") },
67 	{ "JOY_LEFT_STICK_Y", Common::JOYSTICK_AXIS_LEFT_STICK_Y, Common::kAxisTypeFull, _s("C-Pad Y") },
68 	{ nullptr,            0,                                  Common::kAxisTypeFull, nullptr       }
69 };
70 
71 const Common::HardwareInputTableEntry ctrMouseButtons[] = {
72 	{ "MOUSE_LEFT",   Common::MOUSE_BUTTON_LEFT,   _s("Touch") },
73 	{ nullptr,        0,                           nullptr     }
74 };
75 
76 static const int16 CIRCLE_MAX = 160;
77 
pushEventQueue(Common::Queue<Common::Event> * queue,Common::Event & event)78 static void pushEventQueue(Common::Queue<Common::Event> *queue, Common::Event &event) {
79 	Common::StackLock lock(*eventMutex);
80 	queue->push(event);
81 }
82 
doJoyEvent(Common::Queue<Common::Event> * queue,u32 keysPressed,u32 keysReleased,u32 ctrKey,uint8 svmButton)83 static void doJoyEvent(Common::Queue<Common::Event> *queue, u32 keysPressed, u32 keysReleased, u32 ctrKey, uint8 svmButton) {
84 	if (keysPressed & ctrKey || keysReleased & ctrKey) {
85 		Common::Event event;
86 		event.type = (keysPressed & ctrKey) ? Common::EVENT_JOYBUTTON_DOWN : Common::EVENT_JOYBUTTON_UP;
87 		event.joystick.button = svmButton;
88 
89 		pushEventQueue(queue, event);
90 	}
91 }
92 
eventThreadFunc(void * arg)93 static void eventThreadFunc(void *arg) {
94 	OSystem_3DS *osys = dynamic_cast<OSystem_3DS *>(g_system);
95 	Common::Queue<Common::Event> *eventQueue = (Common::Queue<Common::Event> *)arg;
96 
97 	uint32 touchStartTime = osys->getMillis();
98 	touchPosition  lastTouch  = {0, 0};
99 	circlePosition lastCircle = {0, 0};
100 	int borderSnapZone = 6;
101 	Common::Event event;
102 
103 	while (!osys->exiting) {
104 		do {
105 			osys->delayMillis(10);
106 		} while (osys->sleeping && !osys->exiting);
107 
108 		hidScanInput();
109 		u32 held = hidKeysHeld();
110 		u32 keysPressed = hidKeysDown();
111 		u32 keysReleased = hidKeysUp();
112 
113 		// Touch screen events
114 		if (held & KEY_TOUCH) {
115 			touchPosition touch;
116 			hidTouchRead(&touch);
117 			if (config.snapToBorder) {
118 				if (touch.px < borderSnapZone) {
119 					touch.px = 0;
120 				}
121 				if (touch.px > 319 - borderSnapZone) {
122 					touch.px = 319;
123 				}
124 				if (touch.py < borderSnapZone) {
125 					touch.py = 0;
126 				}
127 				if (touch.py > 239 - borderSnapZone) {
128 					touch.py = 239;
129 				}
130 			}
131 
132 			osys->transformPoint(touch);
133 
134 			event.mouse.x = touch.px;
135 			event.mouse.y = touch.py;
136 
137 			if (keysPressed & KEY_TOUCH) {
138 				touchStartTime = osys->getMillis();
139 				if (inputMode == MODE_DRAG) {
140 					event.type = Common::EVENT_LBUTTONDOWN;
141 					pushEventQueue(eventQueue, event);
142 				}
143 			} else if (touch.px != lastTouch.px || touch.py != lastTouch.py) {
144 				event.type = Common::EVENT_MOUSEMOVE;
145 				pushEventQueue(eventQueue, event);
146 			}
147 
148 			lastTouch = touch;
149 		} else if (keysReleased & KEY_TOUCH) {
150 			event.mouse.x = lastTouch.px;
151 			event.mouse.y = lastTouch.py;
152 			if (inputMode == MODE_DRAG) {
153 				event.type = Common::EVENT_LBUTTONUP;
154 				pushEventQueue(eventQueue, event);
155 			} else if (osys->getMillis() - touchStartTime < 200) {
156 				// Process click in MODE_HOVER
157 				event.type = Common::EVENT_MOUSEMOVE;
158 				pushEventQueue(eventQueue, event);
159 				event.type = Common::EVENT_LBUTTONDOWN;
160 				pushEventQueue(eventQueue, event);
161 				event.type = Common::EVENT_LBUTTONUP;
162 				pushEventQueue(eventQueue, event);
163 			}
164 		}
165 
166 		// C-Pad events
167 		circlePosition circle;
168 		hidCircleRead(&circle);
169 
170 		if (circle.dx != lastCircle.dx) {
171 			int32 position = (int32)circle.dx * Common::JOYAXIS_MAX / CIRCLE_MAX;
172 
173 			event.type              = Common::EVENT_JOYAXIS_MOTION;
174 			event.joystick.axis     = Common::JOYSTICK_AXIS_LEFT_STICK_X;
175 			event.joystick.position = CLIP<int32>(position, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
176 			pushEventQueue(eventQueue, event);
177 		}
178 
179 		if (circle.dy != lastCircle.dy) {
180 			int32 position = -(int32)circle.dy * Common::JOYAXIS_MAX / CIRCLE_MAX;
181 
182 			event.type              = Common::EVENT_JOYAXIS_MOTION;
183 			event.joystick.axis     = Common::JOYSTICK_AXIS_LEFT_STICK_Y;
184 			event.joystick.position = CLIP<int32>(position, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
185 			pushEventQueue(eventQueue, event);
186 		}
187 
188 		lastCircle = circle;
189 
190 		// Button events
191 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_L,      Common::JOYSTICK_BUTTON_LEFT_SHOULDER);
192 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_R,      Common::JOYSTICK_BUTTON_RIGHT_SHOULDER);
193 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_A,      Common::JOYSTICK_BUTTON_A);
194 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_B,      Common::JOYSTICK_BUTTON_B);
195 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_X,      Common::JOYSTICK_BUTTON_X);
196 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_Y,      Common::JOYSTICK_BUTTON_Y);
197 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DUP,    Common::JOYSTICK_BUTTON_DPAD_UP);
198 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DDOWN,  Common::JOYSTICK_BUTTON_DPAD_DOWN);
199 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DLEFT,  Common::JOYSTICK_BUTTON_DPAD_LEFT);
200 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DRIGHT, Common::JOYSTICK_BUTTON_DPAD_RIGHT);
201 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_START,  Common::JOYSTICK_BUTTON_START);
202 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_SELECT, Common::JOYSTICK_BUTTON_BACK);
203 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_ZL,     Common::JOYSTICK_BUTTON_LEFT_STICK);
204 		doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_ZR,     Common::JOYSTICK_BUTTON_RIGHT_STICK);
205 	}
206 }
207 
aptHookFunc(APT_HookType hookType,void * param)208 static void aptHookFunc(APT_HookType hookType, void *param) {
209 	OSystem_3DS *osys = dynamic_cast<OSystem_3DS *>(g_system);
210 
211 	switch (hookType) {
212 		case APTHOOK_ONSUSPEND:
213 		case APTHOOK_ONSLEEP:
214 			if (g_engine) {
215 				osys->_sleepPauseToken = g_engine->pauseEngine();
216 			}
217 			osys->sleeping = true;
218 			if (R_SUCCEEDED(gspLcdInit())) {
219 				GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH);
220 				gspLcdExit();
221 			}
222 			break;
223 		case APTHOOK_ONRESTORE:
224 		case APTHOOK_ONWAKEUP:
225 			if (g_engine) {
226 				osys->_sleepPauseToken.clear();
227 			}
228 			osys->sleeping = false;
229 			loadConfig();
230 			break;
231 		case APTHOOK_ONEXIT:
232 			break;
233 		default:
234 			warning("Unhandled APT hook, type: %d", hookType);
235 	}
236 }
237 
timerThreadFunc(void * arg)238 static void timerThreadFunc(void *arg) {
239 	OSystem_3DS *osys = (OSystem_3DS *)arg;
240 	DefaultTimerManager *tm = (DefaultTimerManager *)osys->getTimerManager();
241 	while (!osys->exiting) {
242 		g_system->delayMillis(10);
243 		tm->handler();
244 	}
245 }
246 
getHardwareInputSet()247 Common::HardwareInputSet *OSystem_3DS::getHardwareInputSet() {
248 	using namespace Common;
249 
250 	CompositeHardwareInputSet *inputSet = new CompositeHardwareInputSet();
251 	// Touch input sends mouse events for now, so we need to declare we have a mouse...
252 	inputSet->addHardwareInputSet(new MouseHardwareInputSet(ctrMouseButtons));
253 	inputSet->addHardwareInputSet(new JoystickHardwareInputSet(ctrJoystickButtons, ctrJoystickAxes));
254 
255 	return inputSet;
256 }
257 
initEvents()258 void OSystem_3DS::initEvents() {
259 	eventMutex = new Common::Mutex();
260 	s32 prio = 0;
261 	svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
262 	_timerThread = threadCreate(&timerThreadFunc, this, 32 * 1024, prio - 1, -2, false);
263 	_eventThread = threadCreate(&eventThreadFunc, &_eventQueue, 32 * 1024, prio - 1, -2, false);
264 
265 	aptHook(&cookie, aptHookFunc, this);
266 	_eventManager->getEventDispatcher()->registerObserver(this, 10, false);
267 }
268 
destroyEvents()269 void OSystem_3DS::destroyEvents() {
270 	_eventManager->getEventDispatcher()->unregisterObserver(this);
271 
272 	threadJoin(_timerThread, U64_MAX);
273 	threadFree(_timerThread);
274 
275 	threadJoin(_eventThread, U64_MAX);
276 	threadFree(_eventThread);
277 	delete eventMutex;
278 }
279 
transformPoint(touchPosition & point)280 void OSystem_3DS::transformPoint(touchPosition &point) {
281 	if (!_overlayVisible) {
282 		point.px = static_cast<float>(point.px) / _gameBottomTexture.getScaleX() - _gameBottomTexture.getPosX();
283 		point.py = static_cast<float>(point.py) / _gameBottomTexture.getScaleY() - _gameBottomTexture.getPosY();
284 	}
285 
286 	clipPoint(point);
287 }
288 
clipPoint(touchPosition & point)289 void OSystem_3DS::clipPoint(touchPosition &point) {
290 	if (_overlayVisible) {
291 		point.px = CLIP<uint16>(point.px, 0, getOverlayWidth()  - 1);
292 		point.py = CLIP<uint16>(point.py, 0, getOverlayHeight() - 1);
293 	} else {
294 		point.px = CLIP<uint16>(point.px, 0, _gameTopTexture.actualWidth  - 1);
295 		point.py = CLIP<uint16>(point.py, 0, _gameTopTexture.actualHeight - 1);
296 	}
297 }
298 
299 enum _3DSCustomEvent {
300 	k3DSEventToggleDragMode,
301 	k3DSEventToggleMagnifyMode,
302 	k3DSEventOpenSettings
303 };
304 
getGlobalKeymaps()305 Common::KeymapArray OSystem_3DS::getGlobalKeymaps() {
306 	using namespace Common;
307 
308 	Keymap *keymap = new Keymap(Keymap::kKeymapTypeGlobal, "3ds", "3DS");
309 
310 	Action *act;
311 
312 	act = new Action("DRAGM", _("Toggle Drag Mode"));
313 	act->setCustomBackendActionEvent(k3DSEventToggleDragMode);
314 	act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
315 	keymap->addAction(act);
316 
317 	act = new Action("MAGM", _("Toggle Magnify Mode"));
318 	act->setCustomBackendActionEvent(k3DSEventToggleMagnifyMode);
319 	act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
320 	keymap->addAction(act);
321 
322 	act = new Action("OPTS", _("Open 3DS Settings"));
323 	act->setCustomBackendActionEvent(k3DSEventOpenSettings);
324 	act->addDefaultInputMapping("JOY_BACK");
325 	keymap->addAction(act);
326 
327 	return Keymap::arrayOf(keymap);
328 }
329 
getKeymapperDefaultBindings()330 Common::KeymapperDefaultBindings *OSystem_3DS::getKeymapperDefaultBindings() {
331 	Common::KeymapperDefaultBindings *keymapperDefaultBindings = new Common::KeymapperDefaultBindings();
332 
333 	// Bind the virtual keyboard to X so SELECT can be used for the 3DS options dialog
334 	keymapperDefaultBindings->setDefaultBinding(Common::kGlobalKeymapName, "VIRT", "JOY_X");
335 
336 	// Unmap the main menu standard action so LEFT_SHOULDER can be used for drag mode
337 	keymapperDefaultBindings->setDefaultBinding("engine-default", Common::kStandardActionOpenMainMenu, "");
338 
339 	return keymapperDefaultBindings;
340 }
341 
pollEvent(Common::Event & event)342 bool OSystem_3DS::pollEvent(Common::Event &event) {
343 	if (!aptMainLoop()) {
344 		// The system requested us to quit
345 		if (_sleepPauseToken.isActive()) {
346 			_sleepPauseToken.clear();
347 		}
348 
349 		event.type = Common::EVENT_QUIT;
350 		return true;
351 	}
352 
353 	// If magnify mode is on when returning to Launcher, turn it off
354 	if (_eventManager->shouldReturnToLauncher()) {
355 		if (_magnifyMode == MODE_MAGON) {
356 			_magnifyMode = MODE_MAGOFF;
357 			updateSize();
358 			if (savedInputMode == MODE_DRAG) {
359 				inputMode = savedInputMode;
360 				displayMessageOnOSD(_("Magnify Mode Off. Reactivating Drag Mode.\nReturning to Launcher..."));
361 			} else {
362 				displayMessageOnOSD(_("Magnify Mode Off. Returning to Launcher..."));
363 			}
364 		}
365 	}
366 
367 	Common::StackLock lock(*eventMutex);
368 
369 	if (_eventQueue.empty()) {
370 		return false;
371 	}
372 
373 	event = _eventQueue.pop();
374 
375 	if (Common::isMouseEvent(event)) {
376 		warpMouse(event.mouse.x, event.mouse.y);
377 	}
378 
379 	return true;
380 }
381 
notifyEvent(const Common::Event & event)382 bool OSystem_3DS::notifyEvent(const Common::Event &event) {
383 	if (event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_START
384 	        && event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_END) {
385 		return false; // We're only interested in custom backend events
386 	}
387 
388 	if (event.type == Common::EVENT_CUSTOM_BACKEND_ACTION_END) {
389 		return true; // We'll say we have handled the event so it is not propagated
390 	}
391 
392 	switch ((_3DSCustomEvent)event.customType) {
393 	case k3DSEventToggleDragMode:
394 		if (inputMode == MODE_DRAG) {
395 			inputMode = savedInputMode = MODE_HOVER;
396 			displayMessageOnOSD(_("Hover Mode"));
397 		} else {
398 			if (_magnifyMode == MODE_MAGOFF) {
399 				inputMode = savedInputMode = MODE_DRAG;
400 				displayMessageOnOSD(_("Drag Mode"));
401 			} else {
402 				displayMessageOnOSD(_("Cannot Switch to Drag Mode while Magnify Mode is On"));
403 			}
404 		}
405 		return true;
406 
407 	case k3DSEventToggleMagnifyMode:
408 		if (_overlayVisible) {
409 			displayMessageOnOSD(_("Magnify Mode cannot be activated in menus."));
410 		} else if (config.screen != kScreenBoth && _magnifyMode == MODE_MAGOFF) {
411 			// TODO: Automatically enable both screens while magnify mode is on
412 			displayMessageOnOSD(_("Magnify Mode can only be activated\n when both screens are enabled."));
413 		} else if (_gameWidth <= 400 && _gameHeight <= 240) {
414 			displayMessageOnOSD(_("In-game resolution too small to magnify."));
415 		} else {
416 			if (_magnifyMode == MODE_MAGOFF) {
417 				_magnifyMode = MODE_MAGON;
418 				if (inputMode == MODE_DRAG) {
419 					inputMode = MODE_HOVER;
420 					displayMessageOnOSD(_("Magnify Mode On. Switching to Hover Mode..."));
421 				} else {
422 					displayMessageOnOSD(_("Magnify Mode On"));
423 				}
424 			} else {
425 				_magnifyMode = MODE_MAGOFF;
426 				updateSize();
427 				if (savedInputMode == MODE_DRAG) {
428 					inputMode = savedInputMode;
429 					displayMessageOnOSD(_("Magnify Mode Off. Reactivating Drag Mode..."));
430 				} else {
431 					displayMessageOnOSD(_("Magnify Mode Off"));
432 				}
433 			}
434 		}
435 		return true;
436 
437 	case k3DSEventOpenSettings:
438 		runOptionsDialog();
439 		return true;
440 	}
441 
442 	return false;
443 }
444 
runOptionsDialog()445 void OSystem_3DS::runOptionsDialog() {
446 	static bool optionsDialogRunning = false;
447 
448 	// Prevent opening the options dialog multiple times
449 	if (optionsDialogRunning) {
450 		return;
451 	}
452 
453 	optionsDialogRunning = true;
454 
455 	PauseToken pauseToken;
456 	OptionsDialog dialog;
457 	if (g_engine) {
458 		pauseToken = g_engine->pauseEngine();
459 	}
460 	int result = dialog.runModal();
461 	if (g_engine) {
462 		pauseToken.clear();
463 	}
464 
465 	if (result > 0) {
466 		int oldScreen = config.screen;
467 
468 		config.showCursor   = dialog.getShowCursor();
469 		config.snapToBorder = dialog.getSnapToBorder();
470 		config.stretchToFit = dialog.getStretchToFit();
471 		config.screen       = dialog.getScreen();
472 
473 		saveConfig();
474 		loadConfig();
475 
476 		if (config.screen != oldScreen) {
477 			_screenChangeId++;
478 			g_gui.checkScreenChange();
479 		}
480 	}
481 
482 	optionsDialogRunning = false;
483 }
484 
485 } // namespace N3DS
486