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