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