1 /*
2 Copyright (c) 2008-2018
3 Lars-Dominik Braun <lars@6xq.net>
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22 */
23
24 /* functions responding to user's keystrokes */
25
26 #include "config.h"
27
28 #include <string.h>
29 #include <unistd.h>
30 #include <pthread.h>
31 #include <assert.h>
32 #include <string.h>
33
34 #include "ui.h"
35 #include "ui_readline.h"
36 #include "ui_dispatch.h"
37
38 /* standard eventcmd call
39 */
40 #define BarUiActDefaultEventcmd(name) BarUiStartEventCmd (&app->settings, \
41 name, selStation, selSong, &app->player, app->ph.stations, \
42 pRet, wRet)
43
44 /* standard piano call
45 */
46 #define BarUiActDefaultPianoCall(call, arg) BarUiPianoCall (app, \
47 call, arg, &pRet, &wRet)
48
49 /* helper to _really_ skip a song (unlock mutex, quit player)
50 * @param player handle
51 */
BarUiDoSkipSong(player_t * const player)52 static inline void BarUiDoSkipSong (player_t * const player) {
53 assert (player != NULL);
54
55 pthread_mutex_lock (&player->lock);
56 player->doQuit = true;
57 player->doPause = false;
58 pthread_cond_broadcast (&player->cond);
59 pthread_mutex_unlock (&player->lock);
60 pthread_mutex_lock (&player->aoplayLock);
61 pthread_cond_broadcast (&player->aoplayCond);
62 pthread_mutex_unlock (&player->aoplayLock);
63 }
64
65 /* transform station if necessary to allow changes like rename, rate, ...
66 * @param piano handle
67 * @param transform this station
68 * @return 0 = error, 1 = everything went well
69 */
BarTransformIfShared(BarApp_t * app,PianoStation_t * station)70 static int BarTransformIfShared (BarApp_t *app, PianoStation_t *station) {
71 PianoReturn_t pRet;
72 CURLcode wRet;
73
74 assert (station != NULL);
75
76 /* shared stations must be transformed */
77 if (!station->isCreator) {
78 BarUiMsg (&app->settings, MSG_INFO, "Transforming station... ");
79 if (!BarUiPianoCall (app, PIANO_REQUEST_TRANSFORM_STATION, station,
80 &pRet, &wRet)) {
81 return 0;
82 }
83 }
84 return 1;
85 }
86
87 /* print current shortcut configuration
88 */
BarUiActCallback(BarUiActHelp)89 BarUiActCallback(BarUiActHelp) {
90 BarUiMsg (&app->settings, MSG_NONE, "\r");
91 for (size_t i = 0; i < BAR_KS_COUNT; i++) {
92 if (dispatchActions[i].helpText != NULL &&
93 (context & dispatchActions[i].context) == dispatchActions[i].context &&
94 app->settings.keys[i] != BAR_KS_DISABLED) {
95 BarUiMsg (&app->settings, MSG_LIST, "%c %s\n", app->settings.keys[i],
96 dispatchActions[i].helpText);
97 }
98 }
99 }
100
101 /* add more music to current station
102 */
BarUiActCallback(BarUiActAddMusic)103 BarUiActCallback(BarUiActAddMusic) {
104 PianoReturn_t pRet;
105 CURLcode wRet;
106 PianoRequestDataAddSeed_t reqData;
107
108 assert (selStation != NULL);
109
110 reqData.musicId = BarUiSelectMusicId (app, selStation,
111 "Add artist or title to station: ");
112 if (reqData.musicId != NULL) {
113 if (!BarTransformIfShared (app, selStation)) {
114 return;
115 }
116 reqData.station = selStation;
117
118 BarUiMsg (&app->settings, MSG_INFO, "Adding music to station... ");
119 BarUiActDefaultPianoCall (PIANO_REQUEST_ADD_SEED, &reqData);
120
121 free (reqData.musicId);
122
123 BarUiActDefaultEventcmd ("stationaddmusic");
124 }
125 }
126
127 /* ban song
128 */
BarUiActCallback(BarUiActBanSong)129 BarUiActCallback(BarUiActBanSong) {
130 PianoReturn_t pRet;
131 CURLcode wRet;
132 PianoStation_t *realStation;
133
134 assert (selStation != NULL);
135 assert (selSong != NULL);
136 assert (selSong->stationId != NULL);
137
138 if ((realStation = PianoFindStationById (app->ph.stations,
139 selSong->stationId)) == NULL) {
140 assert (0);
141 return;
142 }
143 if (!BarTransformIfShared (app, realStation)) {
144 return;
145 }
146
147 PianoRequestDataRateSong_t reqData;
148 reqData.song = selSong;
149 reqData.rating = PIANO_RATE_BAN;
150
151 BarUiMsg (&app->settings, MSG_INFO, "Banning song... ");
152 if (BarUiActDefaultPianoCall (PIANO_REQUEST_RATE_SONG, &reqData) &&
153 selSong == app->playlist) {
154 BarUiDoSkipSong (&app->player);
155 }
156 BarUiActDefaultEventcmd ("songban");
157 }
158
159 /* create new station
160 */
BarUiActCallback(BarUiActCreateStation)161 BarUiActCallback(BarUiActCreateStation) {
162 PianoReturn_t pRet;
163 CURLcode wRet;
164 PianoRequestDataCreateStation_t reqData;
165
166 reqData.type = PIANO_MUSICTYPE_INVALID;
167 reqData.token = BarUiSelectMusicId (app, NULL,
168 "Create station from artist or title: ");
169 if (reqData.token != NULL) {
170 BarUiMsg (&app->settings, MSG_INFO, "Creating station... ");
171 BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
172 free (reqData.token);
173 BarUiActDefaultEventcmd ("stationcreate");
174 }
175 }
176
177 /* create new station
178 */
BarUiActCallback(BarUiActCreateStationFromSong)179 BarUiActCallback(BarUiActCreateStationFromSong) {
180 PianoReturn_t pRet;
181 CURLcode wRet;
182 PianoRequestDataCreateStation_t reqData;
183 char selectBuf[2];
184
185 reqData.token = selSong->trackToken;
186 reqData.type = PIANO_MUSICTYPE_INVALID;
187
188 BarUiMsg (&app->settings, MSG_QUESTION, "Create station from [s]ong or [a]rtist? ");
189 BarReadline (selectBuf, sizeof (selectBuf), "sa", &app->input,
190 BAR_RL_FULLRETURN, -1);
191 switch (selectBuf[0]) {
192 case 's':
193 reqData.type = PIANO_MUSICTYPE_SONG;
194 break;
195
196 case 'a':
197 reqData.type = PIANO_MUSICTYPE_ARTIST;
198 break;
199 }
200 if (reqData.type != PIANO_MUSICTYPE_INVALID) {
201 BarUiMsg (&app->settings, MSG_INFO, "Creating station... ");
202 BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
203 BarUiActDefaultEventcmd ("stationcreate");
204 }
205 }
206
207 /* add shared station by id
208 */
BarUiActCallback(BarUiActAddSharedStation)209 BarUiActCallback(BarUiActAddSharedStation) {
210 PianoReturn_t pRet;
211 CURLcode wRet;
212 char stationId[50];
213 PianoRequestDataCreateStation_t reqData;
214
215 reqData.token = stationId;
216 reqData.type = PIANO_MUSICTYPE_INVALID;
217
218 BarUiMsg (&app->settings, MSG_QUESTION, "Station id: ");
219 if (BarReadline (stationId, sizeof (stationId), "0123456789", &app->input,
220 BAR_RL_DEFAULT, -1) > 0) {
221 BarUiMsg (&app->settings, MSG_INFO, "Adding shared station... ");
222 BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
223 BarUiActDefaultEventcmd ("stationaddshared");
224 }
225 }
226
drainPlaylist(BarApp_t * const app)227 static void drainPlaylist (BarApp_t * const app) {
228 BarUiDoSkipSong (&app->player);
229 if (app->playlist != NULL) {
230 /* drain playlist */
231 PianoDestroyPlaylist (PianoListNextP (app->playlist));
232 app->playlist->head.next = NULL;
233 }
234 }
235
236 /* delete current station
237 */
BarUiActCallback(BarUiActDeleteStation)238 BarUiActCallback(BarUiActDeleteStation) {
239 PianoReturn_t pRet;
240 CURLcode wRet;
241
242 assert (selStation != NULL);
243
244 BarUiMsg (&app->settings, MSG_QUESTION, "Really delete \"%s\"? [yN] ",
245 selStation->name);
246 if (BarReadlineYesNo (false, &app->input)) {
247 BarUiMsg (&app->settings, MSG_INFO, "Deleting station... ");
248 if (BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_STATION,
249 selStation) && selStation == app->curStation) {
250 drainPlaylist (app);
251 app->nextStation = NULL;
252 /* XXX: usually we shoudn’t touch cur*, but DELETE_STATION destroys
253 * station struct */
254 app->curStation = NULL;
255 selStation = NULL;
256 }
257 BarUiActDefaultEventcmd ("stationdelete");
258 }
259 }
260
261 /* explain pandora's song choice
262 */
BarUiActCallback(BarUiActExplain)263 BarUiActCallback(BarUiActExplain) {
264 PianoReturn_t pRet;
265 CURLcode wRet;
266 PianoRequestDataExplain_t reqData;
267
268 assert (selSong != NULL);
269
270 reqData.song = selSong;
271
272 BarUiMsg (&app->settings, MSG_INFO, "Receiving explanation... ");
273 if (BarUiActDefaultPianoCall (PIANO_REQUEST_EXPLAIN, &reqData)) {
274 BarUiMsg (&app->settings, MSG_INFO, "%s\n", reqData.retExplain);
275 free (reqData.retExplain);
276 }
277 BarUiActDefaultEventcmd ("songexplain");
278 }
279
280 /* choose genre station and add it as shared station
281 */
BarUiActCallback(BarUiActStationFromGenre)282 BarUiActCallback(BarUiActStationFromGenre) {
283 PianoReturn_t pRet;
284 CURLcode wRet;
285 const PianoGenreCategory_t *curCat;
286 const PianoGenre_t *curGenre;
287 int i;
288
289 /* receive genre stations list if not yet available */
290 if (app->ph.genreStations == NULL) {
291 BarUiMsg (&app->settings, MSG_INFO, "Receiving genre stations... ");
292 const bool ret = BarUiActDefaultPianoCall (
293 PIANO_REQUEST_GET_GENRE_STATIONS, NULL);
294 BarUiActDefaultEventcmd ("stationfetchgenre");
295 if (!ret) {
296 return;
297 }
298 }
299
300 /* print all available categories */
301 curCat = app->ph.genreStations;
302 i = 0;
303 PianoListForeachP (curCat) {
304 BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curCat->name);
305 i++;
306 }
307
308 do {
309 /* select category or exit */
310 BarUiMsg (&app->settings, MSG_QUESTION, "Select category: ");
311 if (BarReadlineInt (&i, &app->input) == 0) {
312 return;
313 }
314 curCat = PianoListGetP (app->ph.genreStations, i);
315 } while (curCat == NULL);
316
317 /* print all available stations */
318 i = 0;
319 curGenre = curCat->genres;
320 PianoListForeachP (curGenre) {
321 BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curGenre->name);
322 i++;
323 }
324
325 do {
326 BarUiMsg (&app->settings, MSG_QUESTION, "Select genre: ");
327 if (BarReadlineInt (&i, &app->input) == 0) {
328 return;
329 }
330 curGenre = PianoListGetP (curCat->genres, i);
331 } while (curGenre == NULL);
332
333 /* create station */
334 PianoRequestDataCreateStation_t reqData;
335 reqData.token = curGenre->musicId;
336 reqData.type = PIANO_MUSICTYPE_INVALID;
337 BarUiMsg (&app->settings, MSG_INFO, "Adding genre station \"%s\"... ",
338 curGenre->name);
339 BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
340 BarUiActDefaultEventcmd ("stationaddgenre");
341 }
342
343 /* print verbose song information
344 */
BarUiActCallback(BarUiActSongInfo)345 BarUiActCallback(BarUiActSongInfo) {
346 assert (selStation != NULL);
347 assert (selSong != NULL);
348
349 BarUiPrintStation (&app->settings, selStation);
350 /* print real station if quickmix */
351 BarUiPrintSong (&app->settings, selSong,
352 selStation->isQuickMix ?
353 PianoFindStationById (app->ph.stations, selSong->stationId) :
354 NULL);
355 }
356
357 /* print some debugging information
358 */
BarUiActCallback(BarUiActDebug)359 BarUiActCallback(BarUiActDebug) {
360 assert (selSong != NULL);
361
362 /* print debug-alike infos */
363 BarUiMsg (&app->settings, MSG_NONE,
364 "album:\t%s\n"
365 "artist:\t%s\n"
366 "audioFormat:\t%i\n"
367 "audioUrl:\t%s\n"
368 "coverArt:\t%s\n"
369 "detailUrl:\t%s\n"
370 "fileGain:\t%f\n"
371 "musicId:\t%s\n"
372 "rating:\t%i\n"
373 "stationId:\t%s\n"
374 "title:\t%s\n"
375 "trackToken:\t%s\n",
376 selSong->album,
377 selSong->artist,
378 selSong->audioFormat,
379 selSong->audioUrl,
380 selSong->coverArt,
381 selSong->detailUrl,
382 selSong->fileGain,
383 selSong->musicId,
384 selSong->rating,
385 selSong->stationId,
386 selSong->title,
387 selSong->trackToken);
388 }
389
390 /* rate current song
391 */
BarUiActCallback(BarUiActLoveSong)392 BarUiActCallback(BarUiActLoveSong) {
393 PianoReturn_t pRet;
394 CURLcode wRet;
395 PianoStation_t *realStation;
396
397 assert (selStation != NULL);
398 assert (selSong != NULL);
399 assert (selSong->stationId != NULL);
400
401 if ((realStation = PianoFindStationById (app->ph.stations,
402 selSong->stationId)) == NULL) {
403 assert (0);
404 return;
405 }
406 if (!BarTransformIfShared (app, realStation)) {
407 return;
408 }
409
410 PianoRequestDataRateSong_t reqData;
411 reqData.song = selSong;
412 reqData.rating = PIANO_RATE_LOVE;
413
414 BarUiMsg (&app->settings, MSG_INFO, "Loving song... ");
415 BarUiActDefaultPianoCall (PIANO_REQUEST_RATE_SONG, &reqData);
416 BarUiActDefaultEventcmd ("songlove");
417 }
418
419 /* skip song
420 */
BarUiActCallback(BarUiActSkipSong)421 BarUiActCallback(BarUiActSkipSong) {
422 BarUiDoSkipSong (&app->player);
423 }
424
425 /* play
426 */
BarUiActCallback(BarUiActPlay)427 BarUiActCallback(BarUiActPlay) {
428 pthread_mutex_lock (&app->player.lock);
429 app->player.doPause = false;
430 pthread_cond_broadcast (&app->player.cond);
431 pthread_mutex_unlock (&app->player.lock);
432 }
433
434 /* pause
435 */
BarUiActCallback(BarUiActPause)436 BarUiActCallback(BarUiActPause) {
437 pthread_mutex_lock (&app->player.lock);
438 app->player.doPause = true;
439 pthread_cond_broadcast (&app->player.cond);
440 pthread_mutex_unlock (&app->player.lock);
441 }
442
443 /* toggle pause
444 */
BarUiActCallback(BarUiActTogglePause)445 BarUiActCallback(BarUiActTogglePause) {
446 pthread_mutex_lock (&app->player.lock);
447 app->player.doPause = !app->player.doPause;
448 pthread_cond_broadcast (&app->player.cond);
449 pthread_mutex_unlock (&app->player.lock);
450 }
451
452 /* rename current station
453 */
BarUiActCallback(BarUiActRenameStation)454 BarUiActCallback(BarUiActRenameStation) {
455 PianoReturn_t pRet;
456 CURLcode wRet;
457 char lineBuf[100];
458
459 assert (selStation != NULL);
460
461 BarUiMsg (&app->settings, MSG_QUESTION, "New name: ");
462 if (BarReadlineStr (lineBuf, sizeof (lineBuf), &app->input, BAR_RL_DEFAULT) > 0) {
463 PianoRequestDataRenameStation_t reqData;
464 if (!BarTransformIfShared (app, selStation)) {
465 return;
466 }
467
468 reqData.station = selStation;
469 reqData.newName = lineBuf;
470
471 BarUiMsg (&app->settings, MSG_INFO, "Renaming station... ");
472 BarUiActDefaultPianoCall (PIANO_REQUEST_RENAME_STATION, &reqData);
473 BarUiActDefaultEventcmd ("stationrename");
474 }
475 }
476
477 /* play another station
478 */
BarUiActCallback(BarUiActSelectStation)479 BarUiActCallback(BarUiActSelectStation) {
480 PianoStation_t *newStation = BarUiSelectStation (app, app->ph.stations,
481 "Select station: ", NULL, app->settings.autoselect);
482 if (newStation != NULL) {
483 app->nextStation = newStation;
484 drainPlaylist (app);
485 }
486 }
487
488 /* ban song for 1 month
489 */
BarUiActCallback(BarUiActTempBanSong)490 BarUiActCallback(BarUiActTempBanSong) {
491 PianoReturn_t pRet;
492 CURLcode wRet;
493
494 assert (selSong != NULL);
495
496 BarUiMsg (&app->settings, MSG_INFO, "Putting song on shelf... ");
497 if (BarUiActDefaultPianoCall (PIANO_REQUEST_ADD_TIRED_SONG, selSong) &&
498 selSong == app->playlist) {
499 BarUiDoSkipSong (&app->player);
500 }
501 BarUiActDefaultEventcmd ("songshelf");
502 }
503
504 /* print upcoming songs
505 */
BarUiActCallback(BarUiActPrintUpcoming)506 BarUiActCallback(BarUiActPrintUpcoming) {
507 PianoSong_t * const nextSong = PianoListNextP (selSong);
508 if (nextSong != NULL) {
509 BarUiListSongs (app, nextSong, NULL);
510 } else {
511 BarUiMsg (&app->settings, MSG_INFO, "No songs in queue.\n");
512 }
513 }
514
515 /* selectStation callback used by BarUiActSelectQuickMix; toggle, select
516 * all/none
517 */
BarUiActQuickmixCallback(BarApp_t * app,char * buf)518 static void BarUiActQuickmixCallback (BarApp_t *app, char *buf) {
519 PianoStation_t *curStation = app->ph.stations;
520
521 /* do nothing if buf is empty/contains more than one character */
522 if (buf[0] == '\0' || buf[1] != '\0') {
523 return;
524 }
525
526 switch (*buf) {
527 case 't':
528 /* toggle */
529 PianoListForeachP (curStation) {
530 curStation->useQuickMix = !curStation->useQuickMix;
531 }
532 *buf = '\0';
533 break;
534
535 case 'a':
536 /* enable all */
537 PianoListForeachP (curStation) {
538 curStation->useQuickMix = true;
539 }
540 *buf = '\0';
541 break;
542
543 case 'n':
544 /* enable none */
545 PianoListForeachP (curStation) {
546 curStation->useQuickMix = false;
547 }
548 *buf = '\0';
549 break;
550 }
551 }
552
553 /* if current station is a quickmix: select stations that are played in
554 * quickmix
555 */
BarUiActCallback(BarUiActSelectQuickMix)556 BarUiActCallback(BarUiActSelectQuickMix) {
557 PianoReturn_t pRet;
558 CURLcode wRet;
559
560 assert (selStation != NULL);
561
562 if (selStation->isQuickMix) {
563 PianoStation_t *toggleStation;
564 while ((toggleStation = BarUiSelectStation (app, app->ph.stations,
565 "Toggle QuickMix for station: ",
566 BarUiActQuickmixCallback, false)) != NULL) {
567 toggleStation->useQuickMix = !toggleStation->useQuickMix;
568 }
569 BarUiMsg (&app->settings, MSG_INFO, "Setting QuickMix stations... ");
570 BarUiActDefaultPianoCall (PIANO_REQUEST_SET_QUICKMIX, NULL);
571 BarUiActDefaultEventcmd ("stationquickmixtoggle");
572 } else {
573 BarUiMsg (&app->settings, MSG_ERR, "Please select a QuickMix station first.\n");
574 }
575 }
576
577 /* quit
578 */
BarUiActCallback(BarUiActQuit)579 BarUiActCallback(BarUiActQuit) {
580 app->doQuit = true;
581 BarUiDoSkipSong (&app->player);
582 }
583
584 /* song history
585 */
BarUiActCallback(BarUiActHistory)586 BarUiActCallback(BarUiActHistory) {
587 char buf[2];
588 PianoSong_t *histSong;
589
590 if (app->songHistory != NULL) {
591 histSong = BarUiSelectSong (app, app->songHistory,
592 &app->input);
593 if (histSong != NULL) {
594 BarKeyShortcutId_t action;
595 PianoStation_t *songStation = PianoFindStationById (app->ph.stations,
596 histSong->stationId);
597
598 if (songStation == NULL) {
599 BarUiMsg (&app->settings, MSG_ERR, "Station does not exist any more.\n");
600 return;
601 }
602
603 do {
604 action = BAR_KS_COUNT;
605
606 BarUiMsg (&app->settings, MSG_QUESTION, "What to do with this song? ");
607
608 if (BarReadline (buf, sizeof (buf), NULL, &app->input,
609 BAR_RL_FULLRETURN, -1) > 0) {
610 /* actions assume that selStation is the song's original
611 * station */
612 action = BarUiDispatch (app, buf[0], songStation, histSong,
613 false, BAR_DC_UNDEFINED);
614 }
615 } while (action == BAR_KS_HELP);
616 } /* end if histSong != NULL */
617 } else {
618 BarUiMsg (&app->settings, MSG_INFO, (app->settings.history == 0) ? "History disabled.\n" :
619 "No history yet.\n");
620 }
621 }
622
623 /* create song bookmark
624 */
BarUiActCallback(BarUiActBookmark)625 BarUiActCallback(BarUiActBookmark) {
626 PianoReturn_t pRet;
627 CURLcode wRet;
628 char selectBuf[2];
629
630 assert (selSong != NULL);
631
632 BarUiMsg (&app->settings, MSG_QUESTION, "Bookmark [s]ong or [a]rtist? ");
633 BarReadline (selectBuf, sizeof (selectBuf), "sa", &app->input,
634 BAR_RL_FULLRETURN, -1);
635 if (selectBuf[0] == 's') {
636 BarUiMsg (&app->settings, MSG_INFO, "Bookmarking song... ");
637 BarUiActDefaultPianoCall (PIANO_REQUEST_BOOKMARK_SONG, selSong);
638 BarUiActDefaultEventcmd ("songbookmark");
639 } else if (selectBuf[0] == 'a') {
640 BarUiMsg (&app->settings, MSG_INFO, "Bookmarking artist... ");
641 BarUiActDefaultPianoCall (PIANO_REQUEST_BOOKMARK_ARTIST, selSong);
642 BarUiActDefaultEventcmd ("artistbookmark");
643 }
644 }
645
646 /* decrease volume
647 */
BarUiActCallback(BarUiActVolDown)648 BarUiActCallback(BarUiActVolDown) {
649 --app->settings.volume;
650 BarPlayerSetVolume (&app->player);
651 }
652
653 /* increase volume
654 */
BarUiActCallback(BarUiActVolUp)655 BarUiActCallback(BarUiActVolUp) {
656 ++app->settings.volume;
657 BarPlayerSetVolume (&app->player);
658 }
659
660 /* reset volume
661 */
BarUiActCallback(BarUiActVolReset)662 BarUiActCallback(BarUiActVolReset) {
663 app->settings.volume = 0;
664 BarPlayerSetVolume (&app->player);
665 }
666
boolToYesNo(const bool value)667 static const char *boolToYesNo (const bool value) {
668 return value ? "yes" : "no";
669 }
670
671 /* change pandora settings
672 */
BarUiActCallback(BarUiActSettings)673 BarUiActCallback(BarUiActSettings) {
674 PianoReturn_t pRet;
675 CURLcode wRet;
676 PianoSettings_t settings;
677 PianoRequestDataChangeSettings_t reqData;
678 bool modified = false;
679
680 memset (&settings, 0, sizeof (settings));
681 memset (&reqData, 0, sizeof (reqData));
682
683 BarUiMsg (&app->settings, MSG_INFO, "Retrieving settings... ");
684 bool bret = BarUiActDefaultPianoCall (PIANO_REQUEST_GET_SETTINGS, &settings);
685 BarUiActDefaultEventcmd ("settingsget");
686 if (!bret) {
687 return;
688 }
689
690 BarUiMsg (&app->settings, MSG_LIST, " 0) Username (%s)\n", settings.username);
691 BarUiMsg (&app->settings, MSG_LIST, " 1) Password (*****)\n");
692 BarUiMsg (&app->settings, MSG_LIST, " 2) Explicit content filter (%s)\n",
693 boolToYesNo (settings.explicitContentFilter));
694
695 while (true) {
696 int val;
697
698 BarUiMsg (&app->settings, MSG_QUESTION, "Change setting: ");
699 if (BarReadlineInt (&val, &app->input) == 0) {
700 break;
701 }
702
703 switch (val) {
704 case 0: {
705 /* username */
706 char buf[80];
707 BarUiMsg (&app->settings, MSG_QUESTION, "New username: ");
708 if (BarReadlineStr (buf, sizeof (buf), &app->input,
709 BAR_RL_DEFAULT) > 0) {
710 reqData.newUsername = strdup (buf);
711 modified = true;
712 }
713 break;
714 }
715
716 case 1: {
717 /* password */
718 char buf[80];
719 BarUiMsg (&app->settings, MSG_QUESTION, "New password: ");
720 if (BarReadlineStr (buf, sizeof (buf), &app->input,
721 BAR_RL_NOECHO) > 0) {
722 reqData.newPassword = strdup (buf);
723 modified = true;
724 }
725 /* write missing newline */
726 puts ("");
727 break;
728 }
729
730 case 2: {
731 /* explicit content filter */
732 BarUiMsg (&app->settings, MSG_QUESTION,
733 "Enable explicit content filter? [yn] ");
734 reqData.explicitContentFilter =
735 BarReadlineYesNo (settings.explicitContentFilter,
736 &app->input) ? PIANO_TRUE : PIANO_FALSE;
737 modified = true;
738 break;
739 }
740
741 default:
742 /* continue */
743 break;
744 }
745 }
746
747 if (modified) {
748 reqData.currentUsername = app->settings.username;
749 reqData.currentPassword = app->settings.password;
750 BarUiMsg (&app->settings, MSG_INFO, "Changing settings... ");
751 BarUiActDefaultPianoCall (PIANO_REQUEST_CHANGE_SETTINGS, &reqData);
752 BarUiActDefaultEventcmd ("settingschange");
753 /* we want to be able to change settings after a username/password
754 * change, so update our internal structs too. the user will have to
755 * update his config file by himself though */
756 if (reqData.newUsername != NULL) {
757 free (app->settings.username);
758 app->settings.username = reqData.newUsername;
759 }
760 if (reqData.newPassword != NULL) {
761 free (app->settings.password);
762 app->settings.password = reqData.newPassword;
763 }
764 }
765 }
766
767 /* manage station (remove seeds or feedback)
768 */
BarUiActCallback(BarUiActManageStation)769 BarUiActCallback(BarUiActManageStation) {
770 PianoReturn_t pRet;
771 CURLcode wRet;
772 PianoRequestDataGetStationInfo_t reqData;
773 char selectBuf[2], allowedActions[6], *allowedPos = allowedActions;
774 char question[128];
775
776 memset (&reqData, 0, sizeof (reqData));
777 reqData.station = selStation;
778
779 BarUiMsg (&app->settings, MSG_INFO, "Fetching station info... ");
780 const bool bret = BarUiActDefaultPianoCall (PIANO_REQUEST_GET_STATION_INFO,
781 &reqData);
782 BarUiActDefaultEventcmd ("stationfetchinfo");
783 if (!bret) {
784 return;
785 }
786
787 /* enable submenus depending on data availability */
788 strcpy (question, "Delete ");
789 if (reqData.info.artistSeeds != NULL) {
790 strcat (question, "[a]rtist");
791 *allowedPos++ = 'a';
792 }
793 if (reqData.info.songSeeds != NULL) {
794 if (allowedPos != allowedActions) {
795 strcat (question, "/");
796 }
797 strcat (question, "[s]ong");
798 *allowedPos++ = 's';
799 }
800 if (reqData.info.stationSeeds != NULL) {
801 if (allowedPos != allowedActions) {
802 strcat (question, "/");
803 }
804 strcat (question, "s[t]ation");
805 *allowedPos++ = 't';
806 }
807 if (allowedPos != allowedActions) {
808 strcat (question, " seeds");
809 }
810 if (reqData.info.feedback != NULL) {
811 if (allowedPos != allowedActions) {
812 strcat (question, " or ");
813 }
814 strcat (question, "[f]eedback");
815 *allowedPos++ = 'f';
816 }
817 /* station mode is always available */
818 if (allowedPos != allowedActions) {
819 strcat (question, "? ");
820 }
821 strcat (question, "Manage [m]ode? ");
822 *allowedPos++ = 'm';
823
824 *allowedPos = '\0';
825
826 assert (strlen (question) < sizeof (question) / sizeof (*question));
827
828 BarUiMsg (&app->settings, MSG_QUESTION, "%s", question);
829 if (BarReadline (selectBuf, sizeof (selectBuf), allowedActions, &app->input,
830 BAR_RL_FULLRETURN, -1)) {
831 if (selectBuf[0] == 'a') {
832 PianoArtist_t *artist = BarUiSelectArtist (app,
833 reqData.info.artistSeeds);
834 if (artist != NULL) {
835 PianoRequestDataDeleteSeed_t subReqData;
836
837 memset (&subReqData, 0, sizeof (subReqData));
838 subReqData.artist = artist;
839
840 BarUiMsg (&app->settings, MSG_INFO, "Deleting artist seed... ");
841 BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_SEED, &subReqData);
842 BarUiActDefaultEventcmd ("stationdeleteartistseed");
843 }
844 } else if (selectBuf[0] == 's') {
845 PianoSong_t *song = BarUiSelectSong (app,
846 reqData.info.songSeeds, &app->input);
847 if (song != NULL) {
848 PianoRequestDataDeleteSeed_t subReqData;
849
850 memset (&subReqData, 0, sizeof (subReqData));
851 subReqData.song = song;
852
853 BarUiMsg (&app->settings, MSG_INFO, "Deleting song seed... ");
854 BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_SEED, &subReqData);
855 BarUiActDefaultEventcmd ("stationdeletesongseed");
856 }
857 } else if (selectBuf[0] == 't') {
858 PianoStation_t *station = BarUiSelectStation (app,
859 reqData.info.stationSeeds, "Delete seed station: ", NULL,
860 false);
861 if (station != NULL) {
862 PianoRequestDataDeleteSeed_t subReqData;
863
864 memset (&subReqData, 0, sizeof (subReqData));
865 subReqData.station = station;
866
867 BarUiMsg (&app->settings, MSG_INFO, "Deleting station seed... ");
868 BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_SEED, &subReqData);
869 BarUiActDefaultEventcmd ("stationdeletestationseed");
870 }
871 } else if (selectBuf[0] == 'f') {
872 PianoSong_t *song = BarUiSelectSong (app,
873 reqData.info.feedback, &app->input);
874 if (song != NULL) {
875 BarUiMsg (&app->settings, MSG_INFO, "Deleting feedback... ");
876 BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_FEEDBACK, song);
877 BarUiActDefaultEventcmd ("stationdeletefeedback");
878 }
879 } else if (selectBuf[0] == 'm') {
880 PianoRequestDataGetStationModes_t subReqData =
881 { .station = selStation };
882 BarUiMsg (&app->settings, MSG_INFO, "Fetching modes... ");
883 BarUiActDefaultPianoCall (PIANO_REQUEST_GET_STATION_MODES,
884 &subReqData);
885 BarUiActDefaultEventcmd ("stationgetmodes");
886
887 const PianoStationMode_t *curMode = subReqData.retModes;
888 unsigned int i = 0;
889 PianoListForeachP (curMode) {
890 BarUiMsg (&app->settings, MSG_LIST, "%2i) %s: %s%s\n", i,
891 curMode->name, curMode->description,
892 curMode->active ? " (active)" : "");
893 i++;
894 }
895
896 BarUiMsg (&app->settings, MSG_QUESTION, "Pick a new mode: ");
897 int selected;
898 while (true) {
899 if (BarReadlineInt (&selected, &app->input) == 0) {
900 break;
901 }
902
903 const PianoStationMode_t * const selMode =
904 PianoListGetP (subReqData.retModes, selected);
905 if (selMode != NULL) {
906 PianoRequestDataSetStationMode_t subReqDataSet =
907 {.station = selStation, .id = selected};
908 BarUiMsg (&app->settings, MSG_INFO,
909 "Selecting mode \"%s\"... ", selMode->name);
910 if (BarUiActDefaultPianoCall (
911 PIANO_REQUEST_SET_STATION_MODE, &subReqDataSet)) {
912 drainPlaylist (app);
913 }
914 BarUiActDefaultEventcmd ("stationsetmode");
915 break;
916 }
917 }
918
919 PianoDestroyStationMode (subReqData.retModes);
920 }
921 }
922
923 PianoDestroyStationInfo (&reqData.info);
924 }
925