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