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