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