1 /***************************************************************************
2 * Copyright (C) 2008-2010 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"
35 #include "playlist_editor.h"
36 #include "search_engine.h"
37 #include "sel_items_adder.h"
40 #include "tag_editor.h"
42 using Global::myScreen
;
43 using Global::wFooter
;
45 using Global::wHeader
;
46 using Global::VolumeState
;
48 bool Global::UpdateStatusImmediately
= 0;
49 bool Global::RedrawStatusbar
= 0;
51 std::string
Global::VolumeState
;
53 timeval
Global::Timer
;
57 time_t time_of_statusbar_lock
;
58 int lock_statusbar_delay
= -1;
60 bool block_statusbar_update
= 0;
61 bool block_progressbar_update
= 0;
62 bool allow_statusbar_unlock
= 1;
66 void WindowTitle(const std::string
&status
)
68 if (strcmp(getenv("TERM"), "linux") && Config
.set_window_title
)
69 std::cout
<< "\033]0;" << status
<< "\7";
71 #endif // !USE_PDCURSES
73 void StatusbarMPDCallback()
75 Mpd
.OrderDataFetching();
78 void StatusbarGetStringHelper(const std::wstring
&)
83 void StatusbarApplyFilterImmediately(const std::wstring
&ws
)
85 static std::wstring cmp
;
88 myScreen
->ApplyFilter(ToString((cmp
= ws
)));
89 myScreen
->RefreshWindow();
94 void LockProgressbar()
96 block_progressbar_update
= 1;
99 void UnlockProgressbar()
101 block_progressbar_update
= 0;
106 if (Config
.statusbar_visibility
)
107 block_statusbar_update
= 1;
109 block_progressbar_update
= 1;
110 allow_statusbar_unlock
= 0;
113 void UnlockStatusbar()
115 allow_statusbar_unlock
= 1;
116 if (lock_statusbar_delay
< 0)
118 if (Config
.statusbar_visibility
)
119 block_statusbar_update
= 0;
121 block_progressbar_update
= 0;
123 if (!Mpd
.isPlaying())
125 if (Config
.new_design
)
126 DrawProgressbar(Mpd
.GetElapsedTime(), Mpd
.GetTotalTime());
128 Statusbar() << wclrtoeol
;
133 void TraceMpdStatus()
135 static timeval past
= { 0, 0 };
137 gettimeofday(&Global::Timer
, 0);
138 if (Mpd
.Connected() && (Mpd
.SupportsIdle() || Timer
.tv_sec
> past
.tv_sec
|| Global::UpdateStatusImmediately
))
141 Global::BlockItemListUpdate
= 0;
142 Playlist::BlockUpdate
= 0;
143 Global::UpdateStatusImmediately
= 0;
144 if (!Mpd
.SupportsIdle())
146 gettimeofday(&past
, 0);
148 else if (Config
.display_bitrate
&& Global::Timer
.tv_sec
> past
.tv_sec
&& Mpd
.isPlaying())
150 // ncmpcpp doesn't fetch status constantly if mpd supports
151 // idle mode so current song's bitrate is never updated.
152 // we need to force ncmpcpp to fetch it.
153 Mpd
.OrderDataFetching();
154 gettimeofday(&past
, 0);
160 if (myScreen
->ActiveWindow() == myPlaylist
->Items
161 && Timer
.tv_sec
== myPlaylist
->Timer()+Config
.playlist_disable_highlight_delay
162 && myPlaylist
->Items
->isHighlighted()
163 && Config
.playlist_disable_highlight_delay
)
165 myPlaylist
->Items
->Highlighting(0);
166 myPlaylist
->Items
->Refresh();
169 if (lock_statusbar_delay
> 0)
171 if (Timer
.tv_sec
>= time_of_statusbar_lock
+lock_statusbar_delay
)
173 lock_statusbar_delay
= -1;
175 if (Config
.statusbar_visibility
)
176 block_statusbar_update
= !allow_statusbar_unlock
;
178 block_progressbar_update
= !allow_statusbar_unlock
;
180 if (Mpd
.GetState() != MPD::psPlay
&& !block_statusbar_update
&& !block_progressbar_update
)
182 if (Config
.new_design
)
183 DrawProgressbar(Mpd
.GetElapsedTime(), Mpd
.GetTotalTime());
185 Statusbar() << wclrtoeol
;
192 void NcmpcppErrorCallback(MPD::Connection
*, int errorid
, const char *msg
, void *)
194 if (errorid
== MPD_SERVER_ERROR_PERMISSION
)
196 wFooter
->SetGetStringHelper(0);
197 Statusbar() << "Password: ";
198 Mpd
.SetPassword(wFooter
->GetString(-1, 0, 1));
199 if (Mpd
.SendPassword())
200 ShowMessage("Password accepted!");
201 wFooter
->SetGetStringHelper(StatusbarGetStringHelper
);
204 ShowMessage("%s", msg
);
207 void NcmpcppStatusChanged(MPD::Connection
*, MPD::StatusChanges changed
, void *)
209 static size_t playing_song_scroll_begin
= 0;
210 static size_t first_line_scroll_begin
= 0;
211 static size_t second_line_scroll_begin
= 0;
212 static std::string player_state
;
217 wFooter
->GetXY(sx
, sy
);
219 if (!Playlist::BlockNowPlayingUpdate
)
220 myPlaylist
->NowPlaying
= Mpd
.GetCurrentSongPos();
222 if (changed
.Playlist
)
224 if (!Playlist::BlockUpdate
)
226 if (!(np
= Mpd
.GetCurrentSong()).Empty())
227 WindowTitle(utf_to_locale_cpy(np
.toString(Config
.song_window_title_format
)));
229 bool was_filtered
= myPlaylist
->Items
->isFiltered();
230 myPlaylist
->Items
->ShowAll();
233 size_t playlist_length
= Mpd
.GetPlaylistLength();
234 if (playlist_length
< myPlaylist
->Items
->Size())
235 myPlaylist
->Items
->ResizeList(playlist_length
);
237 Mpd
.GetPlaylistChanges(Mpd
.GetOldPlaylistID(), list
);
238 myPlaylist
->Items
->Reserve(playlist_length
);
239 for (MPD::SongList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
241 int pos
= (*it
)->GetPosition();
242 if (pos
< int(myPlaylist
->Items
->Size()))
244 // if song's already in playlist, replace it with a new one
245 myPlaylist
->Items
->at(pos
) = **it
;
249 // otherwise just add it to playlist
250 myPlaylist
->Items
->AddOption(**it
);
252 myPlaylist
->Items
->at(pos
).CopyPtr(0);
257 myPlaylist
->ApplyFilter(myPlaylist
->Items
->GetFilter());
258 if (myPlaylist
->Items
->Empty())
259 myPlaylist
->Items
->ShowAll();
264 Playlist::ReloadTotalLength
= 1;
265 Playlist::ReloadRemaining
= 1;
267 if (myPlaylist
->Items
->Empty())
269 myPlaylist
->Items
->Reset();
270 myPlaylist
->Items
->Window::Clear();
271 ShowMessage("Cleared playlist!");
274 if (!Global::BlockItemListUpdate
)
276 if (myScreen
== myBrowser
)
278 myBrowser
->UpdateItemList();
280 else if (myScreen
== mySearcher
)
282 mySearcher
->UpdateFoundList();
284 else if (myScreen
== myLibrary
)
286 UpdateSongList(myLibrary
->Songs
);
288 else if (myScreen
== myPlaylistEditor
)
290 UpdateSongList(myPlaylistEditor
->Content
);
294 if (changed
.Database
)
296 if (myBrowser
->Main())
298 if (myScreen
== myBrowser
)
299 myBrowser
->GetDirectory(myBrowser
->CurrentDir());
301 myBrowser
->Main()->Clear();
303 # ifdef HAVE_TAGLIB_H
304 if (myTagEditor
->Main())
306 myTagEditor
->Albums
->Clear();
307 myTagEditor
->Dirs
->Clear();
309 # endif // HAVE_TAGLIB_H
310 if (myLibrary
->Main())
312 if (myLibrary
->Columns() == 2)
313 myLibrary
->Albums
->Clear();
315 myLibrary
->Artists
->Clear();
317 if (myPlaylistEditor
->Main())
318 myPlaylistEditor
->Content
->Clear();
319 changed
.DBUpdating
= 1;
321 if (changed
.PlayerState
)
323 switch (Mpd
.GetState())
327 player_state
= "[unknown]";
333 WindowTitle(utf_to_locale_cpy(np
.toString(Config
.song_window_title_format
)));
334 player_state
= Config
.new_design
? "[playing]" : "Playing: ";
335 Playlist::ReloadRemaining
= 1;
336 if (Mpd
.GetOldState() == MPD::psStop
) // show track info in status immediately
337 changed
.ElapsedTime
= 1;
342 player_state
= Config
.new_design
? "[paused] " : "[Paused] ";
347 WindowTitle("ncmpc++ ver. "VERSION
);
348 if (!block_progressbar_update
)
350 *wFooter
<< Config
.progressbar_color
;
351 mvwhline(wFooter
->Raw(), 0, 0, 0, wFooter
->GetWidth());
354 Playlist::ReloadRemaining
= 1;
355 myPlaylist
->NowPlaying
= -1;
356 if (Config
.new_design
)
358 *wHeader
<< XY(0, 0) << wclrtoeol
<< XY(0, 1) << wclrtoeol
;
359 player_state
= "[stopped]";
361 changed
.StatusFlags
= 1;
364 player_state
.clear();
368 if (Config
.new_design
)
370 *wHeader
<< XY(0, 1) << fmtBold
<< player_state
<< fmtBoldEnd
;
373 else if (!block_statusbar_update
&& Config
.statusbar_visibility
)
375 *wFooter
<< XY(0, 1);
376 if (player_state
.empty())
377 *wFooter
<< wclrtoeol
;
379 *wFooter
<< fmtBold
<< player_state
<< fmtBoldEnd
;
384 if (myPlaylist
->isPlaying())
386 if (!Config
.execute_on_song_change
.empty())
387 system(Config
.execute_on_song_change
.c_str());
389 if (Mpd
.isPlaying() && !(np
= Mpd
.GetCurrentSong()).Empty())
390 WindowTitle(utf_to_locale_cpy(np
.toString(Config
.song_window_title_format
)));
392 if (Config
.autocenter_mode
&& !myPlaylist
->Items
->isFiltered())
393 myPlaylist
->Items
->Highlight(myPlaylist
->NowPlaying
);
395 if (Config
.now_playing_lyrics
&& !Mpd
.GetSingle() && myScreen
== myLyrics
&& Global::myOldScreen
== myPlaylist
)
398 Playlist::ReloadRemaining
= 1;
399 playing_song_scroll_begin
= 0;
400 first_line_scroll_begin
= 0;
401 second_line_scroll_begin
= 0;
403 if (changed
.ElapsedTime
|| changed
.SongID
|| Global::RedrawStatusbar
)
405 if (np
.Empty() && !(np
= Mpd
.GetCurrentSong()).Empty())
406 WindowTitle(utf_to_locale_cpy(np
.toString(Config
.song_window_title_format
)));
407 if (!np
.Empty() && Mpd
.isPlaying())
409 std::string tracklength
;
410 if (Config
.new_design
)
412 if (Config
.display_remaining_time
)
415 tracklength
+= MPD::Song::ShowTime(Mpd
.GetTotalTime()-Mpd
.GetElapsedTime());
418 tracklength
= MPD::Song::ShowTime(Mpd
.GetElapsedTime());
419 if (Mpd
.GetTotalTime())
422 tracklength
+= MPD::Song::ShowTime(Mpd
.GetTotalTime());
424 // bitrate here doesn't look good, but it can be moved somewhere else later
425 if (Config
.display_bitrate
&& Mpd
.GetBitrate())
428 tracklength
+= IntoStr(Mpd
.GetBitrate());
429 tracklength
+= " kbps";
432 basic_buffer
<my_char_t
> first
, second
;
433 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np
.toString(Config
.new_header_first_line
))), first
);
434 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np
.toString(Config
.new_header_second_line
))), second
);
436 size_t first_len
= Window::Length(first
.Str());
437 size_t first_margin
= (std::max(tracklength
.length()+1, VolumeState
.length()))*2;
438 size_t first_start
= first_len
< COLS
-first_margin
? (COLS
-first_len
)/2 : tracklength
.length()+1;
440 size_t second_len
= Window::Length(second
.Str());
441 size_t second_margin
= (std::max(player_state
.length(), size_t(8))+1)*2;
442 size_t second_start
= second_len
< COLS
-second_margin
? (COLS
-second_len
)/2 : player_state
.length()+1;
444 if (!Global::SeekingInProgress
)
445 *wHeader
<< XY(0, 0) << wclrtoeol
<< tracklength
;
446 *wHeader
<< XY(first_start
, 0);
447 first
.Write(*wHeader
, first_line_scroll_begin
, COLS
-tracklength
.length()-VolumeState
.length()-1, U(" ** "));
449 *wHeader
<< XY(0, 1) << wclrtoeol
<< fmtBold
<< player_state
<< fmtBoldEnd
;
450 *wHeader
<< XY(second_start
, 1);
451 second
.Write(*wHeader
, second_line_scroll_begin
, COLS
-player_state
.length()-8-2, U(" ** "));
453 *wHeader
<< XY(wHeader
->GetWidth()-VolumeState
.length(), 0) << Config
.volume_color
<< VolumeState
<< clEnd
;
455 changed
.StatusFlags
= 1;
457 else if (!block_statusbar_update
&& Config
.statusbar_visibility
)
459 if (Config
.display_bitrate
&& Mpd
.GetBitrate())
462 tracklength
+= IntoStr(Mpd
.GetBitrate());
463 tracklength
+= " kbps]";
466 if (Mpd
.GetTotalTime())
468 if (Config
.display_remaining_time
)
471 tracklength
+= MPD::Song::ShowTime(Mpd
.GetTotalTime()-Mpd
.GetElapsedTime());
474 tracklength
+= MPD::Song::ShowTime(Mpd
.GetElapsedTime());
476 tracklength
+= MPD::Song::ShowTime(Mpd
.GetTotalTime());
481 tracklength
+= MPD::Song::ShowTime(Mpd
.GetElapsedTime());
484 basic_buffer
<my_char_t
> np_song
;
485 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np
.toString(Config
.song_status_format
))), np_song
);
486 *wFooter
<< XY(0, 1) << wclrtoeol
<< player_state
<< fmtBoldEnd
;
487 np_song
.Write(*wFooter
, playing_song_scroll_begin
, wFooter
->GetWidth()-player_state
.length()-tracklength
.length(), U(" ** "));
488 *wFooter
<< fmtBold
<< XY(wFooter
->GetWidth()-tracklength
.length(), 1) << tracklength
;
490 if (!block_progressbar_update
)
491 DrawProgressbar(Mpd
.GetElapsedTime(), Mpd
.GetTotalTime());
492 Global::RedrawStatusbar
= 0;
496 if (!block_statusbar_update
&& Config
.statusbar_visibility
)
497 *wFooter
<< XY(0, 1) << wclrtoeol
;
501 static char mpd_repeat
;
502 static char mpd_random
;
503 static char mpd_single
;
504 static char mpd_consume
;
505 static char mpd_crossfade
;
506 static char mpd_db_updating
;
510 mpd_repeat
= Mpd
.GetRepeat() ? 'r' : 0;
511 ShowMessage("Repeat mode is %s", !mpd_repeat
? "off" : "on");
515 mpd_random
= Mpd
.GetRandom() ? 'z' : 0;
516 ShowMessage("Random mode is %s", !mpd_random
? "off" : "on");
520 mpd_single
= Mpd
.GetSingle() ? 's' : 0;
521 ShowMessage("Single mode is %s", !mpd_single
? "off" : "on");
525 mpd_consume
= Mpd
.GetConsume() ? 'c' : 0;
526 ShowMessage("Consume mode is %s", !mpd_consume
? "off" : "on");
528 if (changed
.Crossfade
)
530 int crossfade
= Mpd
.GetCrossfade();
531 mpd_crossfade
= crossfade
? 'x' : 0;
532 ShowMessage("Crossfade set to %d seconds", crossfade
);
534 if (changed
.DBUpdating
)
536 // mpd-0.{14,15} doesn't support idle notification that dbupdate had
537 // finished and nothing changed, so we need to switch it off for them.
538 if (!Mpd
.SupportsIdle() || Mpd
.Version() > 15)
539 mpd_db_updating
= Mpd
.GetDBIsUpdating() ? 'U' : 0;
540 ShowMessage(Mpd
.GetDBIsUpdating() ? "Database update started!" : "Database update finished!");
541 if (changed
.Database
&& myScreen
== mySelectedItemsAdder
)
543 myScreen
->SwitchTo(); // switch to previous screen
544 ShowMessage("Database has changed, you need to select your item(s) once again!");
547 if (changed
.StatusFlags
&& (Config
.header_visibility
|| Config
.new_design
))
549 std::string switch_state
;
551 if (Config
.new_design
)
554 switch_state
+= mpd_repeat
? mpd_repeat
: '-';
555 switch_state
+= mpd_random
? mpd_random
: '-';
556 switch_state
+= mpd_single
? mpd_single
: '-';
557 switch_state
+= mpd_consume
? mpd_consume
: '-';
558 switch_state
+= mpd_crossfade
? mpd_crossfade
: '-';
559 switch_state
+= mpd_db_updating
? mpd_db_updating
: '-';
561 *wHeader
<< XY(COLS
-switch_state
.length(), 1) << fmtBold
<< Config
.state_flags_color
<< switch_state
<< clEnd
<< fmtBoldEnd
;
562 if (Config
.new_design
&& !Config
.header_visibility
) // in this case also draw separator
564 *wHeader
<< fmtBold
<< clBlack
;
565 mvwhline(wHeader
->Raw(), 2, 0, 0, COLS
);
566 *wHeader
<< clEnd
<< fmtBoldEnd
;
573 switch_state
+= mpd_repeat
;
575 switch_state
+= mpd_random
;
577 switch_state
+= mpd_single
;
579 switch_state
+= mpd_consume
;
581 switch_state
+= mpd_crossfade
;
583 switch_state
+= mpd_db_updating
;
585 // this is done by raw ncurses because creating another
586 // window only for handling this is quite silly
587 attrset(A_BOLD
|COLOR_PAIR(Config
.state_line_color
));
588 mvhline(1, 0, 0, COLS
);
589 if (!switch_state
.empty())
591 mvprintw(1, COLS
-switch_state
.length()-3, "[");
592 attroff(COLOR_PAIR(Config
.state_line_color
));
593 attron(COLOR_PAIR(Config
.state_flags_color
));
594 mvprintw(1, COLS
-switch_state
.length()-2, "%s", switch_state
.c_str());
595 attroff(COLOR_PAIR(Config
.state_flags_color
));
596 attron(COLOR_PAIR(Config
.state_line_color
));
597 mvprintw(1, COLS
-2, "]");
599 attroff(A_BOLD
|COLOR_PAIR(Config
.state_line_color
));
603 if (changed
.Volume
&& (Config
.header_visibility
|| Config
.new_design
))
605 VolumeState
= Config
.new_design
? " Vol: " : " Volume: ";
606 int volume
= Mpd
.GetVolume();
608 VolumeState
+= "n/a";
611 VolumeState
+= IntoStr(volume
);
614 *wHeader
<< Config
.volume_color
;
615 *wHeader
<< XY(wHeader
->GetWidth()-VolumeState
.length(), 0) << VolumeState
;
621 # ifdef ENABLE_OUTPUTS
622 myOutputs
->FetchList();
623 # endif // ENABLE_OUTPUTS
625 *wFooter
<< fmtBoldEnd
;
626 wFooter
->GotoXY(sx
, sy
);
627 if (changed
.PlayerState
|| (changed
.ElapsedTime
&& (!Config
.new_design
|| Mpd
.GetState() == MPD::psPlay
)))
629 if (changed
.Playlist
|| changed
.Database
|| changed
.PlayerState
|| changed
.SongID
)
630 myScreen
->RefreshWindow();
635 *wFooter
<< XY(0, Config
.statusbar_visibility
) << wclrtoeol
;
639 void DrawProgressbar(unsigned elapsed
, unsigned time
)
641 unsigned howlong
= time
? wFooter
->GetWidth()*elapsed
/time
: 0;
642 *wFooter
<< fmtBold
<< Config
.progressbar_color
;
643 mvwhline(wFooter
->Raw(), 0, 0, 0, wFooter
->GetWidth());
646 unsigned pb_width
= std::min(size_t(howlong
), wFooter
->GetWidth());
647 for (unsigned i
= 0; i
< pb_width
; ++i
)
648 *wFooter
<< Config
.progressbar
[0];
649 if (howlong
< wFooter
->GetWidth())
650 *wFooter
<< Config
.progressbar
[1];
652 *wFooter
<< clEnd
<< fmtBoldEnd
;
655 void ShowMessage(const char *format
, ...)
657 if (Global::MessagesAllowed
&& allow_statusbar_unlock
)
659 time(&time_of_statusbar_lock
);
660 lock_statusbar_delay
= Config
.message_delay_time
;
661 if (Config
.statusbar_visibility
)
662 block_statusbar_update
= 1;
664 block_progressbar_update
= 1;
665 wFooter
->GotoXY(0, Config
.statusbar_visibility
);
666 *wFooter
<< fmtBoldEnd
;
668 va_start(list
, format
);
669 wmove(wFooter
->Raw(), Config
.statusbar_visibility
, 0);
670 vw_printw(wFooter
->Raw(), format
, list
);
671 wclrtoeol(wFooter
->Raw());