1 /*
2 Vimpc
3 Copyright (C) 2010 - 2013 Nathan Sweetman
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 3 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, see <http://www.gnu.org/licenses/>.
17
18 mpdclient.cpp - provides interaction with the music player daemon
19 */
20
21 #include "clientstate.hpp"
22
23 #include "mode/mode.hpp"
24 #include "mpdclient.hpp"
25 #include "assert.hpp"
26 #include "buffers.hpp"
27 #include "events.hpp"
28 #include "screen.hpp"
29 #include "settings.hpp"
30 #include "vimpc.hpp"
31
32 using namespace Mpc;
33
34 // Mpc::Client Implementation
ClientState(Main::Vimpc * vimpc,Main::Settings & settings,Ui::Screen & screen)35 ClientState::ClientState(Main::Vimpc * vimpc, Main::Settings & settings, Ui::Screen & screen) :
36 vimpc_ (vimpc),
37 settings_ (settings),
38 screen_ (screen),
39
40 connected_ (false),
41 hostname_ (""),
42 port_ (0),
43 timeSinceUpdate_ (0),
44 timeSinceSong_ (0),
45
46 volume_ (-1),
47 mute_ (false),
48 updating_ (false),
49 random_ (false),
50 repeat_ (false),
51 single_ (false),
52 consume_ (false),
53 crossfade_ (false),
54 running_ (true),
55 newSong_ (false),
56 scrollingStatus_ (false),
57 crossfadeTime_ (0),
58 elapsed_ (0),
59 titlePos_ (0),
60 waitTime_ (150),
61
62 currentSong_ (NULL),
63 currentSongId_ (-1),
64 totalNumberOfSongs_ (0),
65 currentState_ ("Disconnected"),
66 lastTitleStr_ ("")
67 {
68 Main::Vimpc::EventHandler(Event::Connected, [this] (EventData const & Data)
69 {
70 this->connected_ = true;
71 DisplaySongInformation();
72 });
73
74 Main::Vimpc::EventHandler(Event::Disconnected, [this] (EventData const & Data)
75 {
76 this->connected_ = false;
77 this->volume_ = -1;
78 this->mute_ = false;
79 this->updating_ = false;
80 this->random_ = false;
81 this->repeat_ = false;
82 this->single_ = false;
83 this->consume_ = false;
84 this->crossfade_ = false;
85 this->crossfadeTime_ = 0;
86 this->currentSongId_ = -1;
87 this->currentSongURI_ = "";
88 this->totalNumberOfSongs_ = 0;
89
90 if (currentSong_ != NULL)
91 {
92 mpd_song_free(currentSong_);
93 currentSong_ = NULL;
94 }
95
96 DisplaySongInformation();
97 EventData EData;
98 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
99 });
100
101 Main::Vimpc::EventHandler(Event::ClearDatabase, [this] (EventData const & Data)
102 {
103 DisplaySongInformation();
104 });
105
106 Main::Vimpc::EventHandler(Event::DisplaySongInfo, [this] (EventData const & Data)
107 {
108 DisplaySongInformation();
109 });
110
111 Main::Vimpc::EventHandler(Event::ChangeHost, [this] (EventData const & Data)
112 {
113 this->hostname_ = Data.hostname;
114 this->port_ = Data.port;
115 });
116
117 Main::Vimpc::EventHandler(Event::CurrentSongId, [this] (EventData const & Data)
118 {
119 this->currentSongId_ = Data.id;
120 DisplaySongInformation();
121
122 Main::Vimpc::CreateEvent(Event::Repaint, Data);
123 });
124
125 Main::Vimpc::EventHandler(Event::Elapsed, [this] (EventData const & Data)
126 {
127 this->elapsed_ = Data.value;
128 DisplaySongInformation();
129 });
130
131 Main::Vimpc::EventHandler(Event::CurrentSong, [this] (EventData const & Data)
132 {
133 if (currentSong_ != NULL)
134 {
135 mpd_song_free(currentSong_);
136 currentSong_ = NULL;
137 }
138
139 currentSong_ = Data.currentSong;
140 currentSongURI_ = (currentSong_ != NULL) ? mpd_song_get_uri(currentSong_) : "";
141 DisplaySongInformation();
142
143 Main::Vimpc::CreateEvent(Event::Repaint, Data);
144 });
145
146 Main::Vimpc::EventHandler(Event::Random, [this] (EventData const & Data)
147 {
148 this->random_ = Data.state;
149 EventData EData;
150 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
151 });
152
153 Main::Vimpc::EventHandler(Event::Consume, [this] (EventData const & Data)
154 {
155 this->consume_ = Data.state;
156 EventData EData;
157 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
158 });
159
160 Main::Vimpc::EventHandler(Event::Repeat, [this] (EventData const & Data)
161 {
162 this->repeat_ = Data.state;
163 EventData EData;
164 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
165 });
166
167 Main::Vimpc::EventHandler(Event::Single, [this] (EventData const & Data)
168 {
169 this->single_ = Data.state;
170 EventData EData;
171 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
172 });
173
174 Main::Vimpc::EventHandler(Event::Mute, [this] (EventData const & Data)
175 {
176 this->mute_ = Data.state;
177 EventData EData;
178 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
179 });
180
181 Main::Vimpc::EventHandler(Event::Crossfade, [this] (EventData const & Data)
182 {
183 this->crossfade_ = Data.state;
184 EventData EData;
185 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
186 });
187
188 Main::Vimpc::EventHandler(Event::CrossfadeTime, [this] (EventData const & Data)
189 { this->crossfadeTime_ = Data.value; });
190
191 Main::Vimpc::EventHandler(Event::TotalSongCount, [this] (EventData const & Data)
192 {
193 this->totalNumberOfSongs_ = Data.count;
194 EventData EData;
195 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
196 });
197
198 Main::Vimpc::EventHandler(Event::Update, [this] (EventData const & Data)
199 {
200 this->updating_ = true;
201 EventData EData;
202 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
203 });
204
205 Main::Vimpc::EventHandler(Event::UpdateComplete, [this] (EventData const & Data)
206 {
207 this->updating_ = false;
208 EventData EData;
209 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
210 });
211
212 Main::Vimpc::EventHandler(Event::Volume, [this] (EventData const & Data)
213 {
214 this->volume_ = Data.value;
215 EventData EData;
216 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
217 });
218
219 Main::Vimpc::EventHandler(Event::CurrentState, [this] (EventData const & Data)
220 {
221 this->currentState_ = Data.clientstate;
222 EventData EData;
223 Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
224 });
225
226 updateThread_ = std::thread([this]() {
227 while (this->running_)
228 {
229 std::this_thread::sleep_for(std::chrono::milliseconds(this->waitTime_));
230
231 if (this->newSong_)
232 {
233 this->titlePos_ = 0;
234
235 if (this->scrollingStatus_ == true)
236 {
237 this->waitTime_ = 2500;
238 }
239
240 this->newSong_ = false;
241 }
242 else if (this->CurrentState() != "Stopped")
243 {
244 if (this->scrollingStatus_ == true)
245 {
246 this->titlePos_++;
247 EventData EData;
248 Main::Vimpc::CreateEvent(Event::DisplaySongInfo, EData);
249 }
250 this->waitTime_ = 150;
251 }
252 else
253 {
254 this->titlePos_ = 0;
255 this->waitTime_ = 150;
256 }
257 }
258 });
259 }
260
~ClientState()261 ClientState::~ClientState()
262 {
263 running_ = false;
264 updateThread_.join();
265 }
266
Hostname()267 std::string ClientState::Hostname()
268 {
269 return hostname_;
270 }
271
Port()272 uint16_t ClientState::Port()
273 {
274 return port_;
275 }
276
Connected() const277 bool ClientState::Connected() const
278 {
279 return connected_;
280 }
281
IsSocketFile() const282 bool ClientState::IsSocketFile() const
283 {
284 return ((hostname_.length() > 1) && (hostname_[0] == '/'));
285 }
286
Random() const287 bool ClientState::Random() const
288 {
289 return random_;
290 }
291
Single() const292 bool ClientState::Single() const
293 {
294 return single_;
295 }
296
Consume() const297 bool ClientState::Consume() const
298 {
299 return consume_;
300 }
301
Repeat() const302 bool ClientState::Repeat() const
303 {
304 return repeat_;
305 }
306
Crossfade() const307 int32_t ClientState::Crossfade() const
308 {
309 if (crossfade_ == true)
310 {
311 return crossfadeTime_;
312 }
313
314 return 0;
315 }
316
Volume() const317 int32_t ClientState::Volume() const
318 {
319 return volume_;
320 }
321
322
Mute() const323 bool ClientState::Mute() const
324 {
325 return mute_;
326 }
327
IsUpdating() const328 bool ClientState::IsUpdating() const
329 {
330 return updating_;
331 }
332
CurrentState() const333 std::string ClientState::CurrentState() const
334 {
335 return currentState_;
336 }
337
GetCurrentSongURI() const338 std::string ClientState::GetCurrentSongURI() const
339 {
340 return currentSongURI_;
341 }
342
343
GetCurrentSongPos()344 int32_t ClientState::GetCurrentSongPos()
345 {
346 if (currentState_ != "Stopped")
347 {
348 return currentSongId_;
349 }
350 else
351 {
352 return -1;
353 }
354 }
355
TotalNumberOfSongs()356 uint32_t ClientState::TotalNumberOfSongs()
357 {
358 return totalNumberOfSongs_;
359 }
360
DisplaySongInformation()361 void ClientState::DisplaySongInformation()
362 {
363 static char durationStr[128];
364 static char titleStr[512];
365 static char statusStr[1536];
366
367 screen_.HideCursor();
368
369 if ((Connected() == true) && (CurrentState() != "Stopped"))
370 {
371 if (currentSong_ != NULL)
372 {
373 char const * const cartist = mpd_song_get_tag(currentSong_, MPD_TAG_ARTIST, 0);
374 char const * const ctitle = mpd_song_get_tag(currentSong_, MPD_TAG_TITLE, 0);
375 char const * const curi = mpd_song_get_uri(currentSong_);
376 uint32_t const duration = mpd_song_get_duration(currentSong_);
377 uint32_t const elapsed = elapsed_;
378 uint32_t const remain = (duration > elapsed) ? duration - elapsed : 0;
379 std::string const artist = (cartist != NULL) ? cartist : "Unknown";
380 std::string const title = (ctitle != NULL) ? ctitle : "";
381 std::string const uri = (curi != NULL) ? curi : "";
382
383 if (title != "")
384 {
385 snprintf(titleStr, 512, "%s - %s", artist.c_str(), title.c_str());
386 }
387 else
388 {
389 snprintf(titleStr, 512, "%s", uri.c_str());
390 }
391
392 if (settings_.Get(Setting::TimeRemaining) == false)
393 {
394 snprintf(durationStr, 127, " [%d:%.2d/%d:%.2d]",
395 SecondsToMinutes(elapsed), RemainingSeconds(elapsed),
396 SecondsToMinutes(duration), RemainingSeconds(duration));
397 }
398 else
399 {
400 snprintf(durationStr, 127, " [-%d:%.2d/%d:%.2d]",
401 SecondsToMinutes(remain), RemainingSeconds(remain),
402 SecondsToMinutes(duration), RemainingSeconds(duration));
403 }
404
405 if ((strlen(titleStr) >= screen_.MaxColumns() - 7 - strlen(durationStr)) &&
406 (settings_.Get(Setting::ScrollStatus) == true))
407 {
408 snprintf(statusStr, 1536, "%s | %s", titleStr, titleStr);
409 scrollingStatus_ = true;
410 }
411 else
412 {
413 titlePos_ = 0;
414 scrollingStatus_ = false;
415 snprintf(statusStr, 512, "%s", titleStr);
416 }
417
418 std::string const currentTitle = titleStr;
419
420 if (lastTitleStr_ != currentTitle)
421 {
422 if (scrollingStatus_ == true)
423 {
424 newSong_ = true;
425 }
426
427 titlePos_ = 0;
428 }
429
430 lastTitleStr_ = currentTitle;
431
432 screen_.SetStatusLine("[%5u] %s", GetCurrentSongPos() + 1, &statusStr[titlePos_]);
433
434 screen_.MoveSetStatus(screen_.MaxColumns() - strlen(durationStr), "%s", durationStr);
435 screen_.SetProgress(static_cast<double>(elapsed) / duration);
436
437 if (settings_.Get(Setting::ScrollStatus) == true)
438 {
439 titlePos_ %= (strlen(titleStr) + strlen(" | "));
440 }
441 }
442 else
443 {
444 screen_.SetProgress(0);
445 }
446 }
447 else
448 {
449 screen_.SetStatusLine("%s","");
450 screen_.SetProgress(0);
451 }
452
453 if (settings_.Get(Setting::ProgressBar) == true)
454 {
455 screen_.UpdateProgressWindow();
456 }
457
458 if (screen_.PagerIsVisible() == false)
459 {
460 vimpc_->CurrentMode().Refresh();
461 }
462 }
463
464
TimeSinceUpdate()465 long ClientState::TimeSinceUpdate()
466 {
467 return timeSinceUpdate_;
468 }
469
470 /* vim: set sw=3 ts=3: */
471