Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$
main.c
Go to the documentation of this file.
00001 /*  Audacious - Cross-platform multimedia player
00002  *  Copyright (C) 2005-2011  Audacious development team.
00003  *
00004  *  Based on BMP:
00005  *  Copyright (C) 2003-2004  BMP development team.
00006  *
00007  *  Based on XMMS:
00008  *  Copyright (C) 1998-2003  XMMS development team.
00009  *
00010  *  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; under version 3 of the License.
00013  *
00014  *  This program is distributed in the hope that it will be useful,
00015  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  *  GNU General Public License for more details.
00018  *
00019  *  You should have received a copy of the GNU General Public License
00020  *  along with this program.  If not, see <http://www.gnu.org/licenses>.
00021  *
00022  *  The Audacious team does not consider modular code linking to
00023  *  Audacious or using our public API to be a derived work.
00024  */
00025 
00026 #include <errno.h>
00027 #include <limits.h>
00028 
00029 #include <gtk/gtk.h>
00030 
00031 #include <libaudcore/audstrings.h>
00032 #include <libaudcore/hook.h>
00033 #include <libaudtag/audtag.h>
00034 
00035 #include "config.h"
00036 
00037 #ifdef USE_DBUS
00038 #include "audctrl.h"
00039 #include "dbus-service.h"
00040 #endif
00041 
00042 #ifdef USE_EGGSM
00043 #include "eggdesktopfile.h"
00044 #include "eggsmclient.h"
00045 #endif
00046 
00047 #include "audconfig.h"
00048 #include "configdb.h"
00049 #include "debug.h"
00050 #include "drct.h"
00051 #include "equalizer.h"
00052 #include "i18n.h"
00053 #include "interface.h"
00054 #include "misc.h"
00055 #include "playback.h"
00056 #include "playlist.h"
00057 #include "plugins.h"
00058 #include "util.h"
00059 
00060 /* adder.c */
00061 void adder_init (void);
00062 void adder_cleanup (void);
00063 
00064 /* chardet.c */
00065 void chardet_init (void);
00066 
00067 /* mpris-signals.c */
00068 void mpris_signals_init (void);
00069 void mpris_signals_cleanup (void);
00070 
00071 /* signals.c */
00072 void signals_init (void);
00073 
00074 /* smclient.c */
00075 void smclient_init (void);
00076 
00077 #define AUTOSAVE_INTERVAL 300 /* seconds */
00078 
00079 static struct {
00080     gchar **filenames;
00081     gint session;
00082     gboolean play, stop, pause, fwd, rew, play_pause, show_jump_box;
00083     gboolean enqueue, mainwin, remote;
00084     gboolean enqueue_to_temp;
00085     gboolean version;
00086     gchar *previous_session_id;
00087 } options;
00088 
00089 static gchar * aud_paths[AUD_PATH_COUNT];
00090 
00091 static void make_dirs(void)
00092 {
00093 #ifdef S_IRGRP
00094     const mode_t mode755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
00095 #else
00096     const mode_t mode755 = S_IRWXU;
00097 #endif
00098 
00099     make_directory(aud_paths[AUD_PATH_USER_DIR], mode755);
00100     make_directory(aud_paths[AUD_PATH_USER_PLUGIN_DIR], mode755);
00101     make_directory(aud_paths[AUD_PATH_PLAYLISTS_DIR], mode755);
00102 }
00103 
00104 static void normalize_path (gchar * path)
00105 {
00106 #ifdef _WIN32
00107     string_replace_char (path, '/', '\\');
00108 #endif
00109     gint len = strlen (path);
00110 #ifdef _WIN32
00111     if (len > 3 && path[len - 1] == '\\') /* leave "C:\" */
00112 #else
00113     if (len > 1 && path[len - 1] == '/') /* leave leading "/" */
00114 #endif
00115         path[len - 1] = 0;
00116 }
00117 
00118 static gchar * last_path_element (gchar * path)
00119 {
00120     gchar * slash = strrchr (path, G_DIR_SEPARATOR);
00121     return (slash && slash[1]) ? slash + 1 : NULL;
00122 }
00123 
00124 static void strip_path_element (gchar * path, gchar * elem)
00125 {
00126 #ifdef _WIN32
00127     if (elem > path + 3)
00128 #else
00129     if (elem > path + 1)
00130 #endif
00131         elem[-1] = 0; /* overwrite slash */
00132     else
00133         elem[0] = 0; /* leave [drive letter and] leading slash */
00134 }
00135 
00136 static void relocate_path (gchar * * pathp, const gchar * old, const gchar * new)
00137 {
00138     gchar * path = * pathp;
00139     gint oldlen = strlen (old);
00140     gint newlen = strlen (new);
00141 
00142     if (oldlen && old[oldlen - 1] == G_DIR_SEPARATOR)
00143         oldlen --;
00144     if (newlen && new[newlen - 1] == G_DIR_SEPARATOR)
00145         newlen --;
00146 
00147 #ifdef _WIN32
00148     if (strncasecmp (path, old, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR))
00149 #else
00150     if (strncmp (path, old, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR))
00151 #endif
00152     {
00153         fprintf (stderr, "Failed to relocate a data path.  Falling back to "
00154          "compile-time path: %s\n", path);
00155         return;
00156     }
00157 
00158     * pathp = g_strdup_printf ("%.*s%s", newlen, new, path + oldlen);
00159     g_free (path);
00160 }
00161 
00162 static void relocate_paths (void)
00163 {
00164     /* Start with the paths hard coded at compile time. */
00165     aud_paths[AUD_PATH_BIN_DIR] = g_strdup (HARDCODE_BINDIR);
00166     aud_paths[AUD_PATH_DATA_DIR] = g_strdup (HARDCODE_DATADIR);
00167     aud_paths[AUD_PATH_PLUGIN_DIR] = g_strdup (HARDCODE_PLUGINDIR);
00168     aud_paths[AUD_PATH_LOCALE_DIR] = g_strdup (HARDCODE_LOCALEDIR);
00169     aud_paths[AUD_PATH_DESKTOP_FILE] = g_strdup (HARDCODE_DESKTOPFILE);
00170     aud_paths[AUD_PATH_ICON_FILE] = g_strdup (HARDCODE_ICONFILE);
00171     normalize_path (aud_paths[AUD_PATH_BIN_DIR]);
00172     normalize_path (aud_paths[AUD_PATH_DATA_DIR]);
00173     normalize_path (aud_paths[AUD_PATH_PLUGIN_DIR]);
00174     normalize_path (aud_paths[AUD_PATH_LOCALE_DIR]);
00175     normalize_path (aud_paths[AUD_PATH_DESKTOP_FILE]);
00176     normalize_path (aud_paths[AUD_PATH_ICON_FILE]);
00177 
00178     /* Compare the compile-time path to the executable and the actual path to
00179      * see if we have been moved. */
00180     gchar * old = g_strdup (aud_paths[AUD_PATH_BIN_DIR]);
00181     gchar * new = get_path_to_self ();
00182     if (! new)
00183     {
00184 ERR:
00185         g_free (old);
00186         g_free (new);
00187         return;
00188     }
00189     normalize_path (new);
00190 
00191     /* Strip the name of the executable file, leaving the path. */
00192     gchar * base = last_path_element (new);
00193     if (! base)
00194         goto ERR;
00195     strip_path_element (new, base);
00196 
00197     /* Strip innermost folder names from both paths as long as they match.  This
00198      * leaves a compile-time prefix and a run-time one to replace it with. */
00199     gchar * a, * b;
00200     while ((a = last_path_element (old)) && (b = last_path_element (new)) &&
00201 #ifdef _WIN32
00202      ! strcasecmp (a, b))
00203 #else
00204      ! strcmp (a, b))
00205 #endif
00206     {
00207         strip_path_element (old, a);
00208         strip_path_element (new, b);
00209     }
00210 
00211     /* Do the replacements. */
00212     relocate_path (& aud_paths[AUD_PATH_BIN_DIR], old, new);
00213     relocate_path (& aud_paths[AUD_PATH_DATA_DIR], old, new);
00214     relocate_path (& aud_paths[AUD_PATH_PLUGIN_DIR], old, new);
00215     relocate_path (& aud_paths[AUD_PATH_LOCALE_DIR], old, new);
00216     relocate_path (& aud_paths[AUD_PATH_DESKTOP_FILE], old, new);
00217     relocate_path (& aud_paths[AUD_PATH_ICON_FILE], old, new);
00218 
00219     g_free (old);
00220     g_free (new);
00221 }
00222 
00223 static void init_paths (void)
00224 {
00225     relocate_paths ();
00226 
00227     const gchar * xdg_config_home = g_get_user_config_dir ();
00228     const gchar * xdg_data_home = g_get_user_data_dir ();
00229 
00230 #ifdef _WIN32
00231     /* Some libraries (libmcs) and plugins (filewriter) use these variables,
00232      * which are generally not set on Windows. */
00233     g_setenv ("HOME", g_get_home_dir (), TRUE);
00234     g_setenv ("XDG_CONFIG_HOME", xdg_config_home, TRUE);
00235     g_setenv ("XDG_DATA_HOME", xdg_data_home, TRUE);
00236     g_setenv ("XDG_CACHE_HOME", g_get_user_cache_dir (), TRUE);
00237 #endif
00238 
00239     aud_paths[AUD_PATH_USER_DIR] = g_build_filename(xdg_config_home, "audacious", NULL);
00240     aud_paths[AUD_PATH_USER_PLUGIN_DIR] = g_build_filename(xdg_data_home, "audacious", "Plugins", NULL);
00241     aud_paths[AUD_PATH_PLAYLISTS_DIR] = g_build_filename(aud_paths[AUD_PATH_USER_DIR], "playlists", NULL);
00242     aud_paths[AUD_PATH_PLAYLIST_FILE] = g_build_filename(aud_paths[AUD_PATH_USER_DIR], "playlist.xspf", NULL);
00243     aud_paths[AUD_PATH_GTKRC_FILE] = g_build_filename(aud_paths[AUD_PATH_USER_DIR], "gtkrc", NULL);
00244 
00245     for (gint i = 0; i < AUD_PATH_COUNT; i ++)
00246         AUDDBG ("Data path: %s\n", aud_paths[i]);
00247 }
00248 
00249 const gchar * get_path (gint id)
00250 {
00251     g_return_val_if_fail (id >= 0 && id < AUD_PATH_COUNT, NULL);
00252     return aud_paths[id];
00253 }
00254 
00255 static GOptionEntry cmd_entries[] = {
00256     {"rew", 'r', 0, G_OPTION_ARG_NONE, &options.rew, N_("Skip backwards in playlist"), NULL},
00257     {"play", 'p', 0, G_OPTION_ARG_NONE, &options.play, N_("Start playing current playlist"), NULL},
00258     {"pause", 'u', 0, G_OPTION_ARG_NONE, &options.pause, N_("Pause current song"), NULL},
00259     {"stop", 's', 0, G_OPTION_ARG_NONE, &options.stop, N_("Stop current song"), NULL},
00260     {"play-pause", 't', 0, G_OPTION_ARG_NONE, &options.play_pause, N_("Pause if playing, play otherwise"), NULL},
00261     {"fwd", 'f', 0, G_OPTION_ARG_NONE, &options.fwd, N_("Skip forward in playlist"), NULL},
00262     {"show-jump-box", 'j', 0, G_OPTION_ARG_NONE, &options.show_jump_box, N_("Display Jump to File dialog"), NULL},
00263     {"enqueue", 'e', 0, G_OPTION_ARG_NONE, &options.enqueue, N_("Add files to the playlist"), NULL},
00264     {"enqueue-to-temp", 'E', 0, G_OPTION_ARG_NONE, &options.enqueue_to_temp, N_("Add new files to a temporary playlist"), NULL},
00265     {"show-main-window", 'm', 0, G_OPTION_ARG_NONE, &options.mainwin, N_("Display the main window"), NULL},
00266     {"version", 'v', 0, G_OPTION_ARG_NONE, &options.version, N_("Show version"), NULL},
00267     {"verbose", 'V', 0, G_OPTION_ARG_NONE, &cfg.verbose, N_("Print debugging messages"), NULL},
00268     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &options.filenames, N_("FILE..."), NULL},
00269     {NULL},
00270 };
00271 
00272 static void parse_options (gint * argc, gchar *** argv)
00273 {
00274     GOptionContext *context;
00275     GError *error = NULL;
00276 
00277     memset (& options, 0, sizeof options);
00278     options.session = -1;
00279 
00280     context = g_option_context_new(_("- play multimedia files"));
00281     g_option_context_add_main_entries(context, cmd_entries, PACKAGE_NAME);
00282     g_option_context_add_group(context, gtk_get_option_group(FALSE));
00283 #ifdef USE_EGGSM
00284     g_option_context_add_group(context, egg_sm_client_get_option_group());
00285 #endif
00286 
00287     if (!g_option_context_parse(context, argc, argv, &error))
00288     {
00289         fprintf (stderr,
00290          _("%s: %s\nTry `%s --help' for more information.\n"), (* argv)[0],
00291          error->message, (* argv)[0]);
00292         exit (EXIT_FAILURE);
00293     }
00294 
00295     g_option_context_free (context);
00296 }
00297 
00298 static gboolean get_lock (void)
00299 {
00300     gchar path[PATH_MAX];
00301     snprintf (path, sizeof path, "%s" G_DIR_SEPARATOR_S "lock",
00302      aud_paths[AUD_PATH_USER_DIR]);
00303 
00304     int handle = open (path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
00305 
00306     if (handle < 0)
00307     {
00308         if (errno != EEXIST)
00309             fprintf (stderr, "Cannot create %s: %s.\n", path, strerror (errno));
00310         return FALSE;
00311     }
00312 
00313     close (handle);
00314     return TRUE;
00315 }
00316 
00317 static void release_lock (void)
00318 {
00319     gchar path[PATH_MAX];
00320     snprintf (path, sizeof path, "%s" G_DIR_SEPARATOR_S "lock",
00321      aud_paths[AUD_PATH_USER_DIR]);
00322 
00323     unlink (path);
00324 }
00325 
00326 static GList * convert_filenames (void)
00327 {
00328     if (! options.filenames)
00329         return NULL;
00330 
00331     gchar * * f = options.filenames;
00332     GList * list = NULL;
00333     gchar * cur = g_get_current_dir ();
00334 
00335     for (gint i = 0; f[i]; i ++)
00336     {
00337         gchar * uri;
00338 
00339         if (strstr (f[i], "://"))
00340             uri = g_strdup (f[i]);
00341         else if (g_path_is_absolute (f[i]))
00342             uri = filename_to_uri (f[i]);
00343         else
00344         {
00345             gchar * tmp = g_build_filename (cur, f[i], NULL);
00346             uri = filename_to_uri (tmp);
00347             g_free (tmp);
00348         }
00349 
00350         list = g_list_prepend (list, uri);
00351     }
00352 
00353     g_free (cur);
00354     return g_list_reverse (list);
00355 }
00356 
00357 static void do_remote (void)
00358 {
00359 #ifdef USE_DBUS
00360     DBusGProxy * session = audacious_get_dbus_proxy ();
00361 
00362     if (session && audacious_remote_is_running (session))
00363     {
00364         GList * list = convert_filenames ();
00365 
00366         /* if no command line options, then present running instance */
00367         if (! (list || options.play || options.pause || options.play_pause ||
00368          options.stop || options.rew || options.fwd || options.show_jump_box ||
00369          options.mainwin))
00370             options.mainwin = TRUE;
00371 
00372         if (list)
00373         {
00374             if (options.enqueue_to_temp)
00375                 audacious_remote_playlist_open_list_to_temp (session, list);
00376             else if (options.enqueue)
00377                 audacious_remote_playlist_add (session, list);
00378             else
00379                 audacious_remote_playlist_open_list (session, list);
00380 
00381             g_list_foreach (list, (GFunc) g_free, NULL);
00382             g_list_free (list);
00383         }
00384 
00385         if (options.play)
00386             audacious_remote_play (session);
00387         if (options.pause)
00388             audacious_remote_pause (session);
00389         if (options.play_pause)
00390             audacious_remote_play_pause (session);
00391         if (options.stop)
00392             audacious_remote_stop (session);
00393         if (options.rew)
00394             audacious_remote_playlist_prev (session);
00395         if (options.fwd)
00396             audacious_remote_playlist_next (session);
00397         if (options.show_jump_box)
00398             audacious_remote_show_jtf_box (session);
00399         if (options.mainwin)
00400             audacious_remote_main_win_toggle (session, TRUE);
00401 
00402         exit (EXIT_SUCCESS);
00403     }
00404 #endif
00405 
00406     GtkWidget * dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_WARNING,
00407      GTK_BUTTONS_OK_CANCEL, _("Audacious seems to be already running but is "
00408      "not responding.  You can start another instance of the program, but "
00409      "please be warned that this can cause data loss.  If Audacious is not "
00410      "running, you can safely ignore this message.  Press OK to start "
00411      "Audacious or Cancel to quit."));
00412 
00413     g_signal_connect (dialog, "destroy", (GCallback) gtk_widget_destroyed,
00414      & dialog);
00415 
00416     if (gtk_dialog_run ((GtkDialog *) dialog) != GTK_RESPONSE_OK)
00417         exit (EXIT_FAILURE);
00418 
00419     if (dialog)
00420         gtk_widget_destroy (dialog);
00421 }
00422 
00423 static void do_commands (void)
00424 {
00425     GList * list = convert_filenames ();
00426 
00427     if (list)
00428     {
00429         if (options.enqueue_to_temp)
00430         {
00431             drct_pl_open_temp_list (list);
00432             cfg.resume_state = 0;
00433         }
00434         else if (options.enqueue)
00435             drct_pl_add_list (list, -1);
00436         else
00437         {
00438             drct_pl_open_list (list);
00439             cfg.resume_state = 0;
00440         }
00441 
00442         g_list_foreach (list, (GFunc) g_free, NULL);
00443         g_list_free (list);
00444     }
00445 
00446     if (cfg.resume_playback_on_startup && cfg.resume_state > 0)
00447         playback_play (cfg.resume_playback_on_startup_time, cfg.resume_state ==
00448          2);
00449 
00450     if (options.play || options.play_pause)
00451     {
00452         if (! playback_get_playing ())
00453             playback_play (0, FALSE);
00454         else if (playback_get_paused ())
00455             playback_pause ();
00456     }
00457 
00458     if (options.show_jump_box)
00459         interface_show_jump_to_track ();
00460     if (options.mainwin)
00461         interface_show (TRUE);
00462 }
00463 
00464 static void init_one (gint * p_argc, gchar * * * p_argv)
00465 {
00466     init_paths ();
00467     make_dirs ();
00468 
00469     bindtextdomain (PACKAGE_NAME, aud_paths[AUD_PATH_LOCALE_DIR]);
00470     bind_textdomain_codeset (PACKAGE_NAME, "UTF-8");
00471     bindtextdomain (PACKAGE_NAME "-plugins", aud_paths[AUD_PATH_LOCALE_DIR]);
00472     bind_textdomain_codeset (PACKAGE_NAME "-plugins", "UTF-8");
00473     textdomain (PACKAGE_NAME);
00474 
00475     mowgli_init ();
00476     chardet_init ();
00477 
00478     g_thread_init (NULL);
00479     gdk_threads_init ();
00480     gdk_threads_enter ();
00481 
00482     gtk_rc_add_default_file (aud_paths[AUD_PATH_GTKRC_FILE]);
00483     gtk_init (p_argc, p_argv);
00484 
00485 #ifdef USE_EGGSM
00486     egg_sm_client_set_mode (EGG_SM_CLIENT_MODE_NORMAL);
00487     egg_set_desktop_file (aud_paths[AUD_PATH_DESKTOP_FILE]);
00488 #endif
00489 }
00490 
00491 static void init_two (void)
00492 {
00493     hook_init ();
00494     tag_init ();
00495 
00496     aud_config_load ();
00497     tag_set_verbose (cfg.verbose);
00498     vfs_set_verbose (cfg.verbose);
00499 
00500     eq_init ();
00501     register_interface_hooks ();
00502 
00503 #ifdef HAVE_SIGWAIT
00504     signals_init ();
00505 #endif
00506 #ifdef USE_EGGSM
00507     smclient_init ();
00508 #endif
00509 
00510     AUDDBG ("Loading lowlevel plugins.\n");
00511     start_plugins_one ();
00512 
00513     playlist_init ();
00514     adder_init ();
00515     load_playlists ();
00516 
00517 #ifdef USE_DBUS
00518     init_dbus ();
00519 #endif
00520 
00521     do_commands ();
00522 
00523     AUDDBG ("Loading highlevel plugins.\n");
00524     start_plugins_two ();
00525 
00526     mpris_signals_init ();
00527 }
00528 
00529 static void shut_down (void)
00530 {
00531     mpris_signals_cleanup ();
00532 
00533     AUDDBG ("Capturing state.\n");
00534     aud_config_save ();
00535     save_playlists ();
00536 
00537     AUDDBG ("Unloading highlevel plugins.\n");
00538     stop_plugins_two ();
00539 
00540     AUDDBG ("Stopping playback.\n");
00541     if (playback_get_playing ())
00542         playback_stop ();
00543 
00544     adder_cleanup ();
00545     playlist_end ();
00546 
00547     AUDDBG ("Unloading lowlevel plugins.\n");
00548     stop_plugins_one ();
00549 
00550     AUDDBG ("Saving configuration.\n");
00551     cfg_db_flush ();
00552 
00553     gdk_threads_leave ();
00554 }
00555 
00556 static gboolean autosave_cb (void * unused)
00557 {
00558     AUDDBG ("Saving configuration.\n");
00559     aud_config_save ();
00560     cfg_db_flush ();
00561     save_playlists ();
00562     return TRUE;
00563 }
00564 
00565 gint main(gint argc, gchar ** argv)
00566 {
00567     init_one (& argc, & argv);
00568     parse_options (& argc, & argv);
00569 
00570     if (options.version)
00571     {
00572         printf ("%s %s (%s)\n", _("Audacious"), VERSION, BUILDSTAMP);
00573         return EXIT_SUCCESS;
00574     }
00575 
00576     if (! get_lock ())
00577         do_remote (); /* may exit */
00578 
00579     AUDDBG ("No remote session; starting up.\n");
00580     init_two ();
00581 
00582     AUDDBG ("Startup complete.\n");
00583     g_timeout_add_seconds (AUTOSAVE_INTERVAL, autosave_cb, NULL);
00584     hook_associate ("quit", (HookFunction) gtk_main_quit, NULL);
00585 
00586     gtk_main ();
00587 
00588     shut_down ();
00589     release_lock ();
00590     return EXIT_SUCCESS;
00591 }