1 // FLTK GUI related functions
2 //
3 // Copyright 2007-2018 by Daniel Noethen.
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, or (at your option)
8 // 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 
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <unistd.h>
20 #include <signal.h>
21 #include <time.h>
22 #include <sys/stat.h>
23 
24 #ifndef WIN32
25  #include <sys/wait.h>
26 #endif
27 
28 #include "gettext.h"
29 #include "config.h"
30 
31 #include "cfg.h"
32 #include "butt.h"
33 #include "util.h"
34 #include "port_audio.h"
35 #include "timer.h"
36 #include "flgui.h"
37 #include "fl_funcs.h"
38 #include "shoutcast.h"
39 #include "icecast.h"
40 #include "strfuncs.h"
41 #include "fl_timer_funcs.h"
42 #include "command.h"
43 
44 #if __APPLE__ && __MACH__
45  #include "CurrentTrackOSX.h"
46 #endif
47 
48 const char* (*current_track_app)(void);
49 
cmd_timer(void *)50 void cmd_timer(void*)
51 {
52     command_t command;
53     command_get_last_cmd(&command);
54     switch(command.cmd)
55     {
56         case CMD_CONNECT:
57             if (!connected) {
58                 if (command.param_size > 0) {
59                     char *srv_name = (char*)command.param;
60                     int idx;
61                     if ((idx = fl_g->choice_cfg_act_srv->find_index(srv_name)) != -1) {
62                         fl_g->choice_cfg_act_srv->value(idx);
63                         fl_g->choice_cfg_act_srv->do_callback();
64                         button_connect_cb();
65                     }
66                     else
67                         Fl::repeat_timeout(0.25, &cmd_timer);
68 
69                     if (command.param != NULL) {
70                         free(command.param);
71                     }
72                 }
73                 else
74                     button_connect_cb();
75             }
76            //  Fl::repeat_timeout(0.25, &cmd_timer) is called in button_connect_cb() after the connect_thread() has returned (crashes if called here)
77             else
78                 Fl::repeat_timeout(0.25, &cmd_timer);
79             break;
80         case CMD_DISCONNECT:
81             button_disconnect_cb();
82             Fl::repeat_timeout(0.25, &cmd_timer);
83             break;
84         case CMD_START_RECORDING:
85             if (!recording)
86                 button_record_cb();
87             Fl::repeat_timeout(0.25, &cmd_timer);
88             break;
89         case CMD_STOP_RECORDING:
90             stop_recording(false);
91             Fl::repeat_timeout(0.25, &cmd_timer);
92             break;
93         case CMD_GET_STATUS:
94             uint32_t status;
95             status = (connected<<STATUS_CONNECTED) | (try_to_connect<<STATUS_CONNECTING) | (recording<<STATUS_RECORDING);
96             command_send_status_reply(status);
97 
98             Fl::repeat_timeout(0.25, &cmd_timer);
99         default:
100             Fl::repeat_timeout(0.25, &cmd_timer);
101     }
102 }
103 
104 
vu_meter_timer(void *)105 void vu_meter_timer(void*)
106 {
107     if(pa_new_frames)
108         snd_update_vu();
109 
110     Fl::repeat_timeout(0.05, &vu_meter_timer);
111 }
112 
display_info_timer(void *)113 void display_info_timer(void*)
114 {
115     char lcd_text_buf[33];
116 
117     if(try_to_connect == 1)
118     {
119         Fl::repeat_timeout(0.1, &display_info_timer);
120         return;
121     }
122 
123     if(display_info == SENT_DATA)
124     {
125         sprintf(lcd_text_buf, _("stream sent\n%0.2lfMB"),
126                 kbytes_sent / 1024);
127         print_lcd(lcd_text_buf, strlen(lcd_text_buf), 0, 1);
128     }
129 
130     if(display_info == STREAM_TIME && timer_is_elapsed(&stream_timer))
131     {
132         sprintf(lcd_text_buf, _("stream time\n%s"),
133                 timer_get_time_str(&stream_timer));
134         print_lcd(lcd_text_buf, strlen(lcd_text_buf), 0, 1);
135     }
136 
137     if(display_info == REC_TIME && timer_is_elapsed(&rec_timer))
138     {
139         sprintf(lcd_text_buf, _("record time\n%s"),
140                 timer_get_time_str(&rec_timer));
141         print_lcd(lcd_text_buf, strlen(lcd_text_buf), 0, 1);
142     }
143 
144     if(display_info == REC_DATA)
145     {
146         sprintf(lcd_text_buf, _("record size\n%0.2lfMB"),
147                 kbytes_written / 1024);
148         print_lcd(lcd_text_buf, strlen(lcd_text_buf), 0, 1);
149     }
150 
151     Fl::repeat_timeout(0.1, &display_info_timer);
152 }
153 
display_rotate_timer(void *)154 void display_rotate_timer(void*)
155 {
156 
157     if(!connected && !recording)
158         goto exit;
159 
160     if (!cfg.gui.lcd_auto)
161         goto exit;
162 
163     switch(display_info)
164     {
165         case STREAM_TIME:
166             display_info = SENT_DATA;
167             break;
168         case SENT_DATA:
169             if(recording)
170                 display_info = REC_TIME;
171             else
172                 display_info = STREAM_TIME;
173             break;
174         case REC_TIME:
175             display_info = REC_DATA;
176             break;
177         case REC_DATA:
178             if(connected)
179                 display_info = STREAM_TIME;
180             else
181                 display_info = REC_TIME;
182             break;
183         default:
184             break;
185     }
186 
187 exit:
188     Fl::repeat_timeout(5, &display_rotate_timer);
189 
190 }
191 
is_connected_timer(void *)192 void is_connected_timer(void*)
193 {
194     if(!connected)
195     {
196         print_info(_("ERROR: Connection lost\nreconnecting..."), 1);
197         if(cfg.srv[cfg.selected_srv]->type == SHOUTCAST)
198             sc_disconnect();
199         else
200             ic_disconnect();
201 
202         Fl::remove_timeout(&display_info_timer);
203         Fl::remove_timeout(&is_connected_timer);
204 
205         //reconnect
206         button_connect_cb();
207 
208         return;
209     }
210 
211     Fl::repeat_timeout(0.5, &is_connected_timer);
212 }
213 
cfg_win_pos_timer(void *)214 void cfg_win_pos_timer(void*)
215 {
216 
217 #ifdef WIN32
218     fl_g->window_cfg->position(fl_g->window_main->x() +
219                                 fl_g->window_main->w()+0,
220                                 fl_g->window_main->y());
221 #else //UNIX
222     fl_g->window_cfg->position(fl_g->window_main->x() +
223                                 fl_g->window_main->w(),
224                                 fl_g->window_main->y());
225 #endif
226 
227     Fl::repeat_timeout(0.1, &cfg_win_pos_timer);
228 }
229 
split_recording_timer(void * mode)230 void split_recording_timer(void* mode)
231 {
232     int i;
233     int button_clicked = 0;
234     static int with_repeat = 0;
235     char *insert_pos;
236     char *path;
237     char *ext;
238     char file_num_str[12];
239     char *path_for_index_loop;
240     struct tm *local_time;
241     const time_t t = time(NULL);
242 
243     if (recording == 0)
244         return;
245 
246     if(*((int*)mode) == 1)
247         with_repeat = 1;
248     else
249         button_clicked = 1;
250 
251 
252     // Values < 0 are not allowed
253     if(fl_g->input_rec_split_time->value() < 0)
254     {
255         fl_g->input_rec_split_time->value(0);
256         return;
257     }
258 
259     path = strdup(cfg.rec.path_fmt);
260     expand_string(&path);
261     ext = util_get_file_extension(cfg.rec.filename);
262     if(ext == NULL)
263     {
264         print_info(_("Could not find a file extension in current filename\n"
265                 "Automatic file splitting is deactivated"), 0);
266         free(path);
267         return;
268     }
269 
270 
271     path_for_index_loop = strdup(path);
272 
273 
274     //check if the file already exists
275     if((next_fd = fl_fopen(path, "rb")) != NULL)
276     {
277         fclose(next_fd);
278 
279         //increment the index until we find a filename that doesn't exist yet
280         for(i = 1; /*inf*/; i++) // index_loop
281         {
282 
283             free(path);
284             path = strdup(path_for_index_loop);
285 
286             //find beginn of the file extension
287             insert_pos = strrstr(path, ext);
288 
289             //Put index between end of file name end beginning of extension
290             snprintf(file_num_str, sizeof(file_num_str), "-%d", i);
291             strinsrt(&path, file_num_str, insert_pos-1);
292 
293             if((next_fd = fl_fopen(path, "rb")) == NULL)
294                 break; // found valid file name
295 
296             fclose(next_fd);
297 
298             if (i == 0x7FFFFFFF) // 2^31-1
299             {
300                 free(path);
301                 free(path_for_index_loop);
302                 print_info(_("Could not find a valid filename for next file"
303                         "\nbutt keeps recording to current file"), 0);
304                 return;
305             }
306         }
307     }
308 
309     free(path_for_index_loop);
310 
311     if((next_fd = fl_fopen(path, "wb")) == NULL)
312     {
313         fl_alert(_("Could not open:\n%s"), path);
314         free(path);
315         return;
316     }
317 
318     print_info(_("Recording to:"), 0);
319     print_info(path, 0);
320 
321     next_file = 1;
322     free(path);
323 
324 
325     if(with_repeat == 1 && button_clicked == 0)
326     {
327         local_time = localtime(&t);
328 
329         // Make sure that the 60 minutes boundary is not violated in case sync_to_hour == 1
330         if((cfg.rec.sync_to_hour == 1) && ((local_time->tm_min + cfg.rec.split_time) > 60))
331             Fl::repeat_timeout(60*(60 - local_time->tm_min), &split_recording_timer, &with_repeat);
332         else
333             Fl::repeat_timeout(60*cfg.rec.split_time, &split_recording_timer, &with_repeat);
334 
335     }
336 }
337 
stream_signal_timer(void *)338 void stream_signal_timer(void*)
339 {
340    //printf("stream_signal_timer\n");
341     static sec_timer signal_timer;
342     if (signal_detected == true)
343     {
344         if (signal_timer.is_running == false)
345             timer_init(&signal_timer, cfg.main.signal_threshold);
346 
347         if (timer_is_elapsed(&signal_timer))
348         {
349             //print_info("Audio signal detected", 0);
350             button_connect_cb();
351             timer_reset(&signal_timer);
352             return;
353         }
354     }
355     else
356         timer_reset(&signal_timer);
357 
358     Fl::repeat_timeout(1, &stream_signal_timer);
359 }
360 
record_signal_timer(void *)361 void record_signal_timer(void*)
362 {
363     //printf("record_signal_timer\n");
364 
365     static sec_timer signal_timer;
366     if (signal_detected == true)
367     {
368         if (signal_timer.is_running == false)
369             timer_init(&signal_timer, cfg.rec.signal_threshold);
370 
371         if (timer_is_elapsed(&signal_timer))
372         {
373             //print_info("Audio signal detected", 0);
374             button_record_cb();
375             timer_reset(&signal_timer);
376             return;
377         }
378     }
379     else
380         timer_reset(&signal_timer);
381 
382     Fl::repeat_timeout(1, &record_signal_timer);
383 }
384 
stream_silence_timer(void *)385 void stream_silence_timer(void*)
386 {
387     //printf("stream_silence_timer\n");
388 
389     static sec_timer silence_timer;
390     if (silence_detected == true)
391     {
392         if (silence_timer.is_running == false)
393             timer_init(&silence_timer, cfg.main.silence_threshold);
394 
395         if (timer_is_elapsed(&silence_timer))
396         {
397             //print_info("Streaming silence threshold has been reached", 0);
398             button_disconnect_cb();
399             timer_reset(&silence_timer);
400             return;
401         }
402     }
403     else
404         timer_reset(&silence_timer);
405 
406     Fl::repeat_timeout(1, &stream_silence_timer);
407 }
408 
record_silence_timer(void *)409 void record_silence_timer(void*)
410 {
411     //printf("record_silence_timer\n");
412 
413     static sec_timer silence_timer;
414     if (silence_detected == true)
415     {
416         if (silence_timer.is_running == false)
417             timer_init(&silence_timer, cfg.rec.silence_threshold);
418 
419         if (timer_is_elapsed(&silence_timer))
420         {
421             //print_info("Recording silence threshold has been reached", 0);
422             stop_recording(false);
423             timer_reset(&silence_timer);
424             return;
425         }
426     }
427     else
428         timer_reset(&silence_timer);
429 
430     Fl::repeat_timeout(1, &record_silence_timer);
431 }
432 
433 
songfile_timer(void * user_data)434 void songfile_timer(void* user_data)
435 {
436     size_t len;
437 	int i;
438 	int num_of_lines;
439 	int num_of_newlines;
440     char song[501];
441     char msg[100];
442     float repeat_time = 1;
443 
444     int reset;
445     if (user_data != NULL)
446         reset = *((int*)user_data);
447     else
448         reset = 0;
449 
450 #ifdef WIN32
451     struct _stat s;
452 #else
453     struct stat s;
454 #endif
455 
456     static time_t old_t;
457 
458     if (reset == 1) {
459         old_t = 0;
460     }
461 
462     char *last_line = NULL;
463 
464     if( (cfg.main.song_path == NULL) || (connected == 0) )
465         goto exit;
466 
467     if(fl_stat(cfg.main.song_path, (struct stat*)&s) != 0)
468     {
469         // File was probably locked by another application
470         // retry in 5 seconds
471         repeat_time = 5;
472         goto exit;
473     }
474 
475     if(old_t == s.st_mtime) //file hasn't changed
476         goto exit;
477 
478    if((cfg.main.song_fd = fl_fopen(cfg.main.song_path, "rb")) == NULL)
479    {
480 	   snprintf(msg, sizeof(msg), _("Warning\nCould not open: %s.\nWill retry in 5 seconds"),
481 					   cfg.main.song_path);
482 
483        print_info(msg, 1);
484        repeat_time = 5;
485        goto exit;
486    }
487 
488     old_t = s.st_mtime;
489 
490     if(cfg.main.read_last_line == 1)
491     {
492         /* Read last line instead of first */
493 
494         fseek(cfg.main.song_fd, -100, SEEK_END);
495         len = fread(song, sizeof(char), 100, cfg.main.song_fd);
496         if(len == 0)
497         {
498             fclose(cfg.main.song_fd);
499             goto exit;
500         }
501 
502 		// Count number of lines within the last 100 characters of the file
503 		// Some programs add a new line to the end of the file and some don't
504 		// We have to take this into account when counting the number of lines
505 		num_of_newlines = 0;
506 		for(i = 0; i < len; i++)
507 		{
508 			if (song[i] == '\n')
509 				num_of_newlines++;
510 		}
511 
512 		if(num_of_newlines == 0)
513 			num_of_lines = 1;
514 		else if (num_of_newlines > 0 && song[len-1] != '\n')
515 			num_of_lines = num_of_newlines+1;
516 		else
517 			num_of_lines = num_of_newlines;
518 
519         if(num_of_lines > 1) // file has multiple lines
520         {
521 			// Remove newlines at end of file
522             if(song[len-2] == '\r') // Windows
523                 song[len-2] = '\0';
524             else if(song[len-1] == '\n') // OSX, Linux
525                 song[len-1] = '\0';
526 			else
527 				song[len] = '\0';
528 
529             last_line = strrchr(song, '\n')+1;
530         }
531         else // file has only one line
532         {
533 
534 			// Remove newlines at end of file
535             if(song[len-2] == '\r') // Windows
536                 song[len-2] = '\0';
537             else if(song[len-1] == '\n') // OSX, Linux
538                 song[len-1] = '\0';
539 			else
540 				song[len] = '\0';
541 
542 			last_line = song;
543         }
544 
545         cfg.main.song = (char*) realloc(cfg.main.song, strlen(last_line) +1);
546         strcpy(cfg.main.song, last_line);
547         update_song(0);
548     }
549     else
550     {
551 		// read first line
552         if(fgets(song, 500, cfg.main.song_fd) != NULL)
553         {
554             len = strlen(song);
555 
556             //remove newline character
557             if(song[len-2] == '\r') // Windows
558                 song[len-2] = '\0';
559             else if(song[len-1] == '\n') // OSX, Linux
560                 song[len-1] = '\0';
561 
562             cfg.main.song = (char*) realloc(cfg.main.song, strlen(song) +1);
563             strcpy(cfg.main.song, song);
564             update_song(0);
565         }
566     }
567 
568    fclose(cfg.main.song_fd);
569 
570 exit:
571     Fl::repeat_timeout(repeat_time, &songfile_timer);
572 }
573 
574 
app_timer(void * user_data)575 void app_timer(void* user_data)
576 {
577     int reset;
578 
579     if (user_data != NULL)
580         reset = *((int*)user_data);
581     else
582         reset = 0;
583 
584 
585     if(current_track_app != NULL)
586     {
587         const char* track = current_track_app();
588         if(track != NULL)
589         {
590             if(cfg.main.song == NULL || strcmp(cfg.main.song, track) || reset == 1)
591             {
592                 cfg.main.song = (char*) realloc(cfg.main.song, strlen(track) + 1);
593                 strcpy(cfg.main.song, track);
594                 update_song(0);
595             }
596             free((void*)track);
597         }
598         else
599         {
600             if(cfg.main.song != NULL && strcmp(cfg.main.song, ""))
601             {
602                 cfg.main.song = (char*) realloc(cfg.main.song, 1);
603                 strcpy(cfg.main.song, "");
604                 update_song(0);
605             }
606         }
607     }
608 
609     Fl::repeat_timeout(1, &app_timer);
610 }
611