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