Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
main.c
Go to the documentation of this file.
1 /* Audacious - Cross-platform multimedia player
2  * Copyright (C) 2005-2011 Audacious development team.
3  *
4  * Based on BMP:
5  * Copyright (C) 2003-2004 BMP development team.
6  *
7  * Based on XMMS:
8  * Copyright (C) 1998-2003 XMMS development team.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; under version 3 of the License.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <http://www.gnu.org/licenses>.
21  *
22  * The Audacious team does not consider modular code linking to
23  * Audacious or using our public API to be a derived work.
24  */
25 
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 
33 #include <gtk/gtk.h>
34 
35 #include <libaudcore/audstrings.h>
36 #include <libaudcore/hook.h>
37 #include <libaudtag/audtag.h>
38 
39 #include "config.h"
40 
41 #ifdef USE_DBUS
42 #include "../libaudclient/audctrl.h"
43 #include "dbus.h"
44 #endif
45 
46 #ifdef USE_EGGSM
47 #include "eggdesktopfile.h"
48 #include "eggsmclient.h"
49 #endif
50 
51 #include "debug.h"
52 #include "drct.h"
53 #include "equalizer.h"
54 #include "i18n.h"
55 #include "interface.h"
56 #include "main.h"
57 #include "misc.h"
58 #include "playback.h"
59 #include "playlist.h"
60 #include "plugins.h"
61 #include "util.h"
62 
63 #define AUTOSAVE_INTERVAL 300 /* seconds */
64 
66 
67 static struct {
68  char **filenames;
69  int session;
76 } options;
77 
78 static char * aud_paths[AUD_PATH_COUNT];
79 
80 static void make_dirs(void)
81 {
82 #ifdef S_IRGRP
83  const mode_t mode755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
84 #else
85  const mode_t mode755 = S_IRWXU;
86 #endif
87 
90 }
91 
92 static void normalize_path (char * path)
93 {
94 #ifdef _WIN32
95  string_replace_char (path, '/', '\\');
96 #endif
97  int len = strlen (path);
98 #ifdef _WIN32
99  if (len > 3 && path[len - 1] == '\\') /* leave "C:\" */
100 #else
101  if (len > 1 && path[len - 1] == '/') /* leave leading "/" */
102 #endif
103  path[len - 1] = 0;
104 }
105 
106 static char * last_path_element (char * path)
107 {
108  char * slash = strrchr (path, G_DIR_SEPARATOR);
109  return (slash && slash[1]) ? slash + 1 : NULL;
110 }
111 
112 static void strip_path_element (char * path, char * elem)
113 {
114 #ifdef _WIN32
115  if (elem > path + 3)
116 #else
117  if (elem > path + 1)
118 #endif
119  elem[-1] = 0; /* overwrite slash */
120  else
121  elem[0] = 0; /* leave [drive letter and] leading slash */
122 }
123 
124 static void relocate_path (char * * pathp, const char * old, const char * new)
125 {
126  char * path = * pathp;
127  int oldlen = strlen (old);
128  int newlen = strlen (new);
129 
130  if (oldlen && old[oldlen - 1] == G_DIR_SEPARATOR)
131  oldlen --;
132  if (newlen && new[newlen - 1] == G_DIR_SEPARATOR)
133  newlen --;
134 
135 #ifdef _WIN32
136  if (strncasecmp (path, old, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR))
137 #else
138  if (strncmp (path, old, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR))
139 #endif
140  {
141  fprintf (stderr, "Failed to relocate a data path. Falling back to "
142  "compile-time path: %s\n", path);
143  return;
144  }
145 
146  * pathp = g_strdup_printf ("%.*s%s", newlen, new, path + oldlen);
147  g_free (path);
148 }
149 
150 static void relocate_paths (void)
151 {
152  /* Start with the paths hard coded at compile time. */
153  aud_paths[AUD_PATH_BIN_DIR] = g_strdup (HARDCODE_BINDIR);
154  aud_paths[AUD_PATH_DATA_DIR] = g_strdup (HARDCODE_DATADIR);
155  aud_paths[AUD_PATH_PLUGIN_DIR] = g_strdup (HARDCODE_PLUGINDIR);
156  aud_paths[AUD_PATH_LOCALE_DIR] = g_strdup (HARDCODE_LOCALEDIR);
157  aud_paths[AUD_PATH_DESKTOP_FILE] = g_strdup (HARDCODE_DESKTOPFILE);
158  aud_paths[AUD_PATH_ICON_FILE] = g_strdup (HARDCODE_ICONFILE);
165 
166  /* Compare the compile-time path to the executable and the actual path to
167  * see if we have been moved. */
168  char * old = g_strdup (aud_paths[AUD_PATH_BIN_DIR]);
169  char * new = get_path_to_self ();
170  if (! new)
171  {
172 ERR:
173  g_free (old);
174  g_free (new);
175  return;
176  }
177  normalize_path (new);
178 
179  /* Strip the name of the executable file, leaving the path. */
180  char * base = last_path_element (new);
181  if (! base)
182  goto ERR;
183  strip_path_element (new, base);
184 
185  /* Strip innermost folder names from both paths as long as they match. This
186  * leaves a compile-time prefix and a run-time one to replace it with. */
187  char * a, * b;
188  while ((a = last_path_element (old)) && (b = last_path_element (new)) &&
189 #ifdef _WIN32
190  ! strcasecmp (a, b))
191 #else
192  ! strcmp (a, b))
193 #endif
194  {
195  strip_path_element (old, a);
196  strip_path_element (new, b);
197  }
198 
199  /* Do the replacements. */
200  relocate_path (& aud_paths[AUD_PATH_BIN_DIR], old, new);
201  relocate_path (& aud_paths[AUD_PATH_DATA_DIR], old, new);
202  relocate_path (& aud_paths[AUD_PATH_PLUGIN_DIR], old, new);
203  relocate_path (& aud_paths[AUD_PATH_LOCALE_DIR], old, new);
204  relocate_path (& aud_paths[AUD_PATH_DESKTOP_FILE], old, new);
205  relocate_path (& aud_paths[AUD_PATH_ICON_FILE], old, new);
206 
207  g_free (old);
208  g_free (new);
209 }
210 
211 static void init_paths (void)
212 {
213  relocate_paths ();
214 
215  const char * xdg_config_home = g_get_user_config_dir ();
216  const char * xdg_data_home = g_get_user_data_dir ();
217 
218 #ifdef _WIN32
219  /* Some libraries (libmcs) and plugins (filewriter) use these variables,
220  * which are generally not set on Windows. */
221  g_setenv ("HOME", g_get_home_dir (), TRUE);
222  g_setenv ("XDG_CONFIG_HOME", xdg_config_home, TRUE);
223  g_setenv ("XDG_DATA_HOME", xdg_data_home, TRUE);
224  g_setenv ("XDG_CACHE_HOME", g_get_user_cache_dir (), TRUE);
225 #endif
226 
227  aud_paths[AUD_PATH_USER_DIR] = g_build_filename(xdg_config_home, "audacious", NULL);
228  aud_paths[AUD_PATH_USER_PLUGIN_DIR] = g_build_filename(xdg_data_home, "audacious", "Plugins", NULL);
229  aud_paths[AUD_PATH_PLAYLISTS_DIR] = g_build_filename(aud_paths[AUD_PATH_USER_DIR], "playlists", NULL);
230  aud_paths[AUD_PATH_GTKRC_FILE] = g_build_filename(aud_paths[AUD_PATH_USER_DIR], "gtkrc", NULL);
231 
232  for (int i = 0; i < AUD_PATH_COUNT; i ++)
233  AUDDBG ("Data path: %s\n", aud_paths[i]);
234 }
235 
236 const char * get_path (int id)
237 {
238  g_return_val_if_fail (id >= 0 && id < AUD_PATH_COUNT, NULL);
239  return aud_paths[id];
240 }
241 
242 static GOptionEntry cmd_entries[] = {
243  {"rew", 'r', 0, G_OPTION_ARG_NONE, &options.rew, N_("Skip backwards in playlist"), NULL},
244  {"play", 'p', 0, G_OPTION_ARG_NONE, &options.play, N_("Start playing current playlist"), NULL},
245  {"pause", 'u', 0, G_OPTION_ARG_NONE, &options.pause, N_("Pause current song"), NULL},
246  {"stop", 's', 0, G_OPTION_ARG_NONE, &options.stop, N_("Stop current song"), NULL},
247  {"play-pause", 't', 0, G_OPTION_ARG_NONE, &options.play_pause, N_("Pause if playing, play otherwise"), NULL},
248  {"fwd", 'f', 0, G_OPTION_ARG_NONE, &options.fwd, N_("Skip forward in playlist"), NULL},
249  {"show-jump-box", 'j', 0, G_OPTION_ARG_NONE, &options.show_jump_box, N_("Display Jump to File dialog"), NULL},
250  {"enqueue", 'e', 0, G_OPTION_ARG_NONE, &options.enqueue, N_("Add files to the playlist"), NULL},
251  {"enqueue-to-temp", 'E', 0, G_OPTION_ARG_NONE, &options.enqueue_to_temp, N_("Add new files to a temporary playlist"), NULL},
252  {"show-main-window", 'm', 0, G_OPTION_ARG_NONE, &options.mainwin, N_("Display the main window"), NULL},
253  {"version", 'v', 0, G_OPTION_ARG_NONE, &options.version, N_("Show version"), NULL},
254  {"verbose", 'V', 0, G_OPTION_ARG_NONE, &options.verbose, N_("Print debugging messages"), NULL},
255  {"headless", 'h', 0, G_OPTION_ARG_NONE, & headless, N_("Headless mode (beta)"), NULL},
256  {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &options.filenames, N_("FILE..."), NULL},
257  {NULL},
258 };
259 
260 static void parse_options (int * argc, char *** argv)
261 {
262  GOptionContext *context;
263  GError *error = NULL;
264 
265  memset (& options, 0, sizeof options);
266  options.session = -1;
267 
268  context = g_option_context_new(_("- play multimedia files"));
269  g_option_context_add_main_entries(context, cmd_entries, PACKAGE);
270  g_option_context_add_group(context, gtk_get_option_group(FALSE));
271 #ifdef USE_EGGSM
272  g_option_context_add_group(context, egg_sm_client_get_option_group());
273 #endif
274 
275  if (!g_option_context_parse(context, argc, argv, &error))
276  {
277  fprintf (stderr,
278  _("%s: %s\nTry `%s --help' for more information.\n"), (* argv)[0],
279  error->message, (* argv)[0]);
280  exit (EXIT_FAILURE);
281  }
282 
283  g_option_context_free (context);
284 
285  verbose = options.verbose;
286 }
287 
288 static bool_t get_lock (void)
289 {
290  char * path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "lock", aud_paths[AUD_PATH_USER_DIR]);
291  int handle = open (path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
292 
293  if (handle < 0)
294  {
295  if (errno != EEXIST)
296  fprintf (stderr, "Cannot create %s: %s.\n", path, strerror (errno));
297 
298  g_free (path);
299  return FALSE;
300  }
301 
302  close (handle);
303  g_free (path);
304  return TRUE;
305 }
306 
307 static void release_lock (void)
308 {
309  char * path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "lock", aud_paths[AUD_PATH_USER_DIR]);
310  unlink (path);
311  g_free (path);
312 }
313 
314 static Index * convert_filenames (void)
315 {
316  if (! options.filenames)
317  return NULL;
318 
319  Index * filenames = index_new ();
320  char * * f = options.filenames;
321  char * cur = g_get_current_dir ();
322 
323  for (int i = 0; f[i]; i ++)
324  {
325  char * uri = NULL;
326 
327  if (strstr (f[i], "://"))
328  uri = str_get (f[i]);
329  else if (g_path_is_absolute (f[i]))
330  {
331  char * tmp = filename_to_uri (f[i]);
332  uri = str_get (tmp);
333  free (tmp);
334  }
335  else
336  {
337  char * tmp = g_build_filename (cur, f[i], NULL);
338  char * tmp2 = filename_to_uri (tmp);
339  uri = str_get (tmp2);
340  free (tmp);
341  free (tmp2);
342  }
343 
344  if (uri)
345  index_append (filenames, uri);
346  }
347 
348  g_free (cur);
349  return filenames;
350 }
351 
352 static void do_remote (void)
353 {
354 #ifdef USE_DBUS
355  DBusGProxy * session = audacious_get_dbus_proxy ();
356 
357  if (session && audacious_remote_is_running (session))
358  {
359  Index * filenames = convert_filenames ();
360 
361  /* if no command line options, then present running instance */
362  if (! (filenames || options.play || options.pause || options.play_pause ||
363  options.stop || options.rew || options.fwd || options.show_jump_box ||
364  options.mainwin))
365  options.mainwin = TRUE;
366 
367  if (filenames)
368  {
369  GList * list = NULL;
370 
371  for (int f = index_count (filenames); f --; )
372  list = g_list_prepend (list, index_get (filenames, f));
373 
374  if (options.enqueue_to_temp)
376  else if (options.enqueue)
377  audacious_remote_playlist_add (session, list);
378  else
379  audacious_remote_playlist_open_list (session, list);
380 
381  g_list_free (list);
382 
383  for (int f = 0; f < index_count (filenames); f ++)
384  str_unref (index_get (filenames, f));
385 
386  index_free (filenames);
387  }
388 
389  if (options.play)
390  audacious_remote_play (session);
391  if (options.pause)
392  audacious_remote_pause (session);
393  if (options.play_pause)
394  audacious_remote_play_pause (session);
395  if (options.stop)
396  audacious_remote_stop (session);
397  if (options.rew)
399  if (options.fwd)
401  if (options.show_jump_box)
403  if (options.mainwin)
405 
406  exit (EXIT_SUCCESS);
407  }
408 #endif
409 
410  fprintf (stderr, "WARNING: Audacious seems to be already running but is not responding.\n");
411 }
412 
413 static void do_commands (void)
414 {
415  bool_t resume = get_bool (NULL, "resume_playback_on_startup");
416 
417  Index * filenames = convert_filenames ();
418  if (filenames)
419  {
420  if (options.enqueue_to_temp)
421  {
422  drct_pl_open_temp_list (filenames);
423  resume = FALSE;
424  }
425  else if (options.enqueue)
426  drct_pl_add_list (filenames, -1);
427  else
428  {
429  drct_pl_open_list (filenames);
430  resume = FALSE;
431  }
432  }
433 
434  if (resume)
435  playlist_resume ();
436 
437  if (options.play || options.play_pause)
438  {
439  if (! playback_get_playing ())
440  playback_play (0, FALSE);
441  else if (playback_get_paused ())
442  playback_pause ();
443  }
444 
445  if (options.show_jump_box)
447  if (options.mainwin)
449 }
450 
451 static void init_one (void)
452 {
453  init_paths ();
454  make_dirs ();
455 
456  bindtextdomain (PACKAGE, aud_paths[AUD_PATH_LOCALE_DIR]);
457  bind_textdomain_codeset (PACKAGE, "UTF-8");
458  bindtextdomain (PACKAGE "-plugins", aud_paths[AUD_PATH_LOCALE_DIR]);
459  bind_textdomain_codeset (PACKAGE "-plugins", "UTF-8");
460  textdomain (PACKAGE);
461 
462 #ifdef USE_EGGSM
463  egg_sm_client_set_mode (EGG_SM_CLIENT_MODE_NORMAL);
464  egg_set_desktop_file (aud_paths[AUD_PATH_DESKTOP_FILE]);
465 #endif
466 }
467 
468 static void init_two (int * p_argc, char * * * p_argv)
469 {
470  if (! headless)
471  {
472  g_thread_init (NULL);
473  gtk_rc_add_default_file (aud_paths[AUD_PATH_GTKRC_FILE]);
474  gtk_init (p_argc, p_argv);
475  }
476 
477  config_load ();
478  chardet_init ();
479 
480  tag_set_verbose (verbose);
482 
483  eq_init ();
484 
485 #ifdef HAVE_SIGWAIT
486  signals_init ();
487 #endif
488 #ifdef USE_EGGSM
489  smclient_init ();
490 #endif
491 
492  AUDDBG ("Loading lowlevel plugins.\n");
494 
495  playlist_init ();
496  adder_init ();
497  art_init ();
498  load_playlists ();
499 
500 #ifdef USE_DBUS
501  init_dbus ();
502 #endif
503 
504  do_commands ();
505 
506  AUDDBG ("Loading highlevel plugins.\n");
508 
510 }
511 
512 static void shut_down (void)
513 {
515 
516  AUDDBG ("Capturing state.\n");
517  hook_call ("config save", NULL);
519 
520  AUDDBG ("Unloading highlevel plugins.\n");
521  stop_plugins_two ();
522 
523  AUDDBG ("Stopping playback.\n");
524  if (playback_get_playing ())
525  {
526  bool_t stop_after_song = get_bool (NULL, "stop_after_current_song");
527  playback_stop ();
528  set_bool (NULL, "stop_after_current_song", stop_after_song);
529  }
530 
531 #ifdef USE_DBUS
532  cleanup_dbus ();
533 #endif
534 
535  adder_cleanup ();
536  art_cleanup ();
537  history_cleanup ();
538  playlist_end ();
539 
540  AUDDBG ("Unloading lowlevel plugins.\n");
541  stop_plugins_one ();
542 
543  AUDDBG ("Saving configuration.\n");
544  config_save ();
545  config_cleanup ();
546 
547  eq_cleanup ();
548 
549  strpool_shutdown ();
550 }
551 
553 {
554  AUDDBG ("Saving configuration.\n");
555  hook_call ("config save", NULL);
557  config_save ();
558  return TRUE;
559 }
560 
561 int main(int argc, char ** argv)
562 {
563  init_one ();
564  parse_options (& argc, & argv);
565 
566  if (options.version)
567  {
568  printf ("%s %s (%s)\n", _("Audacious"), VERSION, BUILDSTAMP);
569  return EXIT_SUCCESS;
570  }
571 
572  if (! get_lock ())
573  do_remote (); /* may exit */
574 
575  AUDDBG ("No remote session; starting up.\n");
576  init_two (& argc, & argv);
577 
578  AUDDBG ("Startup complete.\n");
579  g_timeout_add_seconds (AUTOSAVE_INTERVAL, (GSourceFunc) do_autosave, NULL);
580 
581  hook_associate ("quit", (HookFunction) gtk_main_quit, NULL);
582  gtk_main ();
583  hook_dissociate ("quit", (HookFunction) gtk_main_quit);
584 
585  shut_down ();
586  release_lock ();
587  return EXIT_SUCCESS;
588 }