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