1 /*
2 This file is part of Deadbeef Player source code
3 http://deadbeef.sourceforge.net
4
5 application launcher, compatible with GNU/Linux and most other POSIX systems
6
7 Copyright (C) 2009-2016 Alexey Yakovenko
8
9 This software is provided 'as-is', without any express or implied
10 warranty. In no event will the authors be held liable for any damages
11 arising from the use of this software.
12
13 Permission is granted to anyone to use this software for any purpose,
14 including commercial applications, and to alter it and redistribute it
15 freely, subject to the following restrictions:
16
17 1. The origin of this software must not be misrepresented; you must not
18 claim that you wrote the original software. If you use this software
19 in a product, an acknowledgment in the product documentation would be
20 appreciated but is not required.
21 2. Altered source versions must be plainly marked as such, and must not be
22 misrepresented as being the original software.
23 3. This notice may not be removed or altered from any source distribution.
24
25 Alexey Yakovenko waker@users.sourceforge.net
26 */
27 #ifdef HAVE_CONFIG_H
28 # include <config.h>
29 #endif
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <stddef.h>
35 #include <time.h>
36 #include <locale.h>
37 #ifdef __linux__
38 #include <sys/prctl.h>
39 #endif
40 #ifndef __linux__
41 #define _POSIX_C_SOURCE 1
42 #endif
43 #include <limits.h>
44 #include <errno.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <sys/select.h>
50 #include <sys/un.h>
51 #include <sys/fcntl.h>
52 #include <sys/errno.h>
53 #include <signal.h>
54 #ifdef __GLIBC__
55 #include <execinfo.h>
56 #endif
57 #include <unistd.h>
58 #include "gettext.h"
59 #include "playlist.h"
60 #include "threading.h"
61 #include "messagepump.h"
62 #include "streamer.h"
63 #include "conf.h"
64 #include "volume.h"
65 #include "plugins.h"
66 #include "common.h"
67 #include "junklib.h"
68 #ifdef HAVE_COCOAUI
69 #include "cocoautil.h"
70 #endif
71 #include "playqueue.h"
72 #include "tf.h"
73
74 #ifndef PREFIX
75 #error PREFIX must be defined
76 #endif
77
78 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
79 #define trace(fmt,...)
80
81 #ifdef HAVE_COCOAUI
82 #define SYS_CONFIG_DIR "Library/Preferences"
83 #else
84 #define SYS_CONFIG_DIR ".config"
85 #endif
86
87 // some common global variables
88 char sys_install_path[PATH_MAX]; // see deadbeef->get_prefix
89 char confdir[PATH_MAX]; // $HOME/.config
90 char dbconfdir[PATH_MAX]; // $HOME/.config/deadbeef
91 char dbinstalldir[PATH_MAX]; // see deadbeef->get_prefix
92 char dbdocdir[PATH_MAX]; // see deadbeef->get_doc_dir
93 char dbplugindir[PATH_MAX]; // see deadbeef->get_plugin_dir
94 char dbpixmapdir[PATH_MAX]; // see deadbeef->get_pixmap_dir
95 char dbcachedir[PATH_MAX];
96
97 char use_gui_plugin[100];
98
99 static void
print_help(void)100 print_help (void) {
101 #ifdef ENABLE_NLS
102 bind_textdomain_codeset (PACKAGE, "");
103 #endif
104 fprintf (stdout, _("Usage: deadbeef [options] [--] [file(s)]\n"));
105 fprintf (stdout, _("Options:\n"));
106 fprintf (stdout, _(" --help or -h Print help (this message) and exit\n"));
107 fprintf (stdout, _(" --quit Quit player\n"));
108 fprintf (stdout, _(" --version Print version info and exit\n"));
109 fprintf (stdout, _(" --play Start playback\n"));
110 fprintf (stdout, _(" --stop Stop playback\n"));
111 fprintf (stdout, _(" --pause Pause playback\n"));
112 fprintf (stdout, _(" --toggle-pause Toggle pause\n"));
113 fprintf (stdout, _(" --play-pause Start playback if stopped, toggle pause otherwise\n"));
114 fprintf (stdout, _(" --next Next song in playlist\n"));
115 fprintf (stdout, _(" --prev Previous song in playlist\n"));
116 fprintf (stdout, _(" --random Random song in playlist\n"));
117 fprintf (stdout, _(" --queue Append file(s) to existing playlist\n"));
118 fprintf (stdout, _(" --gui PLUGIN Tells which GUI plugin to use, default is \"GTK2\"\n"));
119 fprintf (stdout, _(" --nowplaying FMT Print formatted track name to stdout\n"));
120 fprintf (stdout, _(" FMT %%-syntax: [a]rtist, [t]itle, al[b]um,\n"
121 " [l]ength, track[n]umber, [y]ear, [c]omment,\n"
122 " copy[r]ight, [e]lapsed\n"));
123 fprintf (stdout, _(" example: --nowplaying \"%%a - %%t\" should print \"artist - title\"\n"));
124 fprintf (stdout, _(" for more info, see %s\n"), "http://github.com/Alexey-Yakovenko/deadbeef/wiki/Title-formatting");
125 fprintf (stdout, _(" NOTE: --nowplaying is deprecated.\n"));
126 fprintf (stdout, _(" --nowplaying-tf FMT Print formatted track name to stdout, using the new title formatting\n"));
127 fprintf (stdout, _(" FMT syntax: http://github.com/Alexey-Yakovenko/deadbeef/wiki/Title-formatting-2.0\n"));
128 fprintf (stdout, _(" example: --nowplaying-tf \"%%artist%% - %%title%%\" should print \"artist - title\"\n"));
129 #ifdef ENABLE_NLS
130 bind_textdomain_codeset (PACKAGE, "UTF-8");
131 #endif
132 }
133
134 // Parse command line an return a single buffer with all
135 // parameters concatenated (separated by \0). File names
136 // are resolved.
137 char*
prepare_command_line(int argc,char * argv[],int * size)138 prepare_command_line (int argc, char *argv[], int *size) {
139 int seen_ddash = 0;
140
141 // initial buffer limit, will expand if needed
142 int limit = 4096;
143 char *buf = (char*) malloc (limit);
144
145 if (argc <= 1) {
146 buf[0] = 0;
147 *size = 1;
148 return buf;
149 }
150
151 int p = 0;
152 for (int i = 1; i < argc; i++) {
153 // if argument is a filename, try to resolve it
154 char resolved[PATH_MAX];
155 char *arg;
156 if (!strncmp ("--", argv[i], 2) && !seen_ddash || !realpath (argv[i], resolved)) {
157 arg = argv[i];
158 }
159 else {
160 arg = resolved;
161 }
162
163 // make sure that there is enough space in the buffer;
164 // re-allocate, if needed
165 int arglen = strlen(arg) + 1;
166 while (p + arglen >= limit) {
167 char *newbuf = (char*) malloc (limit * 2);
168 memcpy (newbuf, buf, p);
169 free (buf);
170 limit *= 2;
171 buf = newbuf;
172 }
173
174 memcpy (buf + p, arg, arglen);
175 p += arglen;
176
177 if (!strcmp("--", argv[i])) {
178 seen_ddash = 1;
179 }
180 }
181
182 *size = p;
183 return buf;
184 }
185
186
187 // this function executes server-side commands only
188 // must be called only from within server
189 // -1 error, program must exit with error code -1
190 // 0 proceed normally as nothing happened
191 // 1 no error, but program must exit with error code 0
192 // 2 don't load playlist on startup
193 // when executed in remote server -- error code will be ignored
194 int
server_exec_command_line(const char * cmdline,int len,char * sendback,int sbsize)195 server_exec_command_line (const char *cmdline, int len, char *sendback, int sbsize) {
196 if (sendback) {
197 sendback[0] = 0;
198 }
199 const uint8_t *parg = (const uint8_t *)cmdline;
200 const uint8_t *pend = cmdline + len;
201 int queue = 0;
202 while (parg < pend) {
203 const char *parg_c = parg;
204 if (strlen (parg) >= 2 && parg[0] == '-' && parg[1] != '-') {
205 parg += strlen (parg);
206 parg++;
207 return 0; // running under osx debugger?
208 }
209 else if (!strcmp (parg, "--nowplaying")) {
210 parg += strlen (parg);
211 parg++;
212 if (parg >= pend) {
213 const char *errtext = "--nowplaying expects format argument";
214 if (sendback) {
215 snprintf (sendback, sbsize, "error %s\n", errtext);
216 return 0;
217 }
218 else {
219 fprintf (stderr, "%s\n", errtext);
220 return -1;
221 }
222 }
223 char out[2048];
224 playItem_t *curr = streamer_get_playing_track ();
225 if (curr) {
226 pl_format_title (curr, -1, out, sizeof (out), -1, parg);
227 pl_item_unref (curr);
228 }
229 else {
230 strcpy (out, "nothing");
231 }
232 if (sendback) {
233 snprintf (sendback, sbsize, "nowplaying %s", out);
234 }
235 else {
236 fwrite (out, 1, strlen (out), stdout);
237 return 1; // exit
238 }
239 }
240 else if (!strcmp (parg, "--nowplaying-tf")) {
241 parg += strlen (parg);
242 parg++;
243 if (parg >= pend) {
244 const char *errtext = "--nowplaying expects format argument";
245 if (sendback) {
246 snprintf (sendback, sbsize, "error %s\n", errtext);
247 return 0;
248 }
249 else {
250 fprintf (stderr, "%s\n", errtext);
251 return -1;
252 }
253 }
254 char out[2048];
255 playItem_t *curr = streamer_get_playing_track ();
256 char *script = tf_compile (parg);
257 if (script) {
258 ddb_tf_context_t ctx = {
259 ._size = sizeof (ddb_tf_context_t),
260 .it = (DB_playItem_t *)curr,
261 };
262 tf_eval (&ctx, script, out, sizeof (out));
263 tf_free (script);
264 }
265 else {
266 *out = 0;
267 }
268 if (curr) {
269 pl_item_unref (curr);
270 }
271 if (sendback) {
272 snprintf (sendback, sbsize, "nowplaying %s", out);
273 }
274 else {
275 fwrite (out, 1, strlen (out), stdout);
276 return 1; // exit
277 }
278 }
279 else if (!strcmp (parg, "--next")) {
280 messagepump_push (DB_EV_NEXT, 0, 0, 0);
281 return 0;
282 }
283 else if (!strcmp (parg, "--prev")) {
284 messagepump_push (DB_EV_PREV, 0, 0, 0);
285 return 0;
286 }
287 else if (!strcmp (parg, "--play")) {
288 messagepump_push (DB_EV_PLAY_CURRENT, 0, 0, 0);
289 return 0;
290 }
291 else if (!strcmp (parg, "--stop")) {
292 messagepump_push (DB_EV_STOP, 0, 0, 0);
293 return 0;
294 }
295 else if (!strcmp (parg, "--pause")) {
296 messagepump_push (DB_EV_PAUSE, 0, 0, 0);
297 return 0;
298 }
299 else if (!strcmp (parg, "--toggle-pause")) {
300 messagepump_push (DB_EV_TOGGLE_PAUSE, 0, 0, 0);
301 return 0;
302 }
303 else if (!strcmp (parg, "--play-pause")) {
304 int state = deadbeef->get_output ()->state ();
305 if (state == OUTPUT_STATE_PLAYING) {
306 deadbeef->sendmessage (DB_EV_PAUSE, 0, 0, 0);
307 }
308 else {
309 deadbeef->sendmessage (DB_EV_PLAY_CURRENT, 0, 0, 0);
310 }
311 return 0;
312 }
313 else if (!strcmp (parg, "--random")) {
314 messagepump_push (DB_EV_PLAY_RANDOM, 0, 0, 0);
315 return 0;
316 }
317 else if (!strcmp (parg, "--queue")) {
318 queue = 1;
319 }
320 else if (!strcmp (parg, "--quit")) {
321 messagepump_push (DB_EV_TERMINATE, 0, 0, 0);
322 }
323 else if (!strcmp (parg, "--sm-client-id")) {
324 parg += strlen (parg);
325 parg++;
326 if (parg < pend) {
327 parg += strlen (parg);
328 parg++;
329 }
330 continue;
331 }
332 else if (!strcmp (parg, "--gui")) {
333 // need to skip --gui here, it is handled in the client cmdline
334 parg += strlen (parg);
335 parg++;
336 if (parg >= pend) {
337 break;
338 }
339 parg += strlen (parg);
340 parg++;
341 continue;
342 }
343 else if (parg[0] != '-') {
344 break; // unknown option is filename
345 }
346 parg += strlen (parg);
347 parg++;
348 }
349 if (parg < pend) {
350 if (conf_get_int ("cli_add_to_specific_playlist", 1)) {
351 char str[200];
352 conf_get_str ("cli_add_playlist_name", "Default", str, sizeof (str));
353 int idx = plt_find (str);
354 if (idx < 0) {
355 idx = plt_add (plt_get_count (), str);
356 }
357 if (idx >= 0) {
358 plt_set_curr_idx (idx);
359 }
360 }
361 playlist_t *curr_plt = plt_get_curr ();
362 if (plt_add_files_begin (curr_plt, 0) != 0) {
363 plt_unref (curr_plt);
364 snprintf (sendback, sbsize, "it's not allowed to add files to playlist right now, because another file adding operation is in progress. please try again later.");
365 return 0;
366 }
367 // add files
368 if (!queue) {
369 plt_clear (curr_plt);
370 messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
371 plt_reset_cursor (curr_plt);
372 }
373 while (parg < pend) {
374 char resolved[PATH_MAX];
375 const char *pname;
376 if (realpath (parg, resolved)) {
377 pname = resolved;
378 }
379 else {
380 pname = parg;
381 }
382 if (deadbeef->plt_add_dir2 (0, (ddb_playlist_t*)curr_plt, pname, NULL, NULL) < 0) {
383 if (deadbeef->plt_add_file2 (0, (ddb_playlist_t*)curr_plt, pname, NULL, NULL) < 0) {
384 int ab = 0;
385 playItem_t *it = plt_load2 (0, curr_plt, NULL, pname, &ab, NULL, NULL);
386 if (!it) {
387 fprintf (stderr, "failed to add file or folder %s\n", pname);
388 }
389 }
390 }
391 parg += strlen (parg);
392 parg++;
393 }
394 pl_save_current ();
395 messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
396 plt_add_files_end (curr_plt, 0);
397 plt_unref (curr_plt);
398 if (!queue) {
399 messagepump_push (DB_EV_PLAY_NUM, 0, 0, 0);
400 return 2; // don't reload playlist at startup
401 }
402 }
403 return 0;
404 }
405
406 static struct sockaddr_un srv_local;
407 static struct sockaddr_un srv_remote;
408 static unsigned srv_socket;
409
410 #if USE_ABSTRACT_SOCKET_NAME
411 static char server_id[] = "\0deadbeefplayer";
412 #endif
413
414 int
server_start(void)415 server_start (void) {
416 fprintf (stderr, "server_start\n");
417 srv_socket = socket (AF_UNIX, SOCK_STREAM, 0);
418 int flags;
419 flags = fcntl (srv_socket, F_GETFL,0);
420 if (flags == -1) {
421 perror ("fcntl F_GETFL");
422 return -1;
423 }
424 if (fcntl(srv_socket, F_SETFL, flags | O_NONBLOCK) < 0) {
425 perror ("fcntl F_SETFL");
426 return -1;
427 }
428 memset (&srv_local, 0, sizeof (srv_local));
429 srv_local.sun_family = AF_UNIX;
430
431 #if USE_ABSTRACT_SOCKET_NAME
432 memcpy (srv_local.sun_path, server_id, sizeof (server_id));
433 int len = offsetof(struct sockaddr_un, sun_path) + sizeof (server_id)-1;
434 #else
435 char *socketdirenv = getenv ("DDB_SOCKET_DIR");
436 snprintf (srv_local.sun_path, sizeof (srv_local.sun_path), "%s/socket", socketdirenv ? socketdirenv : dbconfdir);
437 if (unlink(srv_local.sun_path) < 0) {
438 perror ("INFO: unlink socket");
439 }
440 int len = offsetof(struct sockaddr_un, sun_path) + strlen (srv_local.sun_path);
441 #endif
442
443 if (bind(srv_socket, (struct sockaddr *)&srv_local, len) < 0) {
444 perror ("bind");
445 return -1;
446 }
447
448 if (listen(srv_socket, 5) == -1) {
449 perror("listen");
450 return -1;
451 }
452 return 0;
453 }
454
455 void
server_close(void)456 server_close (void) {
457 if (srv_socket) {
458 close (srv_socket);
459 srv_socket = 0;
460 }
461 }
462
463 // Read the whole message till end-of-stream
464 char*
read_entire_message(int sockfd,int * size)465 read_entire_message (int sockfd, int *size) {
466 int bufsize = 4096; // initial buffer size, will expand if
467 // the actual package turns out to be bigger
468 char *buf = (char*) malloc(bufsize);
469 int rdp = 0;
470
471 for (;;) {
472 if (rdp >= bufsize) {
473 int newsize = bufsize * 2;
474 char *newbuf = (char*) malloc(newsize);
475 memcpy(newbuf, buf, rdp);
476 free(buf);
477 buf = newbuf;
478 bufsize = newsize;
479 }
480
481 int rd = recv(sockfd, buf + rdp, bufsize - rdp, 0);
482 if (rd < 0) {
483 if (errno == EAGAIN) {
484 usleep (50000);
485 continue;
486 }
487 free(buf);
488 return NULL;
489 }
490 if (rd == 0) {
491 break;
492 }
493 rdp += rd;
494 }
495
496 *size = rdp;
497 return buf;
498 }
499
500 int
server_update(void)501 server_update (void) {
502 // handle remote stuff
503 int t = sizeof (srv_remote);
504 unsigned s2;
505 s2 = accept(srv_socket, (struct sockaddr *)&srv_remote, &t);
506 if (s2 == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
507 perror("accept");
508 return -1;
509 }
510 else if (s2 != -1) {
511 int size = -1;
512 char *buf = read_entire_message(s2, &size);
513 char sendback[1024] = "";
514 if (size > 0) {
515 if (size == 1 && buf[0] == 0) {
516 // FIXME: that should be called right after activation of gui plugin
517 messagepump_push (DB_EV_ACTIVATED, 0, 0, 0);
518 }
519 else {
520 server_exec_command_line (buf, size, sendback, sizeof (sendback));
521 }
522 }
523 if (sendback[0]) {
524 // send nowplaying back to client
525 send (s2, sendback, strlen (sendback)+1, 0);
526 }
527 else {
528 send (s2, "", 1, 0);
529 }
530 close(s2);
531
532 if (buf) {
533 free(buf);
534 }
535 }
536 return 0;
537 }
538
539 static uintptr_t server_tid;
540 static int server_terminate;
541
542 void
server_loop(void * ctx)543 server_loop (void *ctx) {
544 #ifdef __linux__
545 prctl (PR_SET_NAME, "deadbeef-server", 0, 0, 0, 0);
546 #endif
547 fd_set rds;
548 int ret;
549 struct timeval timeout = {0, 0};
550
551 FD_ZERO(&rds);
552 while (!server_terminate) {
553 FD_SET(srv_socket, &rds);
554 timeout.tv_usec = 500000;
555 if ((ret = select(srv_socket + 1, &rds, NULL, NULL, &timeout)) < 0 && errno != EINTR) {
556 perror("select");
557 exit (-1);
558 }
559 if (ret > 0) {
560 if (server_update () < 0) {
561 messagepump_push (DB_EV_TERMINATE, 0, 0, 0);
562 }
563 }
564 }
565 }
566
567 void
save_resume_state(void)568 save_resume_state (void) {
569 playItem_t *trk = streamer_get_playing_track ();
570 DB_output_t *output = plug_get_output ();
571 float playpos = -1;
572 int playtrack = -1;
573 int playlist = streamer_get_current_playlist ();
574 int paused = (output->state () == OUTPUT_STATE_PAUSED);
575 if (trk && playlist >= 0) {
576 playtrack = str_get_idx_of (trk);
577 playpos = streamer_get_playpos ();
578 pl_item_unref (trk);
579 }
580
581 conf_set_float ("resume.position", playpos);
582 conf_set_int ("resume.track", playtrack);
583 conf_set_int ("resume.playlist", playlist);
584 conf_set_int ("resume.paused", paused);
585 }
586
587 void
player_mainloop(void)588 player_mainloop (void) {
589 for (;;) {
590 uint32_t msg;
591 uintptr_t ctx;
592 uint32_t p1;
593 uint32_t p2;
594 int term = 0;
595 while (messagepump_pop(&msg, &ctx, &p1, &p2) != -1) {
596 // broadcast to all plugins
597 DB_plugin_t **plugs = plug_get_list ();
598 for (int n = 0; plugs[n]; n++) {
599 if (plugs[n]->message) {
600 plugs[n]->message (msg, ctx, p1, p2);
601 }
602 }
603 if (!term) {
604 DB_output_t *output = plug_get_output ();
605 switch (msg) {
606 case DB_EV_REINIT_SOUND:
607 plug_reinit_sound ();
608 streamer_reset (1);
609 conf_save ();
610 break;
611 case DB_EV_TERMINATE:
612 {
613 save_resume_state ();
614
615 playqueue_clear ();
616
617 // stop streaming and playback before unloading plugins
618 DB_output_t *output = plug_get_output ();
619 output->stop ();
620 streamer_free ();
621 output->free ();
622 term = 1;
623 }
624 break;
625 case DB_EV_PLAY_CURRENT:
626 streamer_play_current_track ();
627 break;
628 case DB_EV_PLAY_NUM:
629 playqueue_clear ();
630 streamer_set_nextsong (p1, 4);
631 break;
632 case DB_EV_STOP:
633 streamer_set_nextsong (-2, 0);
634 break;
635 case DB_EV_NEXT:
636 streamer_move_to_nextsong (1);
637 break;
638 case DB_EV_PREV:
639 streamer_move_to_prevsong (1);
640 break;
641 case DB_EV_PAUSE:
642 if (output->state () != OUTPUT_STATE_PAUSED) {
643 output->pause ();
644 messagepump_push (DB_EV_PAUSED, 0, 1, 0);
645 }
646 break;
647 case DB_EV_TOGGLE_PAUSE:
648 if (output->state () == OUTPUT_STATE_PAUSED) {
649 streamer_play_current_track ();
650 }
651 else {
652 output->pause ();
653 messagepump_push (DB_EV_PAUSED, 0, 1, 0);
654 }
655 break;
656 case DB_EV_PLAY_RANDOM:
657 streamer_move_to_randomsong (1);
658 break;
659 case DB_EV_CONFIGCHANGED:
660 conf_save ();
661 streamer_configchanged ();
662 junk_configchanged ();
663 break;
664 case DB_EV_SEEK:
665 streamer_set_seek (p1 / 1000.f);
666 break;
667 }
668 }
669 if (msg >= DB_EV_FIRST && ctx) {
670 messagepump_event_free ((ddb_event_t *)ctx);
671 }
672 }
673 if (term) {
674 return;
675 }
676 messagepump_wait ();
677 }
678 }
679
680 #ifdef __GLIBC__
681 void
sigsegv_handler(int sig)682 sigsegv_handler (int sig) {
683 fprintf (stderr, "Segmentation Fault\n");
684 int j, nptrs;
685 #define SIZE 100
686 void *buffer[100];
687 char **strings;
688
689 nptrs = backtrace(buffer, SIZE);
690 printf("backtrace() returned %d addresses\n", nptrs);
691
692 /* The call
693 * backtrace_symbols_fd(buffer,
694 * nptrs,
695 * STDOUT_FILENO)
696 would produce similar output to the following: */
697
698 strings = backtrace_symbols(buffer, nptrs);
699 if (strings == NULL) {
700 perror("backtrace_symbols");
701 exit(EXIT_FAILURE);
702 }
703
704 for (j = 0; j < nptrs; j++)
705 printf("%s\n", strings[j]);
706
707 free(strings);
708 exit (0);
709 }
710 #endif
711
712 void
restore_resume_state(void)713 restore_resume_state (void) {
714 DB_output_t *output = plug_get_output ();
715 if (conf_get_int ("resume_last_session", 0) && output->state () == OUTPUT_STATE_STOPPED) {
716 int plt = conf_get_int ("resume.playlist", -1);
717 int track = conf_get_int ("resume.track", -1);
718 float pos = conf_get_float ("resume.position", -1);
719 int paused = conf_get_int ("resume.paused", 0);
720 trace ("resume: track %d pos %f playlist %d\n", track, pos, plt);
721 if (plt >= 0 && track >= 0 && pos >= 0) {
722 streamer_lock (); // need to hold streamer thread to make the resume operation atomic
723 streamer_set_current_playlist (plt);
724 streamer_set_nextsong (track, paused ? 2 : 3);
725 streamer_set_seek (pos);
726 streamer_unlock ();
727 }
728 }
729 }
730
731 uintptr_t mainloop_tid;
732
733 DB_plugin_t *
plug_get_gui(void)734 plug_get_gui (void) {
735 struct DB_plugin_s **plugs = plug_get_list ();
736 for (int i = 0; plugs[i]; i++) {
737 if (plugs[i]->type == DB_PLUGIN_GUI) {
738 return plugs[i];
739 }
740 }
741 return NULL;
742 }
743
744 void
main_cleanup_and_quit(void)745 main_cleanup_and_quit (void) {
746 // terminate server and wait for completion
747 if (server_tid) {
748 server_terminate = 1;
749 thread_join (server_tid);
750 server_tid = 0;
751 }
752
753 // save config
754 pl_save_all ();
755 conf_save ();
756
757 // delete legacy session file
758 {
759 char sessfile[1024]; // $HOME/.config/deadbeef/session
760 if (snprintf (sessfile, sizeof (sessfile), "%s/deadbeef/session", confdir) < sizeof (sessfile)) {
761 unlink (sessfile);
762 }
763 }
764
765 // stop receiving messages from outside
766 server_close ();
767
768 // plugins might still hold references to playitems,
769 // and query configuration in background
770 // so unload everything 1st before final cleanup
771 plug_disconnect_all ();
772 plug_unload_all ();
773
774 // at this point we can simply do exit(0), but let's clean up for debugging
775 pl_free (); // may access conf_*
776 conf_free ();
777
778 fprintf (stderr, "messagepump_free\n");
779 messagepump_free ();
780 fprintf (stderr, "plug_cleanup\n");
781 plug_cleanup ();
782
783 fprintf (stderr, "hej-hej!\n");
784 }
785
786 static void
mainloop_thread(void * ctx)787 mainloop_thread (void *ctx) {
788 // this runs until DB_EV_TERMINATE is sent (blocks right here)
789 player_mainloop ();
790
791 // tell the gui thread to finish
792 DB_plugin_t *gui = plug_get_gui ();
793 #if HAVE_COCOAUI
794 main_cleanup_and_quit();
795 #endif
796 if (gui) {
797 gui->stop ();
798 }
799 return;
800 }
801
802 int
main(int argc,char * argv[])803 main (int argc, char *argv[]) {
804 int portable = 0;
805 #if STATICLINK
806 int staticlink = 1;
807 #else
808 int staticlink = 0;
809 #endif
810 #if PORTABLE
811 portable = 1;
812 if (!realpath (argv[0], dbinstalldir)) {
813 strcpy (dbinstalldir, argv[0]);
814 }
815 char *e = strrchr (dbinstalldir, '/');
816 if (e) {
817 *e = 0;
818 }
819 else {
820 fprintf (stderr, "couldn't determine install folder from path %s\n", argv[0]);
821 exit (-1);
822 }
823 #else
824 if (!realpath (argv[0], dbinstalldir)) {
825 strcpy (dbinstalldir, argv[0]);
826 }
827 char *e = strrchr (dbinstalldir, '/');
828 if (e) {
829 *e = 0;
830 struct stat st;
831 char checkpath[PATH_MAX];
832 snprintf (checkpath, sizeof (checkpath), "%s/.ddb_portable", dbinstalldir);
833 if (!stat (checkpath, &st)) {
834 if (S_ISREG (st.st_mode)) {
835 portable = 1;
836 }
837 }
838 }
839 if (!portable) {
840 strcpy (dbinstalldir, PREFIX);
841 }
842 #endif
843
844 #ifdef __GLIBC__
845 signal (SIGSEGV, sigsegv_handler);
846 #endif
847 setlocale (LC_ALL, "");
848 setlocale (LC_NUMERIC, "C");
849 #ifdef ENABLE_NLS
850 // fprintf (stderr, "enabling gettext support: package=" PACKAGE ", dir=" LOCALEDIR "...\n");
851 if (portable) {
852 char localedir[PATH_MAX];
853 snprintf (localedir, sizeof (localedir), "%s/locale", dbinstalldir);
854 bindtextdomain (PACKAGE, localedir);
855 }
856 else {
857 bindtextdomain (PACKAGE, LOCALEDIR);
858 }
859 bind_textdomain_codeset (PACKAGE, "UTF-8");
860 textdomain (PACKAGE);
861 #endif
862
863 fprintf (stderr, "starting deadbeef " VERSION "%s%s\n", staticlink ? " [static]" : "", portable ? " [portable]" : "");
864 srand (time (NULL));
865 #ifdef __linux__
866 prctl (PR_SET_NAME, "deadbeef-main", 0, 0, 0, 0);
867 #endif
868
869 #if PORTABLE_FULL
870 if (snprintf (confdir, sizeof (confdir), "%s/config", dbinstalldir) > sizeof (confdir)) {
871 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
872 return -1;
873 }
874
875 strcpy (dbconfdir, confdir);
876
877 if (snprintf (confdir, sizeof (confdir), "%s/cache", dbcachedir) > sizeof (confdir)) {
878 fprintf (stderr, "fatal: too long cache path %s\n", dbcachedir);
879 return -1;
880 }
881 #else
882 char *homedir = getenv ("HOME");
883 if (!homedir) {
884 fprintf (stderr, "unable to find home directory. stopping.\n");
885 return -1;
886 }
887
888 char *xdg_conf_dir = getenv ("XDG_CONFIG_HOME");
889 if (xdg_conf_dir) {
890 if (snprintf (confdir, sizeof (confdir), "%s", xdg_conf_dir) > sizeof (confdir)) {
891 fprintf (stderr, "fatal: XDG_CONFIG_HOME value is too long: %s\n", xdg_conf_dir);
892 return -1;
893 }
894 }
895 else {
896 if (snprintf (confdir, sizeof (confdir), "%s/" SYS_CONFIG_DIR, homedir) > sizeof (confdir)) {
897 fprintf (stderr, "fatal: HOME value is too long: %s\n", homedir);
898 return -1;
899 }
900 }
901 if (snprintf (dbconfdir, sizeof (dbconfdir), "%s/deadbeef", confdir) > sizeof (dbconfdir)) {
902 fprintf (stderr, "fatal: out of memory while configuring\n");
903 return -1;
904 }
905 mkdir (confdir, 0755);
906
907 const char *xdg_cache = getenv("XDG_CACHE_HOME");
908 const char *cache_root = xdg_cache ? xdg_cache : getenv("HOME");
909 if (snprintf(dbcachedir, sizeof (dbcachedir), xdg_cache ? "%s/deadbeef/" : "%s/.cache/deadbeef/", cache_root) >= sizeof (dbcachedir)) {
910 fprintf (stderr, "fatal: too long cache path %s\n", dbcachedir);
911 return -1;
912 }
913 #endif
914
915
916 if (portable) {
917 if (snprintf (dbdocdir, sizeof (dbdocdir), "%s/doc", dbinstalldir) > sizeof (dbdocdir)) {
918 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
919 return -1;
920 }
921 #ifdef HAVE_COCOAUI
922 char respath[PATH_MAX];
923 cocoautil_get_resources_path (respath, sizeof (respath));
924 if (snprintf (dbplugindir, sizeof (dbplugindir), "%s", respath) > sizeof (dbplugindir)) {
925 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
926 return -1;
927 }
928 #else
929 if (snprintf (dbplugindir, sizeof (dbplugindir), "%s/plugins", dbinstalldir) > sizeof (dbplugindir)) {
930 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
931 return -1;
932 }
933 #endif
934 if (snprintf (dbpixmapdir, sizeof (dbpixmapdir), "%s/pixmaps", dbinstalldir) > sizeof (dbpixmapdir)) {
935 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
936 return -1;
937 }
938 mkdir (dbplugindir, 0755);
939 }
940 else {
941 if (snprintf (dbdocdir, sizeof (dbdocdir), "%s", DOCDIR) > sizeof (dbdocdir)) {
942 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
943 return -1;
944 }
945 char *env_plugin_dir = getenv ("DEADBEEF_PLUGIN_DIR");
946 if (env_plugin_dir) {
947 strncpy (dbplugindir, env_plugin_dir, sizeof(dbplugindir));
948 if (dbplugindir[sizeof(dbplugindir) - 1] != 0) {
949 fprintf (stderr, "fatal: too long plugin path %s\n", env_plugin_dir);
950 return -1;
951 }
952 }
953 else if (snprintf (dbplugindir, sizeof (dbplugindir), "%s/deadbeef", LIBDIR) > sizeof (dbplugindir)) {
954 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
955 return -1;
956 }
957 if (snprintf (dbpixmapdir, sizeof (dbpixmapdir), "%s/share/deadbeef/pixmaps", PREFIX) > sizeof (dbpixmapdir)) {
958 fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
959 return -1;
960 }
961 }
962
963 for (int i = 1; i < argc; i++) {
964 // help, version and nowplaying are executed with any filter
965 if (!strcmp (argv[i], "--help") || !strcmp (argv[i], "-h")) {
966 print_help ();
967 return 0;
968 }
969 else if (!strcmp (argv[i], "--version")) {
970 fprintf (stderr, "DeaDBeeF " VERSION " Copyright © 2009-2016 Alexey Yakovenko\n");
971 return 0;
972 }
973 else if (!strcmp (argv[i], "--gui")) {
974 if (i == argc-1) {
975 break;
976 }
977 i++;
978 strncpy (use_gui_plugin, argv[i], sizeof(use_gui_plugin) - 1);
979 use_gui_plugin[sizeof(use_gui_plugin) - 1] = 0;
980 }
981 }
982
983 trace ("installdir: %s\n", dbinstalldir);
984 trace ("confdir: %s\n", confdir);
985 trace ("docdir: %s\n", dbdocdir);
986 trace ("plugindir: %s\n", dbplugindir);
987 trace ("pixmapdir: %s\n", dbpixmapdir);
988
989 mkdir (dbconfdir, 0755);
990
991 int size = 0;
992 char *cmdline = prepare_command_line (argc, argv, &size);
993
994 // try to connect to remote player
995 int s, len;
996 struct sockaddr_un remote;
997
998 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
999 perror("socket");
1000 exit(1);
1001 }
1002
1003 memset (&remote, 0, sizeof (remote));
1004 remote.sun_family = AF_UNIX;
1005 #if USE_ABSTRACT_SOCKET_NAME
1006 memcpy (remote.sun_path, server_id, sizeof (server_id));
1007 len = offsetof(struct sockaddr_un, sun_path) + sizeof (server_id)-1;
1008 #else
1009 char *socketdirenv = getenv ("DDB_SOCKET_DIR");
1010 snprintf (remote.sun_path, sizeof (remote.sun_path), "%s/socket", socketdirenv ? socketdirenv : dbconfdir);
1011 len = offsetof(struct sockaddr_un, sun_path) + strlen (remote.sun_path);
1012 #endif
1013 if (connect(s, (struct sockaddr *)&remote, len) == 0) {
1014 // pass args to remote and exit
1015 if (send(s, cmdline, size, 0) == -1) {
1016 perror ("send");
1017 exit (-1);
1018 }
1019 // end of message
1020 shutdown(s, SHUT_WR);
1021
1022 int sz = -1;
1023 char *out = read_entire_message(s, &sz);
1024 if (sz == -1) {
1025 fprintf (stderr, "failed to pass args to remote!\n");
1026 exit (-1);
1027 }
1028 else {
1029 // check if that's nowplaying response
1030 const char np[] = "nowplaying ";
1031 const char err[] = "error ";
1032 if (!strncmp (out, np, sizeof (np)-1)) {
1033 const char *prn = &out[sizeof (np)-1];
1034 fwrite (prn, 1, strlen (prn), stdout);
1035 }
1036 else if (!strncmp (out, err, sizeof (err)-1)) {
1037 const char *prn = &out[sizeof (err)-1];
1038 fwrite (prn, 1, strlen (prn), stderr);
1039 }
1040 else if (sz > 0 && out[0]) {
1041 fprintf (stderr, "%s\n", out);
1042 }
1043 }
1044 if (out) {
1045 free (out);
1046 }
1047 close (s);
1048 exit (0);
1049 }
1050 // else {
1051 // perror ("INFO: failed to connect to existing session:");
1052 // }
1053 close(s);
1054
1055 // become a server
1056 if (server_start () < 0) {
1057 exit (-1);
1058 }
1059
1060 // hack: report nowplaying
1061 if (!strcmp (cmdline, "--nowplaying")) {
1062 char nothing[] = "nothing";
1063 fwrite (nothing, 1, sizeof (nothing)-1, stdout);
1064 return 0;
1065 }
1066
1067 pl_init ();
1068 conf_init ();
1069 conf_load (); // required by some plugins at startup
1070
1071 if (use_gui_plugin[0]) {
1072 conf_set_str ("gui_plugin", use_gui_plugin);
1073 }
1074
1075 conf_set_str ("deadbeef_version", VERSION);
1076
1077 volume_set_db (conf_get_float ("playback.volume", 0)); // volume need to be initialized before plugins start
1078
1079 messagepump_init (); // required to push messages while handling commandline
1080 if (plug_load_all ()) { // required to add files to playlist from commandline
1081 exit (-1);
1082 }
1083 pl_load_all ();
1084 plt_set_curr_idx (conf_get_int ("playlist.current", 0));
1085
1086 // execute server commands in local context
1087 int noloadpl = 0;
1088 if (argc > 1) {
1089 int res = server_exec_command_line (cmdline, size, NULL, 0);
1090 // some of the server commands ran on 1st instance should terminate it
1091 if (res == 2) {
1092 noloadpl = 1;
1093 }
1094 else if (res > 0) {
1095 exit (0);
1096 }
1097 else if (res < 0) {
1098 exit (-1);
1099 }
1100 }
1101
1102 free (cmdline);
1103
1104 #if 0
1105 signal (SIGTERM, sigterm_handler);
1106 atexit (atexit_handler); // helps to save in simple cases
1107 #endif
1108
1109 streamer_init ();
1110
1111 plug_connect_all ();
1112 messagepump_push (DB_EV_PLUGINSLOADED, 0, 0, 0);
1113
1114 if (!noloadpl) {
1115 restore_resume_state ();
1116 }
1117
1118 server_tid = thread_start (server_loop, NULL);
1119
1120 mainloop_tid = thread_start (mainloop_thread, NULL);
1121
1122 DB_plugin_t *gui = plug_get_gui ();
1123 if (gui) {
1124 gui->start ();
1125 }
1126
1127 fprintf (stderr, "gui plugin has quit; waiting for mainloop thread to finish\n");
1128 thread_join (mainloop_tid);
1129
1130 main_cleanup_and_quit ();
1131 return 0;
1132 }
1133
1134