allow player 2 touchscreen controls
[drnoksnes] / platform / osso.cpp
1 #include <stdio.h>
2
3 #include "snes9x.h"
4
5 #include <glib.h>
6 #include <libosso.h>
7 #include <gconf/gconf.h>
8 #include <gconf/gconf-client.h>
9
10 #include "platform.h"
11 #include "osso.h"
12 #include "../gui/gconf.h"
13
14 static GMainContext *mainContext;
15 static GMainLoop *mainLoop;
16 osso_context_t *ossoContext;
17
18 // Older versions of glib don't have this.
19 #ifndef g_warn_if_fail
20 #define g_warn_if_fail(expr) \
21         if G_UNLIKELY(expr) { \
22                 g_warning("Non critical assertion failed at %s:%d \"%s\"", \
23                         __FILE__, __LINE__, #expr); \
24         }
25 #endif
26 #if ! GLIB_CHECK_VERSION(2,14,0)
27 #define g_timeout_add_seconds(interval, function, data) \
28         g_timeout_add((interval) * 1000, function, data)
29 #endif
30
31 static volatile enum {
32         STARTUP_COMMAND_INVALID = -1,
33         STARTUP_COMMAND_UNKNOWN = 0,
34         STARTUP_COMMAND_RUN,
35         STARTUP_COMMAND_CONTINUE,
36         STARTUP_COMMAND_RESTART,
37         STARTUP_COMMAND_QUIT
38 } startupCommand;
39
40 static void loadSafeKeymap();
41 static void loadPlayer1Keymap(GConfClient* gcc);
42 static void loadPlayer2Keymap(GConfClient* gcc);
43
44 static gint ossoAppCallback(const gchar *interface, const gchar *method,
45   GArray *arguments, gpointer data, osso_rpc_t *retval)
46 {
47         retval->type = DBUS_TYPE_BOOLEAN;
48
49         if (startupCommand == STARTUP_COMMAND_UNKNOWN) {
50                 // Only if we haven't received the startup command yet.
51                 printf("Osso: Startup method is: %s\n", method);
52
53                 if (strcmp(method, "game_run") == 0) {
54                         startupCommand = STARTUP_COMMAND_RUN;
55                         retval->value.b = TRUE;
56                 } else if (strcmp(method, "game_continue") == 0) {
57                         startupCommand = STARTUP_COMMAND_CONTINUE;
58                         retval->value.b = TRUE;
59                 } else if (strcmp(method, "game_restart") == 0) {
60                         startupCommand = STARTUP_COMMAND_RESTART;
61                         retval->value.b = TRUE;
62                 } else if (strcmp(method, "game_close") == 0) {
63                         // A bit weird, but could happen
64                         startupCommand = STARTUP_COMMAND_QUIT;
65                         retval->value.b = TRUE;
66                 } else {
67                         startupCommand = STARTUP_COMMAND_INVALID;
68                         retval->value.b = FALSE;
69                 }
70         } else {
71                 if (strcmp(method, "game_close") == 0) {
72                         printf("Osso: quitting because of D-Bus close message\n");
73                         S9xDoAction(kActionQuit);
74                         retval->value.b = TRUE;
75                 } else {
76                         retval->value.b = FALSE;
77                 }
78         }
79
80         return OSSO_OK;
81 }
82
83 static gboolean ossoTimeoutCallback(gpointer data)
84 {
85         if (startupCommand == STARTUP_COMMAND_UNKNOWN) {
86                 // Assume that after N seconds we're not going to get a startup reason.
87                 startupCommand = STARTUP_COMMAND_INVALID;
88         }
89
90         return FALSE; // This is a timeout, don't call us ever again.
91 }
92
93 static void ossoHwCallback(osso_hw_state_t *state, gpointer data)
94 {
95         if (state->shutdown_ind) {
96                 // Shutting down. Try to quit gracefully.
97                 S9xDoAction(kActionQuit);
98         }
99         if (Config.saver && state->system_inactivity_ind) {
100                 // Screen went off, and power saving is active.
101                 S9xDoAction(kActionQuit);
102         }
103 }
104
105 /** Called from main(), initializes Glib & libosso stuff if needed. */
106 void OssoInit()
107 {
108         char *dbusLaunch = getenv("DRNOKSNES_DBUS");
109
110         if (!dbusLaunch || dbusLaunch[0] != 'y') {
111                 // Not launched from GUI, so we don't assume GUI features.
112                 ossoContext = 0;
113                 return;
114         }
115
116         g_type_init();
117         g_set_prgname("drnoksnes");
118         g_set_application_name("DrNokSnes");
119         mainContext = g_main_context_default();
120         mainLoop = g_main_loop_new(mainContext, FALSE);
121         ossoContext = osso_initialize("com.javispedro.drnoksnes", "1", 0, 0);
122
123         if (!ossoContext) {
124                 fprintf(stderr, "Error initializing libosso\n");
125                 exit(2);
126         }
127
128         // At this point, we still don't know what the startup command is
129         startupCommand = STARTUP_COMMAND_UNKNOWN;
130
131         osso_return_t ret;
132         ret = osso_rpc_set_default_cb_f(ossoContext, ossoAppCallback, 0);
133         g_warn_if_fail(ret == OSSO_OK);
134
135         osso_hw_state_t hwStateFlags = { FALSE };
136         hwStateFlags.shutdown_ind = TRUE;
137         hwStateFlags.system_inactivity_ind = TRUE;
138         ret = osso_hw_set_event_cb(ossoContext, &hwStateFlags, ossoHwCallback, 0);
139         g_warn_if_fail(ret == OSSO_OK);
140
141         printf("Osso: Initialized libosso\n");
142 }
143
144 static osso_return_t invokeLauncherMethod(const char *method, osso_rpc_t *retval)
145 {
146         // The launcher seems to assume there is at least one parameter,
147         // even if the method doesn't actually require one.
148         return osso_rpc_run(ossoContext, "com.javispedro.drnoksnes.startup",
149                 "/com/javispedro/drnoksnes/startup", "com.javispedro.drnoksnes.startup",
150                 method, retval, DBUS_TYPE_INVALID);
151 }
152
153 void OssoDeinit()
154 {
155         if (!OssoOk()) return;
156
157         // Send a goodbye message to the launcher
158         osso_return_t ret;
159         osso_rpc_t retval = { 0 };
160         if (Config.snapshotSave) {
161                 // If we saved game state, notify launcher to enter "paused" status.
162                 ret = invokeLauncherMethod("game_pause", &retval);
163         } else {
164                 ret = invokeLauncherMethod("game_close", &retval);
165         }
166         if (ret != OSSO_OK) {
167                 printf("Osso: failed to notify launcher\n");
168         }
169         osso_rpc_free_val(&retval);
170
171         osso_deinitialize(ossoContext);
172         g_main_loop_unref(mainLoop);
173         g_main_context_unref(mainContext);
174
175         ossoContext = 0;
176 }
177
178 /** Called after loading the config file, loads settings from gconf. */
179 void OssoConfig()
180 {
181         if (!OssoOk()) return;
182
183         GConfClient *gcc = gconf_client_get_default();
184
185         // GUI only allows fullscreen
186         Config.fullscreen = true;
187
188         // Get ROM filename from Gconf
189         gchar *romFile = gconf_client_get_string(gcc, kGConfRomFile, 0);
190         if (romFile && strlen(romFile) > 0) {
191                 S9xSetRomFile(romFile);
192         } else {
193                 printf("Exiting gracefully because there's no ROM in Gconf\n");
194                 OssoDeinit();
195                 exit(0);
196         }
197
198         // Read most of the non-player specific settings
199         Config.saver = gconf_client_get_bool(gcc, kGConfSaver, 0);
200         Config.enableAudio = gconf_client_get_bool(gcc, kGConfSound, 0);
201         Settings.TurboMode = gconf_client_get_bool(gcc, kGConfTurboMode, 0);
202         Settings.Transparency = gconf_client_get_bool(gcc, kGConfTransparency, 0);
203         Settings.DisplayFrameRate = gconf_client_get_bool(gcc, kGConfDisplayFramerate, 0);
204
205         int frameskip = gconf_client_get_int(gcc, kGConfFrameskip, 0);
206         Settings.SkipFrames = (frameskip > 0 ? frameskip : AUTO_FRAMERATE);
207
208         gchar *scaler = gconf_client_get_string(gcc, kGConfScaler, 0);
209         if (scaler && strlen(scaler) > 0) {
210                 free(Config.scaler);
211                 Config.scaler = strdup(scaler);
212         }
213         g_free(scaler);
214
215         int speedhacks = gconf_client_get_int(gcc, kGConfSpeedhacks, 0);
216         if (speedhacks <= 0) {
217                 Settings.HacksEnabled = FALSE;
218                 Settings.HacksFilter = FALSE;
219         } else if (speedhacks == 1) {
220                 Settings.HacksEnabled = TRUE;
221                 Settings.HacksFilter = TRUE;
222         } else {
223                 Settings.HacksEnabled = TRUE;
224                 Settings.HacksFilter = FALSE;
225         }
226
227         if (Settings.HacksEnabled && !Config.hacksFile) {
228                 // Provide a default speedhacks file
229                 gchar *romDir = g_path_get_dirname(romFile);
230                 if (asprintf(&Config.hacksFile, "%s/snesadvance.dat", romDir)
231                                 < 0) {
232                         Config.hacksFile = 0; // malloc error.
233                 }
234                 g_free(romDir);
235         }
236
237         g_free(romFile);
238
239         // Read player 1 controls
240         gchar key[kGConfPlayerPathBufferLen];
241         gchar *relKey = key + sprintf(key, kGConfPlayerPath, 1);
242
243         //  keyboard
244         strcpy(relKey, kGConfPlayerKeyboardEnable);
245         if (gconf_client_get_bool(gcc, key, NULL)) {
246                 Config.joypad1Enabled = true;
247                 loadPlayer1Keymap(gcc);
248         } else {
249                 // We allow controls to be enabled from the command line
250                 loadSafeKeymap();
251         }
252
253         //  touchscreen
254         strcpy(relKey, kGConfPlayerTouchscreenEnable);
255         if (gconf_client_get_bool(gcc, key, NULL)) {
256                 Config.touchscreenInput = 1;
257
258                 strcpy(relKey, kGConfPlayerTouchscreenShow);
259                 if (gconf_client_get_bool(gcc, key, NULL)) {
260                         Config.touchscreenShow = true;
261                 }
262         }
263
264         // Read player 2 controls
265         relKey = key + sprintf(key, kGConfPlayerPath, 2);
266
267         //  keyboard
268         strcpy(relKey, kGConfPlayerKeyboardEnable);
269         if (gconf_client_get_bool(gcc, key, NULL)) {
270                 Config.joypad2Enabled = true;
271                 loadPlayer2Keymap(gcc);
272         }
273
274         //  touchscreen
275         strcpy(relKey, kGConfPlayerTouchscreenEnable);
276         if (gconf_client_get_bool(gcc, key, NULL)) {
277                 Config.touchscreenInput = 2;
278
279                 strcpy(relKey, kGConfPlayerTouchscreenShow);
280                 if (gconf_client_get_bool(gcc, key, NULL)) {
281                         Config.touchscreenShow = true;
282                 }
283         }
284
285         // Time to read the startup command from D-Bus
286
287         // Timeout after 3 seconds, and assume we didn't receive any.
288         guint timeout = g_timeout_add_seconds(3, ossoTimeoutCallback, 0);
289         g_warn_if_fail(timeout > 0);
290
291         // Iterate the event loop since we want to catch the initial dbus messages
292         while (startupCommand == STARTUP_COMMAND_UNKNOWN) {
293                 // This is not busylooping since we are blocking here
294                 g_main_context_iteration(mainContext, TRUE);
295         }
296
297         // The command we received from the launcher will tell us if we have to
298         // load a snapshot file.
299         switch (startupCommand) {
300                 case STARTUP_COMMAND_RUN:
301                 case STARTUP_COMMAND_RESTART:
302                         Config.snapshotLoad = false;
303                         Config.snapshotSave = true;
304                         break;
305                 case STARTUP_COMMAND_CONTINUE:
306                         Config.snapshotLoad = true;
307                         Config.snapshotSave = true;
308                         break;
309                 case STARTUP_COMMAND_QUIT:
310                         Config.snapshotLoad = false;
311                         Config.snapshotSave = false;
312                         Config.quitting = true;
313                         break;
314                 default:
315                         Config.snapshotLoad = false;
316                         Config.snapshotSave = false;
317                         break;
318         }
319
320         g_object_unref(G_OBJECT(gcc));
321 }
322
323 /** This is called periodically from the main loop.
324         Iterates the GLib loop to get D-Bus events.
325  */
326 void OssoPollEvents()
327 {
328         if (!OssoOk()) return;
329
330         g_main_context_iteration(mainContext, FALSE);
331 }
332
333 typedef struct ButtonEntry {
334         const char * gconf_key;
335         unsigned long mask;
336         bool is_action;
337 } ButtonEntry;
338
339 /** This arrays contains generic info about each of the mappable buttons the
340         GUI shows */
341 static const ButtonEntry buttons[] = {
342 #define HELP(...)
343 #define P(x) SNES_##x##_MASK
344 #define A(x) kAction##x
345 #define BUTTON(description, slug, actions, d, f) \
346         { G_STRINGIFY(slug), actions, false },
347 #define ACTION(description, slug, actions, d, f) \
348         { G_STRINGIFY(slug), actions, true },
349 #define LAST \
350         { 0 }
351 #include "../gui/buttons.inc"
352 #undef HELP
353 #undef P
354 #undef A
355 #undef BUTTON
356 #undef ACTION
357 #undef LAST
358 };
359
360 /** This loads a keymap for player 1 that will allow him to exit the app. */
361 static void loadSafeKeymap()
362 {
363         // Map quit to fullscreen, escape and task switch.
364         Config.action[72] = kActionQuit;
365         Config.action[9] = kActionQuit;
366         Config.action[71] = kActionQuit;
367 }
368
369 static void loadPlayer1Keymap(GConfClient* gcc)
370 {
371         // Build player 1 keyboard gconf key relative path
372         gchar key[kGConfPlayerPathBufferLen];
373         gchar *relKey = key + sprintf(key,
374                 kGConfPlayerPath kGConfPlayerKeyboardPath "/", 1);
375
376         // If the user does not map fullscreen or quit
377         bool quit_mapped = false;
378
379         printf("Hgw: Using gconf key mappings\n");
380         // Thus ignoring config file key mappings
381         ZeroMemory(Config.joypad1Mapping, sizeof(Config.joypad1Mapping));
382         ZeroMemory(Config.action, sizeof(Config.action));
383
384         int i, scancode;
385         for (i = 0; buttons[i].gconf_key; i++) {
386                 strcpy(relKey, buttons[i].gconf_key);
387                 scancode = gconf_client_get_int(gcc, key, NULL);
388
389                 if (scancode <= 0 || scancode > 255) continue;
390
391                 if (buttons[i].is_action) {
392                         Config.action[scancode] |= buttons[i].mask;
393                         if (buttons[i].mask & (kActionQuit | kActionToggleFullscreen)) {
394                                 quit_mapped = true;
395                         }
396                 } else {
397                         Config.joypad1Mapping[scancode] |= buttons[i].mask;
398                 }
399         }
400
401 #if MAEMO && !CONF_EXIT_BUTTON
402         // Safeguards
403         if (!quit_mapped) {
404                 // Newbie user won't know how to quit game.
405                 if (!Config.joypad1Mapping[72] && !Config.action[72]) {
406                         // Fullscreen key is not mapped, map
407                         Config.action[72] = kActionQuit;
408                         quit_mapped = true;
409                 }
410                 if (!quit_mapped && !Config.joypad1Mapping[9] && !Config.action[9]) {
411                         // Escape key is not mapped, map
412                         // But only if we couldn't map quit to fullscreen. Some people
413                         // actually want Quit not to be mapped.
414                         Config.action[9] = kActionQuit;
415                         quit_mapped = true;
416                 }
417                 if (!quit_mapped) {
418                         // Force mapping of fullscreen to Quit if can't map anywhere else.
419                         Config.joypad1Mapping[72] = 0;
420                         Config.action[72] = kActionQuit;
421                 }
422         }
423
424         // If task switch key is not mapped, map it to Quit by default.
425         if (!Config.action[71] && !Config.joypad1Mapping[71]) {
426                 Config.action[71] = kActionQuit;
427         }
428 #endif
429 }
430
431 // This version is simpler since we don't need safeguards.
432 static void loadPlayer2Keymap(GConfClient* gcc)
433 {
434         // Build player 2 keyboard gconf key relative path
435         gchar key[kGConfPlayerPathBufferLen];
436         gchar *relKey = key + sprintf(key,
437                 kGConfPlayerPath kGConfPlayerKeyboardPath "/", 2);
438
439         // Ignore config file key mappings
440         ZeroMemory(Config.joypad2Mapping, sizeof(Config.joypad2Mapping));
441
442         int i, scancode;
443         for (i = 0; buttons[i].gconf_key; i++) {
444                 if (buttons[i].is_action) continue;
445
446                 strcpy(relKey, buttons[i].gconf_key);
447                 scancode = gconf_client_get_int(gcc, key, NULL);
448
449                 // Ignore out of range values
450                 if (scancode <= 0 || scancode > 255) continue;
451
452                 Config.joypad2Mapping[scancode] |= buttons[i].mask;
453         }
454 }
455