make MPD::Connection::Toggle() run playback if player is stopped
[ncmpcpp.git] / src / status.cpp
blob1e43652fd52d01e45a85bc1339a80bf07454881c
1 /***************************************************************************
2 * Copyright (C) 2008-2009 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <cstring>
22 #include <sys/time.h>
24 #include <iostream>
25 #include <stdexcept>
27 #include "browser.h"
28 #include "charset.h"
29 #include "global.h"
30 #include "helpers.h"
31 #include "lyrics.h"
32 #include "media_library.h"
33 #include "misc.h"
34 #include "outputs.h"
35 #include "playlist.h"
36 #include "playlist_editor.h"
37 #include "search_engine.h"
38 #include "settings.h"
39 #include "status.h"
40 #include "tag_editor.h"
42 using namespace Global;
43 using namespace MPD;
45 bool Global::UpdateStatusImmediately = 0;
46 bool Global::RedrawStatusbar = 0;
48 std::string Global::VolumeState;
50 timeval Global::Timer;
52 namespace
54 time_t time_of_statusbar_lock;
55 int lock_statusbar_delay = -1;
57 bool block_statusbar_update = 0;
58 bool block_progressbar_update = 0;
59 bool allow_statusbar_unlock = 1;
62 #ifndef USE_PDCURSES
63 void WindowTitle(const std::string &status)
65 if (strcmp(getenv("TERM"), "linux") && Config.set_window_title)
66 std::cout << "\033]0;" << status << "\7";
68 #endif // !USE_PDCURSES
70 void StatusbarMPDCallback()
72 Mpd.OrderDataFetching();
75 void StatusbarGetStringHelper(const std::wstring &)
77 TraceMpdStatus();
80 void StatusbarApplyFilterImmediately(const std::wstring &ws)
82 static std::wstring cmp;
83 if (cmp != ws)
85 myScreen->ApplyFilter(ToString((cmp = ws)));
86 myScreen->RefreshWindow();
88 TraceMpdStatus();
91 void LockProgressbar()
93 block_progressbar_update = 1;
96 void UnlockProgressbar()
98 block_progressbar_update = 0;
101 void LockStatusbar()
103 if (Config.statusbar_visibility)
104 block_statusbar_update = 1;
105 else
106 block_progressbar_update = 1;
107 allow_statusbar_unlock = 0;
110 void UnlockStatusbar()
112 allow_statusbar_unlock = 1;
113 if (lock_statusbar_delay < 0)
115 if (Config.statusbar_visibility)
116 block_statusbar_update = 0;
117 else
118 block_progressbar_update = 0;
120 if (!Mpd.isPlaying())
122 Statusbar() << wclrtoeol;
123 wFooter->Refresh();
127 void TraceMpdStatus()
129 static timeval past = { 0, 0 };
131 gettimeofday(&Global::Timer, 0);
132 if (Mpd.Connected() && (Mpd.SupportsIdle() || Timer.tv_sec > past.tv_sec || UpdateStatusImmediately))
134 Mpd.UpdateStatus();
135 BlockItemListUpdate = 0;
136 Playlist::BlockUpdate = 0;
137 UpdateStatusImmediately = 0;
138 if (!Mpd.SupportsIdle())
140 gettimeofday(&past, 0);
142 else if (Config.display_bitrate && Global::Timer.tv_sec > past.tv_sec && Mpd.isPlaying())
144 // ncmpcpp doesn't fetch status constantly if mpd supports
145 // idle mode so current song's bitrate is never updated.
146 // we need to force ncmpcpp to fetch it.
147 Mpd.OrderDataFetching();
148 gettimeofday(&past, 0);
152 myScreen->Update();
154 if (myScreen->ActiveWindow() == myPlaylist->Items
155 && Timer.tv_sec == myPlaylist->Timer()+Config.playlist_disable_highlight_delay
156 && myPlaylist->Items->isHighlighted()
157 && Config.playlist_disable_highlight_delay)
159 myPlaylist->Items->Highlighting(0);
160 myPlaylist->Items->Refresh();
163 if (lock_statusbar_delay > 0)
165 if (Timer.tv_sec >= time_of_statusbar_lock+lock_statusbar_delay)
167 lock_statusbar_delay = -1;
169 if (Config.statusbar_visibility)
170 block_statusbar_update = !allow_statusbar_unlock;
171 else
172 block_progressbar_update = !allow_statusbar_unlock;
174 if (Mpd.GetState() != psPlay && !block_statusbar_update && !block_progressbar_update)
176 if (Config.new_design)
177 DrawProgressbar(Mpd.GetElapsedTime(), Mpd.GetTotalTime());
178 else
179 Statusbar() << wclrtoeol;
180 wFooter->Refresh();
186 void NcmpcppErrorCallback(Connection *, int errorid, const char *msg, void *)
188 if (errorid == MPD_SERVER_ERROR_PERMISSION)
190 wFooter->SetGetStringHelper(0);
191 Statusbar() << "Password: ";
192 Mpd.SetPassword(wFooter->GetString(-1, 0, 1));
193 if (Mpd.SendPassword())
194 ShowMessage("Password accepted!");
195 wFooter->SetGetStringHelper(StatusbarGetStringHelper);
197 else
198 ShowMessage("%s", msg);
201 void NcmpcppStatusChanged(Connection *, StatusChanges changed, void *)
203 static size_t playing_song_scroll_begin = 0;
204 static size_t first_line_scroll_begin = 0;
205 static size_t second_line_scroll_begin = 0;
206 static std::string player_state;
207 static MPD::Song np;
209 int sx, sy;
210 *wFooter << fmtBold;
211 wFooter->GetXY(sx, sy);
213 if (!Playlist::BlockNowPlayingUpdate)
214 myPlaylist->NowPlaying = Mpd.GetCurrentSongPos();
216 if (changed.Playlist)
218 if (!Playlist::BlockUpdate)
220 if (!(np = Mpd.GetCurrentSong()).Empty())
221 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
223 bool was_filtered = myPlaylist->Items->isFiltered();
224 myPlaylist->Items->ShowAll();
225 SongList list;
227 size_t playlist_length = Mpd.GetPlaylistLength();
228 if (playlist_length < myPlaylist->Items->Size())
229 myPlaylist->Items->ResizeList(playlist_length);
231 Mpd.GetPlaylistChanges(Mpd.GetOldPlaylistID(), list);
232 myPlaylist->Items->Reserve(playlist_length);
233 for (SongList::const_iterator it = list.begin(); it != list.end(); ++it)
235 int pos = (*it)->GetPosition();
236 if (pos < int(myPlaylist->Items->Size()))
238 // if song's already in playlist, replace it with a new one
239 myPlaylist->Items->at(pos) = **it;
241 else
243 // otherwise just add it to playlist
244 myPlaylist->Items->AddOption(**it);
246 myPlaylist->Items->at(pos).CopyPtr(0);
247 (*it)->NullMe();
249 if (was_filtered)
251 myPlaylist->ApplyFilter(myPlaylist->Items->GetFilter());
252 if (myPlaylist->Items->Empty())
253 myPlaylist->Items->ShowAll();
255 FreeSongList(list);
258 Playlist::ReloadTotalLength = 1;
259 Playlist::ReloadRemaining = 1;
261 if (myPlaylist->Items->Empty())
263 myPlaylist->Items->Reset();
264 myPlaylist->Items->Window::Clear();
265 ShowMessage("Cleared playlist!");
268 if (!BlockItemListUpdate)
270 if (myScreen == myBrowser)
272 myBrowser->UpdateItemList();
274 else if (myScreen == mySearcher)
276 mySearcher->UpdateFoundList();
278 else if (myScreen == myLibrary)
280 UpdateSongList(myLibrary->Songs);
282 else if (myScreen == myPlaylistEditor)
284 UpdateSongList(myPlaylistEditor->Content);
288 if (changed.Database)
290 if (myBrowser->Main())
292 if (myScreen == myBrowser)
293 myBrowser->GetDirectory(myBrowser->CurrentDir());
294 else
295 myBrowser->Main()->Clear();
297 # ifdef HAVE_TAGLIB_H
298 if (myTagEditor->Main())
300 myTagEditor->Albums->Clear();
301 myTagEditor->Dirs->Clear();
303 # endif // HAVE_TAGLIB_H
304 if (myLibrary->Main())
306 if (myLibrary->Columns() == 2)
307 myLibrary->Albums->Clear();
308 else
309 myLibrary->Artists->Clear();
311 if (myPlaylistEditor->Main())
312 myPlaylistEditor->Content->Clear();
313 changed.DBUpdating = 1;
315 if (changed.PlayerState)
317 switch (Mpd.GetState())
319 case psUnknown:
321 player_state = "[unknown]";
322 break;
324 case psPlay:
326 if (!np.Empty())
327 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
328 player_state = Config.new_design ? "[playing]" : "Playing: ";
329 Playlist::ReloadRemaining = 1;
330 if (Mpd.GetOldState() == psStop) // show track info in status immediately
331 changed.ElapsedTime = 1;
332 break;
334 case psPause:
336 player_state = Config.new_design ? "[paused] " : "[Paused] ";
337 break;
339 case psStop:
341 WindowTitle("ncmpc++ ver. "VERSION);
342 if (!block_progressbar_update)
344 *wFooter << Config.progressbar_color;
345 mvwhline(wFooter->Raw(), 0, 0, 0, wFooter->GetWidth());
346 *wFooter << clEnd;
348 Playlist::ReloadRemaining = 1;
349 myPlaylist->NowPlaying = -1;
350 if (Config.new_design)
352 *wHeader << XY(0, 0) << wclrtoeol << XY(0, 1) << wclrtoeol;
353 player_state = "[stopped]";
354 changed.Volume = 1;
355 changed.StatusFlags = 1;
357 else
358 player_state.clear();
359 break;
362 if (Config.new_design)
364 *wHeader << XY(0, 1) << fmtBold << player_state << fmtBoldEnd;
365 wHeader->Refresh();
367 else if (!block_statusbar_update && Config.statusbar_visibility)
369 *wFooter << XY(0, 1);
370 if (player_state.empty())
371 *wFooter << wclrtoeol;
372 else
373 *wFooter << fmtBold << player_state << fmtBoldEnd;
376 if (changed.SongID)
378 if (myPlaylist->isPlaying())
380 if (!Config.execute_on_song_change.empty())
381 system(Config.execute_on_song_change.c_str());
383 if (Mpd.isPlaying() && !(np = Mpd.GetCurrentSong()).Empty())
384 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
386 if (Config.autocenter_mode && !myPlaylist->Items->isFiltered())
387 myPlaylist->Items->Highlight(myPlaylist->NowPlaying);
389 if (Config.now_playing_lyrics && !Mpd.GetSingle() && myScreen == myLyrics && myOldScreen == myPlaylist)
390 Lyrics::Reload = 1;
392 Playlist::ReloadRemaining = 1;
393 playing_song_scroll_begin = 0;
394 first_line_scroll_begin = 0;
395 second_line_scroll_begin = 0;
397 if (changed.ElapsedTime || changed.SongID || RedrawStatusbar)
399 if (np.Empty() && !(np = Mpd.GetCurrentSong()).Empty())
400 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
401 if (!np.Empty() && Mpd.isPlaying())
403 std::string tracklength;
404 if (Config.new_design)
406 if (Config.display_remaining_time)
408 tracklength = "-";
409 tracklength += Song::ShowTime(Mpd.GetTotalTime()-Mpd.GetElapsedTime());
411 else
412 tracklength = Song::ShowTime(Mpd.GetElapsedTime());
413 if (Mpd.GetTotalTime())
415 tracklength += "/";
416 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime());
418 // bitrate here doesn't look good, but it can be moved somewhere else later
419 if (Config.display_bitrate && Mpd.GetBitrate())
421 tracklength += " ";
422 tracklength += IntoStr(Mpd.GetBitrate());
423 tracklength += " kbps";
426 basic_buffer<my_char_t> first, second;
427 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.new_header_first_line))), first);
428 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.new_header_second_line))), second);
430 size_t first_len = Window::Length(first.Str());
431 size_t first_margin = (std::max(tracklength.length()+1, VolumeState.length()))*2;
432 size_t first_start = first_len < COLS-first_margin ? (COLS-first_len)/2 : tracklength.length()+1;
434 size_t second_len = Window::Length(second.Str());
435 size_t second_margin = (std::max(player_state.length(), size_t(8))+1)*2;
436 size_t second_start = second_len < COLS-second_margin ? (COLS-second_len)/2 : player_state.length()+1;
438 if (!SeekingInProgress)
439 *wHeader << XY(0, 0) << wclrtoeol << tracklength;
440 *wHeader << XY(first_start, 0);
441 first.Write(*wHeader, first_line_scroll_begin, COLS-tracklength.length()-VolumeState.length()-1, U(" ** "));
443 *wHeader << XY(0, 1) << wclrtoeol << fmtBold << player_state << fmtBoldEnd;
444 *wHeader << XY(second_start, 1);
445 second.Write(*wHeader, second_line_scroll_begin, COLS-player_state.length()-8-2, U(" ** "));
447 *wHeader << XY(wHeader->GetWidth()-VolumeState.length(), 0) << Config.volume_color << VolumeState << clEnd;
449 changed.StatusFlags = 1;
451 else if (!block_statusbar_update && Config.statusbar_visibility)
453 if (Config.display_bitrate && Mpd.GetBitrate())
455 tracklength += " [";
456 tracklength += IntoStr(Mpd.GetBitrate());
457 tracklength += " kbps]";
459 tracklength += " [";
460 if (Mpd.GetTotalTime())
462 if (Config.display_remaining_time)
464 tracklength += "-";
465 tracklength += Song::ShowTime(Mpd.GetTotalTime()-Mpd.GetElapsedTime());
467 else
468 tracklength += Song::ShowTime(Mpd.GetElapsedTime());
469 tracklength += "/";
470 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime());
471 tracklength += "]";
473 else
475 tracklength += Song::ShowTime(Mpd.GetElapsedTime());
476 tracklength += "]";
478 basic_buffer<my_char_t> np_song;
479 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.song_status_format))), np_song);
480 *wFooter << XY(0, 1) << wclrtoeol << player_state << fmtBoldEnd;
481 np_song.Write(*wFooter, playing_song_scroll_begin, wFooter->GetWidth()-player_state.length()-tracklength.length(), U(" ** "));
482 *wFooter << fmtBold << XY(wFooter->GetWidth()-tracklength.length(), 1) << tracklength;
484 if (!block_progressbar_update)
485 DrawProgressbar(Mpd.GetElapsedTime(), Mpd.GetTotalTime());
486 RedrawStatusbar = 0;
488 else
490 if (!block_statusbar_update && Config.statusbar_visibility)
491 *wFooter << XY(0, 1) << wclrtoeol;
495 static char mpd_repeat;
496 static char mpd_random;
497 static char mpd_single;
498 static char mpd_consume;
499 static char mpd_crossfade;
500 static char mpd_db_updating;
502 if (changed.Repeat)
504 mpd_repeat = Mpd.GetRepeat() ? 'r' : 0;
505 ShowMessage("Repeat mode is %s", !mpd_repeat ? "off" : "on");
507 if (changed.Random)
509 mpd_random = Mpd.GetRandom() ? 'z' : 0;
510 ShowMessage("Random mode is %s", !mpd_random ? "off" : "on");
512 if (changed.Single)
514 mpd_single = Mpd.GetSingle() ? 's' : 0;
515 ShowMessage("Single mode is %s", !mpd_single ? "off" : "on");
517 if (changed.Consume)
519 mpd_consume = Mpd.GetConsume() ? 'c' : 0;
520 ShowMessage("Consume mode is %s", !mpd_consume ? "off" : "on");
522 if (changed.Crossfade)
524 int crossfade = Mpd.GetCrossfade();
525 mpd_crossfade = crossfade ? 'x' : 0;
526 ShowMessage("Crossfade set to %d seconds", crossfade);
528 if (changed.DBUpdating)
530 // mpd-0.{14,15} doesn't support idle notification that dbupdate had
531 // finished and nothing changed, so we need to switch it off for them.
532 if (Mpd.Version() < 14 || Mpd.Version() > 15)
533 mpd_db_updating = Mpd.GetDBIsUpdating() ? 'U' : 0;
534 ShowMessage(Mpd.GetDBIsUpdating() ? "Database update started!" : "Database update finished!");
535 if (changed.Database && myScreen == mySelectedItemsAdder)
537 myScreen->SwitchTo(); // switch to previous screen
538 ShowMessage("Database has changed, you need to select your item(s) once again!");
541 if (changed.StatusFlags && (Config.header_visibility || Config.new_design))
543 std::string switch_state;
545 if (Config.new_design)
547 switch_state += '[';
548 switch_state += mpd_repeat ? mpd_repeat : '-';
549 switch_state += mpd_random ? mpd_random : '-';
550 switch_state += mpd_single ? mpd_single : '-';
551 switch_state += mpd_consume ? mpd_consume : '-';
552 switch_state += mpd_crossfade ? mpd_crossfade : '-';
553 switch_state += mpd_db_updating ? mpd_db_updating : '-';
554 switch_state += ']';
555 *wHeader << XY(COLS-switch_state.length(), 1) << fmtBold << Config.state_flags_color << switch_state << clEnd << fmtBoldEnd;
556 if (Config.new_design && !Config.header_visibility) // in this case also draw separator
558 *wHeader << fmtBold << clBlack;
559 mvwhline(wHeader->Raw(), 2, 0, 0, COLS);
560 *wHeader << clEnd << fmtBoldEnd;
562 wHeader->Refresh();
564 else
566 if (mpd_repeat)
567 switch_state += mpd_repeat;
568 if (mpd_random)
569 switch_state += mpd_random;
570 if (mpd_single)
571 switch_state += mpd_single;
572 if (mpd_consume)
573 switch_state += mpd_consume;
574 if (mpd_crossfade)
575 switch_state += mpd_crossfade;
576 if (mpd_db_updating)
577 switch_state += mpd_db_updating;
579 // this is done by raw ncurses because creating another
580 // window only for handling this is quite silly
581 attrset(A_BOLD|COLOR_PAIR(Config.state_line_color));
582 mvhline(1, 0, 0, COLS);
583 if (!switch_state.empty())
585 mvprintw(1, COLS-switch_state.length()-3, "[");
586 attroff(COLOR_PAIR(Config.state_line_color));
587 attron(COLOR_PAIR(Config.state_flags_color));
588 mvprintw(1, COLS-switch_state.length()-2, "%s", switch_state.c_str());
589 attroff(COLOR_PAIR(Config.state_flags_color));
590 attron(COLOR_PAIR(Config.state_line_color));
591 mvprintw(1, COLS-2, "]");
593 attroff(A_BOLD|COLOR_PAIR(Config.state_line_color));
594 refresh();
597 if (changed.Volume && (Config.header_visibility || Config.new_design))
599 VolumeState = Config.new_design ? " Vol: " : " Volume: ";
600 int volume = Mpd.GetVolume();
601 if (volume < 0)
602 VolumeState += "n/a";
603 else
605 VolumeState += IntoStr(volume);
606 VolumeState += "%";
608 *wHeader << Config.volume_color;
609 *wHeader << XY(wHeader->GetWidth()-VolumeState.length(), 0) << VolumeState;
610 *wHeader << clEnd;
611 wHeader->Refresh();
613 if (changed.Outputs)
615 # ifdef ENABLE_OUTPUTS
616 myOutputs->FetchList();
617 # endif // ENABLE_OUTPUTS
619 *wFooter << fmtBoldEnd;
620 wFooter->GotoXY(sx, sy);
621 if (changed.PlayerState || (changed.ElapsedTime && (!Config.new_design || Mpd.GetState() == psPlay)))
622 wFooter->Refresh();
623 if (changed.Playlist || changed.Database || changed.PlayerState || changed.SongID)
624 myScreen->RefreshWindow();
627 Window &Statusbar()
629 *wFooter << XY(0, Config.statusbar_visibility) << wclrtoeol;
630 return *wFooter;
633 void DrawProgressbar(unsigned elapsed, unsigned time)
635 unsigned howlong = time ? wFooter->GetWidth()*elapsed/time : 0;
636 *wFooter << fmtBold << Config.progressbar_color;
637 mvwhline(wFooter->Raw(), 0, 0, 0, wFooter->GetWidth());
638 if (time)
640 unsigned pb_width = std::min(size_t(howlong), wFooter->GetWidth());
641 for (unsigned i = 0; i < pb_width; ++i)
642 *wFooter << Config.progressbar[0];
643 if (howlong < wFooter->GetWidth())
644 *wFooter << Config.progressbar[1];
646 *wFooter << clEnd << fmtBoldEnd;
649 void ShowMessage(const char *format, ...)
651 if (MessagesAllowed && allow_statusbar_unlock)
653 time(&time_of_statusbar_lock);
654 lock_statusbar_delay = Config.message_delay_time;
655 if (Config.statusbar_visibility)
656 block_statusbar_update = 1;
657 else
658 block_progressbar_update = 1;
659 wFooter->GotoXY(0, Config.statusbar_visibility);
660 *wFooter << fmtBoldEnd;
661 va_list list;
662 va_start(list, format);
663 wmove(wFooter->Raw(), Config.statusbar_visibility, 0);
664 vw_printw(wFooter->Raw(), format, list);
665 wclrtoeol(wFooter->Raw());
666 va_end(list);
667 wFooter->Refresh();