/* DeaDBeeF - ultimate music player for GNU/Linux systems with X11 Copyright (C) 2009 Alexey Yakovenko This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H # include #endif #include "playlist.h" #include "playback.h" #include "unistd.h" #include "threading.h" #include "messagepump.h" #include "codec.h" #include "streamer.h" #include "conf.h" #include "volume.h" #include "session.h" #include "plugins.h" #ifndef PREFIX #error PREFIX must be defined #endif // some common global variables char confdir[1024]; // $HOME/.config char dbconfdir[1024]; // $HOME/.config/deadbeef char defpl[1024]; // $HOME/.config/deadbeef/default.dbpl char sessfile[1024]; // $HOME/.config/deadbeef/session // -1 error, program must exit with error code -1 // 0 proceed normally as nothing happened // 1 no error, but program must exit with error code 0 // 2 no error, start playback immediately after startup // 3 no error, don't start playback immediately after startup int exec_command_line (const char *cmdline, int len, int filter) { const uint8_t *parg = (const uint8_t *)cmdline; const uint8_t *pend = cmdline + len; int exitcode = 0; int queue = 0; while (parg < pend) { if (filter == 1) { if (!strcmp (parg, "--help") || !strcmp (parg, "-h")) { printf ("Usage: deadbeef [options] [file(s)]\n"); printf ("Options:\n"); printf (" --help or -h Print help (this message) and exit\n"); printf (" --version Print version info and exit\n"); printf (" --play Start playback\n"); printf (" --stop Stop playback\n"); printf (" --pause Pause playback\n"); printf (" --next Next song in playlist\n"); printf (" --prev Previous song in playlist\n"); printf (" --random Previous song in playlist\n"); printf (" --queue Append file(s) to existing playlist\n"); return 1; } else if (!strcmp (parg, "--version")) { printf ("DeaDBeeF %s Copyright (C) 2009 Alexey Yakovenko\n", VERSION); return 1; } } else if (filter == 0) { if (!strcmp (parg, "--next")) { messagepump_push (M_NEXTSONG, 0, 0, 0); } else if (!strcmp (parg, "--prev")) { messagepump_push (M_PREVSONG, 0, 0, 0); } else if (!strcmp (parg, "--play")) { messagepump_push (M_PLAYSONG, 0, 0, 0); } else if (!strcmp (parg, "--stop")) { messagepump_push (M_STOPSONG, 0, 0, 0); } else if (!strcmp (parg, "--pause")) { messagepump_push (M_PAUSESONG, 0, 0, 0); } else if (!strcmp (parg, "--random")) { messagepump_push (M_PLAYRANDOM, 0, 0, 0); } else if (!strcmp (parg, "--queue")) { queue = 1; } else if (parg[0] != '-') { break; } } parg += strlen (parg); parg++; } if (parg < pend) { // add files if (!queue) { pl_free (); pl_reset_cursor (); } while (parg < pend) { char resolved[PATH_MAX]; const char *pname; if (realpath (parg, resolved)) { pname = resolved; } else { pname = parg; } if (pl_add_file (pname, NULL, NULL) >= 0) { if (queue) { exitcode = 3; } else { exitcode = 2; } } parg += strlen (parg); parg++; } } if (exitcode == 2 || exitcode == 3) { // added some files, need to redraw messagepump_push (M_PLAYLISTREFRESH, 0, 0, 0); } return exitcode; } static struct sockaddr_un srv_local; static struct sockaddr_un srv_remote; static unsigned srv_socket; int server_start (void) { srv_socket = socket (AF_UNIX, SOCK_STREAM, 0); int flags; flags = fcntl (srv_socket, F_GETFL,0); if (flags == -1) { fprintf (stderr, "server_start failed, flags == -1\n"); return -1; } fcntl(srv_socket, F_SETFL, flags | O_NONBLOCK); srv_local.sun_family = AF_UNIX; /* local is declared before socket() ^ */ snprintf (srv_local.sun_path, 108, "%s/socket", dbconfdir); unlink(srv_local.sun_path); int len = strlen(srv_local.sun_path) + sizeof(srv_local.sun_family); bind(srv_socket, (struct sockaddr *)&srv_local, len); if (listen(srv_socket, 5) == -1) { perror("listen"); return -1; } return 0; } void server_close (void) { if (srv_socket) { close (srv_socket); srv_socket = 0; } } int server_update (void) { // handle remote stuff int t = sizeof (srv_remote); unsigned s2; s2 = accept(srv_socket, (struct sockaddr *)&srv_remote, &t); if (s2 == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { perror("accept"); return -1; } else if (s2 != -1) { char str[2048]; int size; if ((size = recv (s2, str, 2048, 0)) >= 0) { if (size == 1 && str[0] == 0) { // FIXME: that should be called right after activation of gui plugin plug_trigger_event (DB_EV_ACTIVATE, 0); } else { int res = exec_command_line (str, size, 0); if (res == 2) { streamer_play_current_track (); } } } send (s2, "", 1, 0); close(s2); } return 0; } void player_thread (uintptr_t ctx) { prctl (PR_SET_NAME, "deadbeef-player", 0, 0, 0, 0); for (;;) { static int srvupd_count = 0; if (--srvupd_count <= 0) { srvupd_count = 10; if (server_update () < 0) { messagepump_push (M_TERMINATE, 0, 0, 0); } } uint32_t msg; uintptr_t ctx; uint32_t p1; uint32_t p2; while (messagepump_pop(&msg, &ctx, &p1, &p2) != -1) { switch (msg) { case M_REINIT_SOUND: { int play = 0; if (!palsa_ispaused () && !palsa_isstopped ()) { play = 1; } palsa_free (); palsa_init (); if (play) { palsa_play (); } } break; case M_TERMINATE: return; case M_SONGCHANGED: plug_trigger_event_trackchange (p1, p2); break; case M_PLAYSONG: streamer_play_current_track (); break; case M_TRACKCHANGED: plug_trigger_event_trackinfochanged (p1); break; case M_PLAYSONGNUM: p_stop (); streamer_set_nextsong (p1, 1); break; case M_STOPSONG: streamer_set_nextsong (-2, 0); break; case M_NEXTSONG: p_stop (); pl_nextsong (1); break; case M_PREVSONG: p_stop (); pl_prevsong (); break; case M_PAUSESONG: if (p_ispaused ()) { p_unpause (); plug_trigger_event_paused (0); } else { p_pause (); plug_trigger_event_paused (1); } break; case M_PLAYRANDOM: p_stop (); pl_randomsong (); break; case M_FMDRAGDROP: // // handle drag-n-drop from filemanager // that should be handled internally in ui plugin // guiplug_add_fm_dropped_files ((char *)ctx, p1, p2) break; case M_PLAYLISTREFRESH: plug_trigger_event_playlistchanged (); break; case M_CONFIGCHANGED: palsa_configchanged (); streamer_configchanged (); plug_trigger_event (DB_EV_CONFIGCHANGED, 0); break; } } usleep(50000); plug_trigger_event (DB_EV_FRAMEUPDATE, 0); } } void sigterm_handler (int sig) { fprintf (stderr, "got sigterm, saving...\n"); pl_save (defpl); conf_save (); fprintf (stderr, "bye.\n"); exit (0); } int main (int argc, char *argv[]) { srand (time (NULL)); prctl (PR_SET_NAME, "deadbeef-main", 0, 0, 0, 0); char *homedir = getenv ("HOME"); if (!homedir) { fprintf (stderr, "unable to find home directory. stopping.\n"); return -1; } char *xdg_conf_dir = getenv ("XDG_CONFIG_HOME"); if (xdg_conf_dir) { if (snprintf (confdir, sizeof (confdir), "%s", xdg_conf_dir) > sizeof (confdir)) { fprintf (stderr, "fatal: XDG_CONFIG_HOME value is too long: %s\n", xdg_conf_dir); return -1; } } else { if (snprintf (confdir, sizeof (confdir), "%s/.config", homedir) > sizeof (confdir)) { fprintf (stderr, "fatal: HOME value is too long: %s\n", homedir); return -1; } } if (snprintf (defpl, sizeof (defpl), "%s/deadbeef/default.dbpl", confdir) > sizeof (defpl)) { fprintf (stderr, "fatal: out of memory while configuring\n"); return -1; } if (snprintf (sessfile, sizeof (sessfile), "%s/deadbeef/session", confdir) > sizeof (sessfile)) { fprintf (stderr, "fatal: out of memory while configuring\n"); return -1; } mkdir (confdir, 0755); if (snprintf (dbconfdir, sizeof (dbconfdir), "%s/deadbeef", confdir) > sizeof (dbconfdir)) { fprintf (stderr, "fatal: out of memory while configuring\n"); return -1; } mkdir (dbconfdir, 0755); char cmdline[2048]; int size = 0; if (argc > 1) { size = 2048; // join command line into single string char *p = cmdline; cmdline[0] = 0; for (int i = 1; i < argc; i++) { if (size < 2) { break; } if (i > 1) { size--; p++; } int len = strlen (argv[i]); if (len >= size) { break; } char resolved[PATH_MAX]; // need to resolve path here, because remote doesn't know current // path of this process if (argv[i][0] != '-' && realpath (argv[i], resolved)) { len = strlen (resolved); memcpy (p, resolved, len+1); } else { memcpy (p, argv[i], len+1); } p += len; size -= len; } size = 2048 - size + 1; if (exec_command_line (cmdline, size, 1) == 1) { return 0; // if it was help request } } // try to connect to remote player int s, t, len; struct sockaddr_un remote; char str[100]; if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } remote.sun_family = AF_UNIX; snprintf (remote.sun_path, 108, "%s/socket", dbconfdir); len = strlen(remote.sun_path) + sizeof(remote.sun_family); if (connect(s, (struct sockaddr *)&remote, len) == 0) { if (argc <= 1) { cmdline[0] = 0; size = 1; } // pass args to remote and exit if (send(s, cmdline, size, 0) == -1) { perror ("send"); exit (-1); } char out[1]; if (recv(s, out, 1, 0) == -1) { fprintf (stderr, "failed to pass args to remote!\n"); exit (-1); } close (s); exit (0); } close(s); signal (SIGTERM, sigterm_handler); // become a server server_start (); conf_load (); plug_load_all (); pl_load (defpl); session_load (sessfile); messagepump_init (); codec_init_locking (); streamer_init (); // p_init (); // thread_start (player_thread, 0); volume_set_db (conf_get_float ("playback.volume", 0)); if (argc > 1) { int res = exec_command_line (cmdline, size, 0); if (res == -1) { server_close (); return -1; } if (res == 2) { messagepump_push (M_PLAYSONG, 0, 0, 0); } } player_thread (0); // save config pl_save (defpl); conf_save (); // stop receiving messages from outside server_close (); // at this point we can simply do exit(0), but let's clean up for debugging messagepump_free (); p_free (); streamer_free (); codec_free_locking (); session_save (sessfile); pl_free (); conf_free (); plug_unload_all (); return 0; }