1 /* vi:set ts=8 sts=8 sw=8:
2  *
3  * PMS  <<Practical Music Search>>
4  * Copyright (C) 2006-2010  Kim Tore Jensen
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *
20  * pms.cpp - The PMS main class
21  *
22  */
23 
24 #include "pms.h"
25 
26 #if defined(__FreeBSD__) || defined(__DragonFly__)
27 #include <sys/wait.h>
28 #endif
29 
30 using namespace std;
31 
32 Pms *		pms;
33 
34 
35 /*
36  * 1..2..3..
37  */
main(int argc,char * argv[])38 int main(int argc, char *argv[])
39 {
40 	int		exitcode;
41 
42 	pms = new Pms(argc, argv);
43 	if (!pms)
44 	{
45 		printf("Not enough memory, aborting.\n");
46 		return PMS_EXIT_LOMEM;
47 	}
48 
49 	exitcode = pms->init();
50 	if (exitcode == 0)
51 	{
52 		exitcode = pms->main();
53 	}
54 	delete pms;
55 	return exitcode;
56 }
57 
58 /*
59  * Init
60  */
Pms(int c,char ** v)61 Pms::Pms(int c, char **v)
62 {
63 	argc = c;
64 	argv = v;
65 	disp = NULL;
66 }
67 
68 /*
69  * Unit
70  */
~Pms()71 Pms::~Pms()
72 {
73 }
74 
75 /*
76  * Connection and main loop
77  */
main()78 int			Pms::main()
79 {
80 	string			t_str;
81 	pms_pending_keys	pending = PEND_NONE;
82 	char			pass[512] = "";
83 	bool			statechanged = false;
84 	bool			songchanged = false;
85 	pms_window *		win = NULL;
86 	time_t			timer = 0;
87 
88 	/* Connection */
89 	printf(_("Connecting to host %s, port %ld..."), options->get_string("hostname").c_str(), options->get_long("port"));
90 
91 	if (conn->connect() != 0)
92 	{
93 		printf(_("failed.\n"));
94 		printf("%s\n", conn->errorstr().c_str());
95 
96 		return PMS_EXIT_CANTCONNECT;
97 	}
98 
99 	printf(_("connected.\n"));
100 
101 	/* Password? */
102 	if (options->get_string("password").size() > 0)
103 	{
104 		printf(_("Sending password..."));
105 		if (comm->sendpassword(options->get_string("password")))
106 			printf(_("password accepted.\n"));
107 		else
108 			printf(_("wrong password.\n"));
109 	}
110 
111 	comm->get_available_commands();
112 	if (!(comm->authlevel() & AUTH_READ))
113 	{
114 		printf(_("This mpd server requires a password.\n"));
115 		while(true)
116 		{
117 			printf(_("Password: "));
118 
119 			fgets(pass, 512, stdin) ? 1 : 0; //ternary here is a hack to get rid of a warn_unused_result warning
120 			if (pass[strlen(pass)-1] == '\n')
121 				pass[strlen(pass)-1] = '\0';
122 
123 			options->set_string("password", pass);
124 
125 			comm->sendpassword(pass);
126 			comm->get_available_commands();
127 			if (!(comm->authlevel() & AUTH_READ))
128 				printf(_("Wrong password, try again.\n"));
129 			else
130 				break;
131 		}
132 	}
133 
134 	printf(_("Successfully logged in.\n"));
135 
136 	/* Update lists */
137 	printf(_("Retrieving library and all playlists..."));
138 	comm->update(true);
139 	printf(_("done.\n"));
140 
141 	comm->has_new_library();
142 	comm->has_new_playlist();
143 	printf(_("Sorting library..."));
144 	comm->library()->sort(options->get_string("sort"));
145 	printf(_("done.\n"));
146 
147 	/* Center attention to current song */
148 	comm->library()->gotocurrent();
149 	comm->playlist()->gotocurrent();
150 
151 	_shutdown = false;
152 	if (!disp->init())
153 	{
154 		printf(_("Can't initialize display!\n"));
155 		return PMS_EXIT_NODISPLAY;
156 	}
157 
158 	/* Workaround for buggy ncurses clearing the screen on first getch() */
159 	getch();
160 
161 	/* Set up library and playlist windows */
162 	playlist = disp->create_playlist();
163 	library = disp->create_playlist();
164 //	dirlist = disp->create_directorylist();
165 	if (!playlist || !library)
166 	{
167 		delete disp;
168 		printf(_("Can't initialize windows!\n"));
169 		return PMS_EXIT_NOWINDOWS;
170 	}
171 	playlist->settitle(_("Playlist"));
172 	library->settitle(_("Library"));
173 	playlist->list = comm->playlist();
174 	library->list = comm->library();
175 
176 	resetstatus(-1);
177 	drawstatus();
178 
179 	playlist->set_column_size();
180 	library->set_column_size();
181 
182 	connect_window_list();
183 
184 	/* Focus startup list */
185 	comm->activatelist(comm->playlist());
186 	t_str = options->get_string("startuplist");
187 	if (t_str == "library")
188 	{
189 		comm->activatelist(comm->library());
190 	}
191 	else if (t_str.size() > 0 && t_str != "playlist")
192 	{
193 		comm->activatelist(comm->findplaylist(t_str));
194 	}
195 	disp->activate(disp->findwlist(comm->activelist()));
196 
197 	disp->forcedraw();
198 	disp->refresh();
199 
200 	/*
201 	 * Main loop
202 	 */
203 	do
204 	{
205 		/* Has to have valid connection. */
206 		if (!conn->connected() || !comm->update(false) == -1)
207 		{
208 			if (timer == 0)
209 			{
210 				log(MSG_STATUS, STERR, "Disconnected from mpd: %s", comm->err());
211 			}
212 			if (difftime(time(NULL), timer) >= options->get_long("reconnectdelay"))
213 			{
214 				if (timer != 0)
215 					log(MSG_STATUS, STOK, _("Attempting reconnect..."));
216 
217 				if (conn->connect() != 0)
218 				{
219 					if (timer != 0)
220 						log(MSG_STATUS, STERR, conn->errorstr().c_str());
221 
222 					time(&timer);
223 					continue;
224 				}
225 				else
226 				{
227 					log(MSG_STATUS, STOK, _("Reconnected successfully."));
228 					comm->clearerror();
229 					timer = 0;
230 				}
231 			}
232 		}
233 
234 		/* Get updated info about state and playlists */
235 		if (comm->has_new_library())
236 		{
237 			log(MSG_STATUS, STOK, _("Library updated."));
238 			if (disp->actwin())
239 				disp->actwin()->wantdraw = true;
240 			library->list->sort(options->get_string("sort"));
241 			library->set_column_size();
242 			connect_window_list();
243 		}
244 		if (comm->has_new_playlist())
245 		{
246 			if (disp->actwin())
247 				disp->actwin()->wantdraw = true;
248 			playlist->set_column_size();
249 		}
250 
251 		/* Progress to next song? */
252 		progress_nextsong();
253 
254 		/* Any pending keystrokes? */
255 		if (input->get_keystroke())
256 		{
257 			pending = input->dispatch();
258 			if (pending != PEND_NONE)
259 			{
260 				handle_command(pending);
261 				comm->update(true);
262 			}
263 		}
264 
265 		songchanged = comm->song_changed();
266 		statechanged = comm->state_changed();
267 		if (songchanged)
268 		{
269 			/* Cursor follows playback if song changed */
270 			if (options->get_bool("followplayback"))
271 			{
272 				win = disp->findwlist(comm->activelist());
273 				if (win)
274 				{
275 					setwin(win);
276 					win->gotocurrent();
277 				}
278 			}
279 		}
280 
281 		if (statechanged)
282 		{
283 			/* Shell command when song finishes */
284 			if (options->get_string("onplaylistfinish").size() > 0 && cursong() && cursong()->pos == comm->playlist()->end())
285 			{
286 				/* If a manual stop was issued, don't do anything */
287 				if (comm->status()->state == MPD_STATUS_STATE_STOP && pending != PEND_STOP)
288 				{
289 					/* soak up return value to suppress
290 					 * warning */
291 					int code = system(options->get_string("onplaylistfinish").c_str());
292 				}
293 			}
294 		}
295 
296 
297 		/* Reset status */
298 		if (resetstatus(0) >= options->get_long("resetstatus") || songchanged || statechanged)
299 			drawstatus();
300 
301 		/* Draw XTerm window title */
302 		disp->set_xterm_title();
303 
304 		/* Check out mediator events */
305 		/* FIXME: add these into their appropriate places */
306 		if (mediator->changed("setting.sort"))
307 			comm->library()->sort(options->get_string("sort"));
308 		else if (mediator->changed("setting.ignorecase"))
309 			comm->library()->sort(options->get_string("sort"));
310 		else if (mediator->changed("setting.columns"))
311 			disp->actwin()->set_column_size();
312 		else if (mediator->changed("setting.mouse"))
313 			disp->setmousemask();
314 		else if (mediator->changed("redraw.topbar"))
315 			disp->resized();
316 		else if (mediator->changed("topbarvisible"))
317 			disp->resized();
318 		else if (mediator->changed("topbarborders"))
319 			disp->resized();
320 		else if (mediator->changed("topbarspace"))
321 			disp->resized();
322 		else if (mediator->changed("columnspace"))
323 			disp->resized();
324 		else if (mediator->changed("setting.topbarclear"))
325 		{
326 			if (options->get_bool("topbarclear"))
327 				options->topbar.clear();
328 		}
329 
330 		/* Draw */
331 		disp->topbar->wantdraw = true;
332 		if (mediator->changed("redraw"))
333 			disp->forcedraw();
334 		else
335 			disp->draw();
336 		disp->refresh();
337 
338 	}
339 	while (!_shutdown);
340 
341 	log(MSG_CONSOLE, STOK, _("Shutting down program.\n"));
342 
343 	delete disp;
344 	delete comm;
345 	delete conn;
346 
347 	/* Unclutter the prompt */
348 	printf("\n");
349 
350 	return PMS_EXIT_SUCCESS;
351 }
352 
353 /*
354  * Set up neccessary variables
355  */
init()356 int			Pms::init()
357 {
358 	string			str;
359 	vector<string> *	tok;
360 
361 	int			exitcode = PMS_EXIT_SUCCESS;
362 	char *			host;
363 	char *			port;
364 	char *			password;
365 	const char *		charset = NULL;
366 
367 	/* Internal pointers */
368 	msg = new Message();
369 	mediator = new Mediator();
370 	interface = new Interface();
371 	formatter = new Formatter();
372 
373 	/* Setup locales and internationalization */
374 	setlocale(LC_ALL, "");
375 	setlocale(LC_CTYPE, "");
376 	g_get_charset(&charset);
377 	bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
378 	bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
379 	textdomain(GETTEXT_PACKAGE);
380 
381 	/* Print program header */
382 	printf("%s v%s\n%s\n", PMS_NAME, PACKAGE_VERSION, PMS_COPYRIGHT);
383 
384 	/* Read important environment variables */
385 	host = getenv("MPD_HOST");
386 	port = getenv("MPD_PORT");
387 	password = getenv("MPD_PASSWORD");
388 
389 	/* Set up field types */
390 	fieldtypes = new Fieldtypes();
391 	fieldtypes->add("num", _("#"), FIELD_NUM, 0, NULL);
392 	fieldtypes->add("file", _("Filename"), FIELD_FILE, 0, sort_compare_file);
393 	fieldtypes->add("artist", _("Artist"), FIELD_ARTIST, 0, sort_compare_artist);
394 	fieldtypes->add("artistsort", _("Artist sort name"), FIELD_ARTISTSORT, 0, sort_compare_artistsort);
395 	fieldtypes->add("albumartist", _("Album artist"), FIELD_ALBUMARTIST, 0, sort_compare_albumartist);
396 	fieldtypes->add("albumartistsort", _("Album artist sort name"), FIELD_ALBUMARTISTSORT, 0, sort_compare_albumartistsort);
397 	fieldtypes->add("title", _("Title"), FIELD_TITLE, 0, sort_compare_title);
398 	fieldtypes->add("album", _("Album"), FIELD_ALBUM, 0, sort_compare_album);
399 	fieldtypes->add("track", _("Track"), FIELD_TRACK, 6, sort_compare_track);
400 	fieldtypes->add("trackshort", _("No"), FIELD_TRACKSHORT, 3, sort_compare_track);
401 	fieldtypes->add("length", _("Length"), FIELD_TIME, 7, sort_compare_length);
402 	fieldtypes->add("date", _("Date"), FIELD_DATE, 11, sort_compare_date);
403 	fieldtypes->add("year", _("Year"), FIELD_YEAR, 5, sort_compare_year);
404 	fieldtypes->add("name", _("Name"), FIELD_NAME, 0, sort_compare_name);
405 	fieldtypes->add("genre", _("Genre"), FIELD_GENRE, 0, sort_compare_genre);
406 	fieldtypes->add("composer", _("Composer"), FIELD_COMPOSER, 0, sort_compare_composer);
407 	fieldtypes->add("performer", _("Performer"), FIELD_PERFORMER, 0, sort_compare_performer);
408 	fieldtypes->add("disc", _("Disc"), FIELD_DISC, 5, sort_compare_disc);
409 	fieldtypes->add("comment", _("Comment"), FIELD_COMMENT, 0, sort_compare_comment);
410 
411 	/* Set up default bindings */
412 	if (!init_commandmap())
413 	{
414 		return PMS_EXIT_NOCOMMAND;
415 	}
416 	options = new Options();
417 	init_default_keymap();
418 
419 	/* Our configuration */
420 	config = new Configurator(options, bindings);
421 
422 	/* Some default options */
423 	options->set_string("hostname", (host ? host : "127.0.0.1"));
424 	if (!password && host)
425 	{
426 		tok = splitstr(host, "@");
427 		if (tok->size() == 2)
428 		{
429 			options->set_string("hostname", (*tok)[0]);
430 			options->set_string("password", (*tok)[1]);
431 		}
432 		delete tok;
433 	}
434 	if (options->get_string("password").size() == 0)
435 	{
436 		options->set_string("password", (password ? password : ""));
437 	}
438 	options->set_long("port", (port ? atoi(port) : 6600));
439 
440 	if (options->get_long("port") <= 0 || options->get_long("port") > 65535)
441 	{
442 		printf(_("Error: port number in environment variable MPD_PORT must be from 1-65535\n"));
443 		return PMS_EXIT_BADARGS;
444 	}
445 
446 	/* Parse command-line */
447 	if (parse_args(argc, argv) == false)
448 	{
449 		return PMS_EXIT_BADARGS;
450 	}
451 
452 	if (!config->loadconfigs())
453 		return PMS_EXIT_CONFIGERR;
454 
455 	/* Seed random number generator */
456 	srand(time(NULL));
457 
458 	/* Setup some important stuff */
459 	conn	= new Connection(options->get_string("hostname"), options->get_long("port"), options->get_long("mpd_timeout"));
460 	comm	= new Control(conn);
461 	disp	= new Display(comm);
462 	input	= new Input();
463 	if (!conn || !comm || !disp || !input)
464 		return PMS_EXIT_LOMEM;
465 
466 	/* Initialization finished */
467 	return PMS_EXIT_SUCCESS;
468 }
469 
470 
471 
472 
473 
474 
475 /*
476  * Converts long to string
477  */
tostring(long number)478 string			Pms::tostring(long number)
479 {
480 	ostringstream s;
481 	s << number;
482 	return s.str();
483 }
484 
485 /*
486  * Converts size_t to string
487  */
tostring(size_t number)488 string			Pms::tostring(size_t number)
489 {
490 	ostringstream s;
491 	s << number;
492 	return s.str();
493 }
494 
495 /*
496  * Converts int to string
497  */
tostring(int number)498 string			Pms::tostring(int number)
499 {
500 	ostringstream s;
501 	s << number;
502 	return s.str();
503 }
504 
505 /*
506  * Split a string into tokens
507  */
splitstr(string str,string delimiter)508 vector<string> *	Pms::splitstr(string str, string delimiter)
509 {
510 	vector<string> *	tokens = new vector<string>;
511 
512 	string::size_type last	= str.find_first_not_of(delimiter, 0);
513 	string::size_type pos	= str.find_first_of(delimiter, last);
514 
515 	while (string::npos != pos || string::npos != last)
516 	{
517 		tokens->push_back(str.substr(last, pos - last));
518 		last = str.find_first_not_of(delimiter, pos);
519 		pos = str.find_first_of(delimiter, last);
520 	}
521 
522 	return tokens;
523 }
524 
525 /*
526  * Join tokens into a string
527  */
joinstr(vector<string> * source,vector<string>::iterator start,vector<string>::iterator end,string delimiter)528 string			Pms::joinstr(vector<string> * source, vector<string>::iterator start, vector<string>::iterator end, string delimiter)
529 {
530 	string			dest = "";
531 
532 	while (start != source->end())
533 	{
534 		dest += *start;
535 
536 		if (start == end)
537 			break;
538 
539 		if (++start != end)
540 		{
541 			dest += delimiter;
542 		}
543 	}
544 
545 	return dest;
546 }
547 
548 /*
549  * Formats seconds into the format Dd H:MM:SS.
550  */
timeformat(int seconds)551 string			Pms::timeformat(int seconds)
552 {
553 	static const int	day	= (60 * 60 * 24);
554 	static const int	hour	= (60 * 60);
555 	static const int	minute	= 60;
556 
557 	int		i;
558 	string		s = "";
559 
560 	/* No time */
561 	if (seconds < 0)
562 	{
563 		s = "--:--";
564 		return s;
565 	}
566 
567 	/* days */
568 	if (seconds >= day)
569 	{
570 		i = seconds / day;
571 		s = Pms::tostring(i) + "d ";
572 		seconds %= day;
573 	}
574 
575 	/* hours */
576 	if (seconds >= hour)
577 	{
578 		i = seconds / hour;
579 		s += zeropad(i, 1) + ":";
580 		seconds %= hour;
581 	}
582 
583 	/* minutes */
584 	i = seconds / minute;
585 	s = s + zeropad(i, 2) + ":";
586 	seconds %= minute;
587 
588 	/* seconds */
589 	s += zeropad(seconds, 2);
590 
591 	return s;
592 }
593 
594 /*
595  * Return "song" or "songs" based on plural or not
596  */
pluralformat(unsigned int i)597 string			Pms::pluralformat(unsigned int i)
598 {
599 	if (i == 1)
600 		return _("song");
601 	else
602 		return _("songs");
603 }
604 /*
605  * Pad integer with zeroes up to target length
606  */
zeropad(int i,unsigned int target)607 string			Pms::zeropad(int i, unsigned int target)
608 {
609 	string s;
610 	s = Pms::tostring(i);
611 	while(s.size() < target)
612 		s = '0' + s;
613 	return s;
614 }
615 
616 /*
617  * Replaces % with %%
618  */
formtext(string text)619 string			Pms::formtext(string text)
620 {
621 	string::const_iterator	i;
622 	string			nutext;
623 
624 	i = text.begin();
625 	nutext.clear();
626 
627 	while (i != text.end())
628 	{
629 		nutext += *i;
630 		if (*i == '%')
631 			nutext += *i;
632 		++i;
633 	}
634 
635 	return nutext;
636 }
637 
638 /*
639  * Return true if the terminal supports Unicode
640  */
unicode()641 bool			Pms::unicode()
642 {
643 	const char *		charset = NULL;
644 
645 	g_get_charset(&charset);
646 	return strcmp(charset, "UTF-8") == 0;
647 }
648 
649 
650 
651 
652 
653 
654 
655 
656 
657 
658 /*
659  * Run a shell command
660  *
661  * FIXME: perhaps this command should be within Interface class?
662  * TODO: add %artist% tags through the field pattern parser: meaning %file% -> filename, not % -> filename
663  *	...but current implementation is nice and vim-like
664  */
run_shell(string cmd)665 bool			Pms::run_shell(string cmd)
666 {
667 	string				search;
668 	string				replace;
669 	string::size_type		pos;
670 	int				i;
671 	Songlist *			list;
672 	char				c;
673 
674 	msg->clear();
675 
676 	/*
677 	 * %: path to current song, not enclosed in quotes
678 	 */
679 	if (cursong())
680 	{
681 		search = "%";
682 		replace = options->get_string("libraryroot");
683 		replace += cursong()->file;
684 		pos = 0;
685 		while ((pos = cmd.find(search, pos)) != string::npos)
686 		{
687 			if (pos == 0 || cmd[pos - 1] != '\\')
688 				cmd.replace(pos, search.size(), replace);
689 			pos++;
690 		}
691 	}
692 
693 	/*
694 	 * ##: path to each song in selection (or each song on the current
695 	 * playlist if there is no selection), each enclosed with doublequotes
696 	 * and separated by spaces
697 	 */
698 	list = disp->actwin()->plist();
699 	search = "##";
700 	if (cmd.find(search, 0) != string::npos && list && list->size())
701 	{
702 		replace = "";
703 		for (i = 0; i < list->size(); i++)
704 		{
705 			if (!list->selection.size || list->song(i)->selected)
706 			{
707 				replace += options->get_string("libraryroot");
708 				replace += list->song(i)->file;
709 				replace += "\" \"";
710 			}
711 		}
712 		if (replace.size() > 0)
713 		{
714 			replace = "\"" + replace.substr(0, replace.size() - 2);
715 			pos = 0;
716 			while ((pos = cmd.find(search, pos)) != string::npos)
717 			{
718 				if (pos == 0 || cmd[pos - 1] != '\\')
719 					cmd.replace(pos, search.size(), replace);
720 				pos++;
721 			}
722 		}
723 	}
724 
725 	/*
726 	 * #: path to song the cursor is on, not enclosed in quotes
727 	 */
728 	if (disp->cursorsong())
729 	{
730 		search = "#";
731 		replace = options->get_string("libraryroot");
732 		replace += disp->cursorsong()->file;
733 		pos = 0;
734 		while ((pos = cmd.find(search, pos)) != string::npos)
735 		{
736 			if (pos == 0 || cmd[pos - 1] != '\\')
737 				cmd.replace(pos, search.size(), replace);
738 			pos++;
739 		}
740 	}
741 
742 	//pms->log(MSG_DEBUG, 0, "running shell command '%s'\n", cmd.c_str());
743 	endwin();
744 
745 	msg->code = system(cmd.c_str());
746 	msg->code = WEXITSTATUS(msg->code);
747 
748 	pms->log(MSG_DEBUG, 0, "Shell returned %d\n", msg->code);
749 	if (msg->code != 0)
750 		printf(_("\nShell returned %d\n"), msg->code);
751 
752 	printf(_("\nPress ENTER to continue"));
753 	fflush(stdout);
754 	{
755 		/* soak up return value to suppress warning */
756 		int key = scanf("%c", &c);
757 	}
758 
759 	reset_prog_mode();
760 	refresh();
761 
762 	return true;
763 }
764 
765 /*
766  * Returns the currently playing song
767  */
cursong()768 Song *			Pms::cursong()
769 {
770 	if (!comm) return NULL;
771 	return comm->song();
772 }
773 
774 /*
775  * Reset status to its original state
776  */
drawstatus()777 void			Pms::drawstatus()
778 {
779 	if (input->mode() == INPUT_JUMP)
780 		log(MSG_STATUS, STOK, "/%s", formtext(input->text).c_str());
781 	else if (input->mode() == INPUT_FILTER)
782 		log(MSG_STATUS, STOK, ":g/%s", formtext(input->text).c_str());
783 	else if (input->mode() == INPUT_COMMAND)
784 		log(MSG_STATUS, STOK, ":%s", formtext(input->text).c_str());
785 	else
786 		log(MSG_STATUS, STOK, "%s", playstring().c_str());
787 
788 	resetstatus(-1);
789 }
790 
791 /*
792  * Measures time from last statusbar text
793  */
resetstatus(int set)794 int			Pms::resetstatus(int set)
795 {
796 	static time_t 		stored = time(NULL);
797 	static time_t 		now = time(NULL);
798 
799 	if (set == 1)
800 		time(&stored);
801 	else if (set == -1)
802 		stored = 0;
803 
804 	if (stored == 0)
805 		return 0;
806 
807 	if (time(&now) == -1)
808 		return -1;
809 
810 	return (static_cast<int>(difftime(now, stored)));
811 }
812 
813 /*
814  * Return a textual description on how song progression works
815  */
playstring()816 string			Pms::playstring()
817 {
818 	string		s;
819 	string		list = "<unknown>";
820 	bool		is_last;
821 
822 	long		playmode = options->get_long("playmode");
823 	long		repeatmode = options->get_long("repeat");
824 
825 	if (!comm->status() || !conn->connected())
826 	{
827 		s = "Not connected.";
828 		return s;
829 	}
830 
831 	if (comm->status()->state == MPD_STATUS_STATE_STOP || !cursong())
832 	{
833 		s = "Stopped.";
834 		return s;
835 	}
836 	else if (comm->status()->state == MPD_STATUS_STATE_PAUSE)
837 	{
838 		s = "Paused...";
839 		return s;
840 	}
841 
842 	if (comm->activelist())
843 		list = comm->activelist()->filename;
844 
845 	if (list.size() == 0)
846 	{
847 		if (comm->activelist() == comm->library())
848 			list = "library";
849 		else if (comm->activelist() == comm->playlist())
850 			list = "playlist";
851 	}
852 
853 	s = "Playing ";
854 
855 	if (playmode == PLAYMODE_MANUAL)
856 	{
857 		s += "this song, then stopping.";
858 		return s;
859 	}
860 
861 	if (repeatmode == REPEAT_ONE)
862 	{
863 		s += "the same song indefinitely.";
864 		return s;
865 	}
866 
867 	is_last = (cursong()->pos == static_cast<int>(comm->playlist()->end()));
868 
869 	if (!is_last && !(comm->activelist() == comm->playlist() && repeatmode == REPEAT_LIST))
870 	{
871 		s += "through playlist, then ";
872 	}
873 
874 	if (playmode == PLAYMODE_RANDOM)
875 	{
876 		s += "random songs from " + list + ".";
877 		return s;
878 	}
879 
880 	if (repeatmode == REPEAT_LIST)
881 	{
882 		s += "songs from " + list + " repeatedly.";
883 		return s;
884 	}
885 
886 	if (repeatmode == REPEAT_NONE)
887 	{
888 		if (comm->activelist() == comm->playlist())
889 		{
890 			if (options->get_bool("followcursor"))
891 			{
892 				if (is_last)
893 					s += "this song, then ";
894 
895 				s += "following cursor.";
896 				return s;
897 			}
898 
899 			if (is_last)
900 				s += "this song, then stopping.";
901 			else
902 				s += "stopping.";
903 
904 			return s;
905 		}
906 		else
907 		{
908 			s += "songs from " + list + ".";
909 			return s;
910 		}
911 	}
912 
913 	return s;
914 }
915 
916 /*
917  * Put an arbitrary message into the message log
918  */
putlog(Message * m)919 void			Pms::putlog(Message * m)
920 {
921 	if (m->code == 0 && m->str.size() == 0)
922 		return;
923 
924 	log(MSG_CONSOLE, m->code, m->str.c_str());
925 }
926 
927 /*
928  * Log a message.
929  * Verbosity levels:
930  *  0 = statusbar
931  *  1 = console
932  *  2 = debug
933  */
log(int verbosity,long code,const char * format,...)934 void			Pms::log(int verbosity, long code, const char * format, ...)
935 {
936 	long		loglines;
937 	va_list		ap;
938 	char		buffer[1024];
939 	char		tbuffer[20];
940 	string		level;
941 	Message *	m;
942 	tm *		timeinfo;
943 	color *		pair;
944 
945 	if (verbosity >= MSG_DEBUG && !pms->options->get_bool("debug"))
946 		return;
947 
948 	m = new Message();
949 	if (m == NULL)
950 		return;
951 
952 	va_start(ap, format);
953 	vsprintf(buffer, format, ap);
954 	va_end(ap);
955 
956 	m->str = buffer;
957 	m->code = code;
958 
959 	if (verbosity == MSG_STATUS)
960 	{
961 		m->str += "\n";
962 
963 		if (code == STOK)
964 			pair = options->colors->status;
965 		else
966 			pair = options->colors->status_error;
967 
968 		disp->statusbar->clear(false, pair);
969 		colprint(disp->statusbar, 0, 0, pair, "%s", buffer);
970 		resetstatus(1);
971 	}
972 
973 	if (verbosity <= MSG_DEBUG && pms->options->get_bool("debug"))
974 	{
975 		timeinfo = localtime(&(m->timestamp));
976 		strftime(tbuffer, 20, "%Y-%m-%d %H:%M:%S", timeinfo);
977 		if (verbosity == MSG_STATUS)
978 			level = "status";
979 		else if (verbosity == MSG_CONSOLE)
980 			level = "console";
981 		else if (verbosity == MSG_DEBUG)
982 			level = "debug";
983 		fprintf(stderr, "%s /%s/ %s", tbuffer, level.c_str(), m->str.c_str());
984 	}
985 
986 	if (!disp && verbosity < MSG_DEBUG)
987 	{
988 		printf("%s", buffer);
989 	}
990 
991 	msglog.push_back(m);
992 	loglines = options->get_long("msg_buffer_size");
993 	if (loglines > 0 && msglog.size() > loglines)
994 		msglog.erase(msglog.begin());
995 }
996 
997 /*
998  * Checks if time is right for song progression, and takes necessary action
999  */
progress_nextsong()1000 bool			Pms::progress_nextsong()
1001 {
1002 	static song_t		lastid = MPD_SONG_NO_ID;
1003 	static Song *		lastcursor = NULL;
1004 	Songlist *		list = NULL;
1005 	unsigned int		remaining;
1006 
1007 	long			repeatmode;
1008 	long			playmode;
1009 
1010 	if (!cursong())		return false;
1011 
1012 	if (comm->status()->state != MPD_STATUS_STATE_PLAY)
1013 		return false;
1014 
1015 	remaining = (comm->status()->time_total - comm->status()->time_elapsed - comm->status()->crossfade);
1016 
1017 	repeatmode = options->get_long("repeat");
1018 	playmode = options->get_long("playmode");
1019 
1020 	/* Too early */
1021 	if (remaining > options->get_long("nextinterval") || lastid == cursong()->id)
1022 		return false;
1023 
1024 	/* No auto-progression, even when in the middle of playlist */
1025 	if (playmode == PLAYMODE_MANUAL)
1026 	{
1027 		if (remaining <= options->get_long("stopdelay"))
1028 		{
1029 			pms->log(MSG_DEBUG, 0, "Manual playmode, stopping playback.\n");
1030 			comm->stop();
1031 			lastid = cursong()->id;
1032 			return true;
1033 		}
1034 		else
1035 		{
1036 			return false;
1037 		}
1038 	}
1039 	/* Defeat desync with server */
1040 	lastid = cursong()->id;
1041 
1042 	/* Normal progression: reached end of playlist */
1043 	if (comm->status()->song == static_cast<int>(playlist->list->end()))
1044 	{
1045 		/* List to play from */
1046 		list = comm->activelist();
1047 		if (!list) return false;
1048 
1049 		if (list == comm->playlist())
1050 		{
1051 			/* Let MPD handle repeating of the playlist itself */
1052 			if (repeatmode == REPEAT_LIST)
1053 				return false;
1054 
1055 			/* Let MPD handle random songs from playlist */
1056 			if (playmode == PLAYMODE_RANDOM)
1057 				return false;
1058 		}
1059 
1060 		pms->log(MSG_DEBUG, 0, "Auto-progressing to next song.\n");
1061 
1062 		/* Playback follows cursor */
1063 		if (options->get_bool("followcursor") && lastcursor != disp->cursorsong() && disp->cursorsong()->file != cursong()->file)
1064 		{
1065 			pms->log(MSG_DEBUG, 0, "Playback follows cursor: last cursor=%p, now cursor=%p.\n", lastcursor, disp->cursorsong());
1066 			lastcursor = disp->cursorsong();
1067 			lastid = comm->add(comm->playlist(), lastcursor);
1068 		}
1069 
1070 		/* Normal song progression */
1071 		lastid = playnext(playmode, false);
1072 	}
1073 
1074 	if (lastcursor == NULL)
1075 	{
1076 		lastcursor = disp->cursorsong();
1077 	}
1078 
1079 	return (lastid != MPD_SONG_NO_ID);
1080 }
1081 
1082 /*
1083  * Create new windows for each custom playlist
1084  */
connect_window_list()1085 bool			Pms::connect_window_list()
1086 {
1087 	bool				ok = true;
1088 	pms_window *			win;
1089 	vector<Songlist *>::iterator	i;
1090 
1091 	i = comm->playlists.begin();
1092 	while (i != comm->playlists.end())
1093 	{
1094 		if (disp->findwlist(*i) == NULL)
1095 		{
1096 			win = disp->create_playlist();
1097 			if (win)
1098 				win->setplist(*i);
1099 			else
1100 				ok = false;
1101 		}
1102 		++i;
1103 	}
1104 
1105 	return ok;
1106 }
1107 
1108 /*
1109  * Default key bindings
1110  */
init_default_keymap()1111 void			Pms::init_default_keymap()
1112 {
1113 	bindings->clear();
1114 
1115 	/* Movement */
1116 	bindings->add("up", "move-up");
1117 	bindings->add("down", "move-down");
1118 	bindings->add("pageup", "move-pgup");
1119 	bindings->add("pagedown", "move-pgdn");
1120 	bindings->add("^B", "move-pgup");
1121 	bindings->add("^F", "move-pgdn");
1122 	bindings->add("^U", "move-halfpgup");
1123 	bindings->add("^D", "move-halfpgdn");
1124 	bindings->add("^Y", "scroll-up");
1125 	bindings->add("^E", "scroll-down");
1126 	bindings->add("z", "center-cursor");
1127 	bindings->add("home", "move-home");
1128 	bindings->add("end", "move-end");
1129 	bindings->add("g", "goto-current");
1130 	bindings->add("R", "goto-random");
1131 	bindings->add("j", "move-down");
1132 	bindings->add("k", "move-up");
1133 	bindings->add("t", "prev-window");
1134 	bindings->add("T", "next-window");
1135 	bindings->add("(", "prev-of album");
1136 	bindings->add(")", "next-of album");
1137 	bindings->add("{", "prev-of artist");
1138 	bindings->add("}", "next-of artist");
1139 	bindings->add("1", "change-window playlist");
1140 	bindings->add("2", "change-window library");
1141 	bindings->add("w", "change-window windowlist");
1142 	// TODO: add this for a later version
1143 	//bindings->add("W", "change-window directorylist");
1144 	bindings->add("tab", "last-window");
1145 
1146 	/* Searching */
1147 	bindings->add("/", "quick-find");
1148 	bindings->add("n", "next-result");
1149 	bindings->add("N", "prev-result");
1150 
1151 	/* Playlist management */
1152 	bindings->add("a", "add");
1153 	bindings->add("A", "add-to");
1154 	bindings->add("b", "add-album");
1155 	bindings->add("B", "play-album");
1156 	bindings->add("delete", "remove");
1157 	bindings->add("c", "cropsel");
1158 	bindings->add("C", "crop");
1159 	bindings->add("insert", "toggle-select");
1160 	bindings->add("F12", "activate-list");
1161 	bindings->add("^X", "delete-list");
1162 	bindings->add("J", "move 1");
1163 	bindings->add("K", "move -1");
1164 
1165 	/* Controls */
1166 	bindings->add("return", "play");
1167 	bindings->add("kpenter", "play");
1168 	bindings->add("backspace", "stop");
1169 	bindings->add("p", "pause");
1170 	bindings->add("space", "toggle-play");
1171 	bindings->add("l", "next");
1172 	bindings->add("h", "prev");
1173 	bindings->add("M", "mute");
1174 	bindings->add("m", "playmode");
1175 	bindings->add("r", "repeat");
1176 	bindings->add("+", "volume +5");
1177 	bindings->add("-", "volume -5");
1178 	bindings->add("left", "seek -5");
1179 	bindings->add("right", "seek 5");
1180 
1181 	/* Maintenance */
1182 	bindings->add("f", "toggle followcursor");
1183 	bindings->add("F", "toggle followplayback");
1184 	bindings->add("^F", "toggle followwindow");
1185 	bindings->add(":", "command-mode");
1186 	bindings->add("u", "update-library");
1187 	bindings->add("v", "version");
1188 	bindings->add("q", "quit");
1189 	bindings->add("F1", "help");
1190 	bindings->add("^L", "redraw");
1191 }
1192 
1193 
1194 
1195 
1196 
1197 
1198 
1199 
1200 
1201 
1202 /*
1203  * Print the version string
1204  */
print_version()1205 void			Pms::print_version()
1206 {
1207    	printf("Uses libmpdclient (c) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)\n");
1208 	printf("This program is licensed under the GNU General Public License 3.\n");
1209 }
1210 
1211 /*
1212  * Print switch usage
1213  */
print_usage()1214 void			Pms::print_usage()
1215 {
1216 	printf("Usage:\n");
1217 	printf("  -%s\t\t\t%s\n", "v", "print version and exit");
1218 	printf("  -%s\t\t%s\n", "? --help", "display command-line options");
1219 	printf("  -%s\t\t\t%s\n", "d", "turn on debugging to stderr");
1220 	printf("  -%s\t\t%s\n", "c <filename>", "use an alternative config file");
1221 	printf("  -%s\t\t%s\n", "h <hostname>", "connect to this MPD server");
1222 	printf("  -%s\t\t%s\n", "p <port>", "connect to this port");
1223 	printf("  -%s\t\t%s\n", "P <password>", "give this password to MPD server");
1224 }
1225 
1226 /*
1227  * Helper function, prints an error
1228  */
require_arg(char c)1229 bool			Pms::require_arg(char c)
1230 {
1231 	printf("Error: option '%c' requires an argument.\n", c);
1232 	print_usage();
1233 	return false;
1234 }
1235 
1236 /*
1237  * Parse command-line arguments
1238  */
parse_args(int argc,char * argv[])1239 bool			Pms::parse_args(int argc, char * argv[])
1240 {
1241 	int			argn;
1242 	string			value = "";
1243 	string			arg = "";
1244 	bool			switched = false;
1245 	string			s;
1246 	string::iterator	i;
1247 
1248 	if (argc <= 1)
1249 		return true;
1250 
1251 	for (argn = 1; argn < argc; argn++)
1252 	{
1253 		s = argv[argn];
1254 
1255 		if (s == "--help")
1256 		{
1257 			print_usage();
1258 			return false;
1259 		}
1260 
1261 		i = s.begin();
1262 
1263 		while (i != s.end())
1264 		{
1265 			if (!switched)
1266 				if (*i != '-')
1267 					return false;
1268 
1269 			switch (*i)
1270 			{
1271 				case 'd':
1272 					options->set_bool("debug", true);
1273 					break;
1274 				case 'v':
1275 					print_version();
1276 					return false;
1277 				case '?':
1278 					print_usage();
1279 					return false;
1280 				case 'c':
1281 					if (++argn >= argc)
1282 						return require_arg(*i);
1283 					options->set_string("configfile", argv[argn]);
1284 					break;
1285 				case 'h':
1286 					if (++argn >= argc)
1287 						return require_arg(*i);
1288 					options->set_string("hostname", argv[argn]);
1289 					break;
1290 				case 'p':
1291 					if (++argn >= argc)
1292 						return require_arg(*i);
1293 					options->set_long("port", atoi(argv[argn]));
1294 					if (options->get_long("port") <= 0 || options->get_long("port") > 65535)
1295 					{
1296 						printf(_("Error: port number must be from 1-65535\n"));
1297 						return false;
1298 					}
1299 					break;
1300 				case 'P':
1301 					if (++argn >= argc)
1302 						return require_arg(*i);
1303 					options->set_string("password", argv[argn]);
1304 					break;
1305 				case '-':
1306 					if (switched)
1307 					{
1308 						print_usage();
1309 						return false;
1310 					}
1311 					switched = true;
1312 					break;
1313 				default:
1314 					printf(_("Error: unknown option '%c'\n"), *i);
1315 					print_usage();
1316 					return false;
1317 			}
1318 			++i;
1319 		}
1320 
1321 		switched = false;
1322 	}
1323 
1324 	return true;
1325 }
1326 
1327