1 /***************************************************************************
2 * Copyright (C) 2008-2009 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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. *
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. *
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 ***************************************************************************/
32 #include "media_library.h"
36 #include "playlist_editor.h"
37 #include "search_engine.h"
40 #include "tag_editor.h"
42 using namespace Global
;
45 bool Global::UpdateStatusImmediately
= 0;
46 bool Global::RedrawStatusbar
= 0;
48 std::string
Global::VolumeState
;
50 timeval
Global::Timer
;
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;
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
&)
80 void StatusbarApplyFilterImmediately(const std::wstring
&ws
)
82 static std::wstring cmp
;
85 myScreen
->ApplyFilter(ToString((cmp
= ws
)));
86 myScreen
->RefreshWindow();
91 void LockProgressbar()
93 block_progressbar_update
= 1;
96 void UnlockProgressbar()
98 block_progressbar_update
= 0;
103 if (Config
.statusbar_visibility
)
104 block_statusbar_update
= 1;
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;
118 block_progressbar_update
= 0;
120 if (!Mpd
.isPlaying())
122 Statusbar() << wclrtoeol
;
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
))
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);
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
;
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());
179 Statusbar() << wclrtoeol
;
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
);
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
;
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();
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
;
243 // otherwise just add it to playlist
244 myPlaylist
->Items
->AddOption(**it
);
246 myPlaylist
->Items
->at(pos
).CopyPtr(0);
251 myPlaylist
->ApplyFilter(myPlaylist
->Items
->GetFilter());
252 if (myPlaylist
->Items
->Empty())
253 myPlaylist
->Items
->ShowAll();
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());
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();
309 myLibrary
->Artists
->Clear();
311 if (myPlaylistEditor
->Main())
312 myPlaylistEditor
->Content
->Clear();
313 changed
.DBUpdating
= 1;
315 if (changed
.PlayerState
)
317 switch (Mpd
.GetState())
321 player_state
= "[unknown]";
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;
336 player_state
= Config
.new_design
? "[paused] " : "[Paused] ";
341 WindowTitle("ncmpc++ ver. "VERSION
);
342 if (!block_progressbar_update
)
344 *wFooter
<< Config
.progressbar_color
;
345 mvwhline(wFooter
->Raw(), 0, 0, 0, wFooter
->GetWidth());
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]";
355 changed
.StatusFlags
= 1;
358 player_state
.clear();
362 if (Config
.new_design
)
364 *wHeader
<< XY(0, 1) << fmtBold
<< player_state
<< fmtBoldEnd
;
367 else if (!block_statusbar_update
&& Config
.statusbar_visibility
)
369 *wFooter
<< XY(0, 1);
370 if (player_state
.empty())
371 *wFooter
<< wclrtoeol
;
373 *wFooter
<< fmtBold
<< player_state
<< fmtBoldEnd
;
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
)
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
)
409 tracklength
+= Song::ShowTime(Mpd
.GetTotalTime()-Mpd
.GetElapsedTime());
412 tracklength
= Song::ShowTime(Mpd
.GetElapsedTime());
413 if (Mpd
.GetTotalTime())
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())
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())
456 tracklength
+= IntoStr(Mpd
.GetBitrate());
457 tracklength
+= " kbps]";
460 if (Mpd
.GetTotalTime())
462 if (Config
.display_remaining_time
)
465 tracklength
+= Song::ShowTime(Mpd
.GetTotalTime()-Mpd
.GetElapsedTime());
468 tracklength
+= Song::ShowTime(Mpd
.GetElapsedTime());
470 tracklength
+= MPD::Song::ShowTime(Mpd
.GetTotalTime());
475 tracklength
+= Song::ShowTime(Mpd
.GetElapsedTime());
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());
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
;
504 mpd_repeat
= Mpd
.GetRepeat() ? 'r' : 0;
505 ShowMessage("Repeat mode is %s", !mpd_repeat
? "off" : "on");
509 mpd_random
= Mpd
.GetRandom() ? 'z' : 0;
510 ShowMessage("Random mode is %s", !mpd_random
? "off" : "on");
514 mpd_single
= Mpd
.GetSingle() ? 's' : 0;
515 ShowMessage("Single mode is %s", !mpd_single
? "off" : "on");
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
)
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
: '-';
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
;
567 switch_state
+= mpd_repeat
;
569 switch_state
+= mpd_random
;
571 switch_state
+= mpd_single
;
573 switch_state
+= mpd_consume
;
575 switch_state
+= mpd_crossfade
;
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
));
597 if (changed
.Volume
&& (Config
.header_visibility
|| Config
.new_design
))
599 VolumeState
= Config
.new_design
? " Vol: " : " Volume: ";
600 int volume
= Mpd
.GetVolume();
602 VolumeState
+= "n/a";
605 VolumeState
+= IntoStr(volume
);
608 *wHeader
<< Config
.volume_color
;
609 *wHeader
<< XY(wHeader
->GetWidth()-VolumeState
.length(), 0) << VolumeState
;
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
)))
623 if (changed
.Playlist
|| changed
.Database
|| changed
.PlayerState
|| changed
.SongID
)
624 myScreen
->RefreshWindow();
629 *wFooter
<< XY(0, Config
.statusbar_visibility
) << wclrtoeol
;
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());
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;
658 block_progressbar_update
= 1;
659 wFooter
->GotoXY(0, Config
.statusbar_visibility
);
660 *wFooter
<< fmtBoldEnd
;
662 va_start(list
, format
);
663 wmove(wFooter
->Raw(), Config
.statusbar_visibility
, 0);
664 vw_printw(wFooter
->Raw(), format
, list
);
665 wclrtoeol(wFooter
->Raw());