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