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