// butt - broadcast using this tool // // Copyright 2007-2018 by Daniel Noethen. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // #include #include #include #include #include #include #ifdef WIN32 #include #define IDI_ICON 101 #endif #ifndef BUILD_CLIENT #include #include #if !defined(__APPLE__) && !defined(WIN32) #include #include #endif #if defined(__APPLE__) #include // for dirname #include // for _NSGetExecutablePath #include #include "AskForMicPermission.h" #endif #endif // #ifndef BUILD_CLIENT #include "config.h" #include "gettext.h" #include "butt.h" #include "command.h" #include "sockfuncs.h" #ifndef BUILD_CLIENT #include "cfg.h" #include "port_audio.h" #include "lame_encode.h" #include "opus_encode.h" #include "flac_encode.h" #include "shoutcast.h" #include "parseconfig.h" #include "vu_meter.h" #include "util.h" #include "flgui.h" #include "fl_funcs.h" #include "fl_timer_funcs.h" #include "update.h" #include "tray_agent.h" #ifdef WITH_RADIOCO #include "radioco.h" #endif #ifdef HAVE_LIBFDK_AAC #include "aac_encode.h" #endif #include "port_midi.h" int g_print_debug_info = 0; bool recording; bool connected; bool streaming; bool disconnect; bool try_to_connect; int stream_socket; double kbytes_sent; double kbytes_written; unsigned int record_start_hour; timer_ms_t rec_timer; timer_ms_t stream_timer; lame_enc lame_stream; lame_enc lame_rec; vorbis_enc vorbis_stream; vorbis_enc vorbis_rec; opus_enc opus_stream; opus_enc opus_rec; flac_enc flac_stream; flac_enc flac_rec; #ifdef HAVE_LIBFDK_AAC aac_enc aac_stream; aac_enc aac_rec; aacEncOpenPtr aacEncOpen_butt; aacEncoder_SetParamPtr aacEncoder_SetParam_butt; aacEncoder_GetParamPtr aacEncoder_GetParam_butt; aacEncEncodePtr aacEncEncode_butt; aacEncInfoPtr aacEncInfo_butt; aacEncClosePtr aacEncClose_butt; #endif // ifdef HAVE_LIBFDK_AAC #endif // ifndef BUILD_CLIENT void print_usage(void) { #ifndef BUILD_CLIENT printf( "Usage: butt [-h | -v | -s [name] | -u | -M | -m | -O | -o | -SLdrtqn | -c ] [-A | -U | -x] [-a ] [-p ]\n"); #else printf( "Usage: butt-client [-h | -v | -s [name] | -u | -M | -m | -O | -o | -USdrtqn ] [-a ] [-p ]\n"); #endif fflush(stdout); } #ifndef BUILD_CLIENT void load_AAC_lib(void) { #ifdef WIN32 // Load aac library HMODULE hModule = LoadLibrary(TEXT("libfdk-aac-2.dll")); if (hModule != NULL) { aacEncOpen_butt = (aacEncOpenPtr)GetProcAddress(hModule, "aacEncOpen"); aacEncoder_SetParam_butt = (aacEncoder_SetParamPtr)GetProcAddress(hModule, "aacEncoder_SetParam"); aacEncoder_GetParam_butt = (aacEncoder_GetParamPtr)GetProcAddress(hModule, "aacEncoder_GetParam"); aacEncEncode_butt = (aacEncEncodePtr)GetProcAddress(hModule, "aacEncEncode"); aacEncInfo_butt = (aacEncInfoPtr)GetProcAddress(hModule, "aacEncInfo"); aacEncClose_butt = (aacEncClosePtr)GetProcAddress(hModule, "aacEncClose"); g_aac_lib_available = 1; } #endif #if defined(__APPLE__) #ifdef HAVE_LIBFDK_AAC void *dylib = dlopen("libfdk-aac.2.dylib", RTLD_LAZY); if (dylib == NULL) { dylib = dlopen("/Library/Application Support/butt/libfdk-aac.2.dylib", RTLD_LAZY); // New path since 0.1.34 } if (dylib != NULL) { // typedef AACENC_ERROR(WINAPI *aacEncOpenPtr)(HANDLE_AACENCODER*, UINT, UINT); aacEncOpen_butt = (aacEncOpenPtr)dlsym(dylib, "aacEncOpen"); aacEncoder_SetParam_butt = (aacEncoder_SetParamPtr)dlsym(dylib, "aacEncoder_SetParam"); aacEncoder_GetParam_butt = (aacEncoder_GetParamPtr)dlsym(dylib, "aacEncoder_GetParam"); aacEncEncode_butt = (aacEncEncodePtr)dlsym(dylib, "aacEncEncode"); aacEncInfo_butt = (aacEncInfoPtr)dlsym(dylib, "aacEncInfo"); aacEncClose_butt = (aacEncClosePtr)dlsym(dylib, "aacEncClose"); g_aac_lib_available = 1; } // askForMicPermission(); #endif #endif #if !defined(__APPLE__) && !defined(WIN32) // LINUX #ifdef HAVE_LIBFDK_AAC void *dylib = dlopen("libfdk-aac.so", RTLD_LAZY); if (dylib == NULL) { // Try other lib name dylib = dlopen("libfdk-aac.so.2", RTLD_LAZY); } if (dylib != NULL) { // typedef AACENC_ERROR(WINAPI *aacEncOpenPtr)(HANDLE_AACENCODER*, UINT, UINT); aacEncOpen_butt = (aacEncOpenPtr)dlsym(dylib, "aacEncOpen"); aacEncoder_SetParam_butt = (aacEncoder_SetParamPtr)dlsym(dylib, "aacEncoder_SetParam"); aacEncoder_GetParam_butt = (aacEncoder_GetParamPtr)dlsym(dylib, "aacEncoder_GetParam"); aacEncEncode_butt = (aacEncEncodePtr)dlsym(dylib, "aacEncEncode"); aacEncInfo_butt = (aacEncInfoPtr)dlsym(dylib, "aacEncInfo"); aacEncClose_butt = (aacEncClosePtr)dlsym(dylib, "aacEncClose"); g_aac_lib_available = 1; } #endif #endif } int read_cfg(void) { char *p; int shift_pressed; if (Fl::get_key(FL_Shift_L) || Fl::get_key(FL_Shift_R)) { shift_pressed = 1; } else { shift_pressed = 0; } // Prepare configuration loading #ifdef WIN32 if ((cfg_path == NULL) || (shift_pressed == 1)) { if ((p = fl_getenv("APPDATA")) == NULL) { // If there is no "%APPDATA% we are probably in none-NT Windows // So we save the config file to the programm directory cfg_path = (char *)malloc(strlen(CONFIG_FILE) + 1); strcpy(cfg_path, CONFIG_FILE); } else { cfg_path = (char *)malloc((PATH_MAX + strlen(CONFIG_FILE) + 1) * sizeof(char)); snprintf(cfg_path, PATH_MAX + strlen(CONFIG_FILE), "%s\\%s", p, CONFIG_FILE); } } #else // UNIX/MacOS if ((cfg_path == NULL) || (shift_pressed == 1)) { if ((p = fl_getenv("HOME")) == NULL) { fl_alert(_("No home-directory found")); return 1; } else { cfg_path = (char *)malloc((PATH_MAX + strlen(CONFIG_FILE) + 1) * sizeof(char)); snprintf(cfg_path, PATH_MAX + strlen(CONFIG_FILE), "%s/%s", p, CONFIG_FILE); } } #endif if (shift_pressed) { int rc = fl_choice(_("The shift key was held down during startup.\n" "Do you want to start butt with a new configuration file?\n" "This will overwrite your existing configuration file at\n%s"), 0, _("Start with old"), _("Start with new"), cfg_path); if (rc == 2) { if (cfg_create_default()) { fl_alert(_("Could not create config %s\nbutt is going to close now"), cfg_path); return 1; } } } printf(_("Reading config %s\n"), cfg_path); fflush(stdout); DEBUG_LOG("Reading config from "); DEBUG_LOG(cfg_path); if (cfg_set_values(NULL) != 0) // read config file and initialize config struct { printf(_("Could not find config %s\n"), cfg_path); fflush(stdout); if (cfg_create_default()) { fl_alert(_("Could not create config %s\nbutt is going to close now"), cfg_path); DEBUG_LOG("Could not create default config file"); return 1; } printf(_("butt created a default config at\n%s\n"), cfg_path); fflush(stdout); cfg_set_values(NULL); } if (cfg.audio.dev_count == 0) { DEBUG_LOG("Could not find input audio devices"); fl_alert(_( "Could not find any audio device with input channels.\nbutt requires at least one audio device with input channels in order to work.\nThis can either be a built-in audio device, an external audio device or a virtual audio device.\n\nbutt is going to close now.")); return 1; } return 0; } void set_locale_from_system(void) { setlocale(LC_CTYPE, ""); setlocale(LC_MESSAGES, ""); #if defined(__APPLE__) char path_to_executeable[PATH_MAX]; char path_to_locale_dir[PATH_MAX]; uint32_t path_len = (uint32_t)sizeof(path_to_executeable); if (_NSGetExecutablePath(path_to_executeable, &path_len) == 0) { char *folder_of_executable = strdup(dirname(path_to_executeable)); snprintf(path_to_locale_dir, PATH_MAX, "%s%s", folder_of_executable, "/../Resources/locale"); free(folder_of_executable); } bindtextdomain(PACKAGE, path_to_locale_dir); #elif defined(WIN32) bindtextdomain(PACKAGE, "locale"); #else // Linux char *p; if ((p = fl_getenv("APPDIR")) != NULL) { // Set locale dir when launched from .AppImage char path_to_locale_dir[PATH_MAX]; snprintf(path_to_locale_dir, sizeof(path_to_locale_dir), "%s/usr/share/locale", p); bindtextdomain(PACKAGE, path_to_locale_dir); } else { // Set locale dir to dir found by ./configure bindtextdomain(PACKAGE, LOCALEDIR); } #endif textdomain(PACKAGE); bind_textdomain_codeset(PACKAGE, "UTF-8"); } void set_locale_from_config(void) { int lang; static char env_str[32]; setlocale(LC_CTYPE, ""); setlocale(LC_MESSAGES, ""); snprintf(env_str, sizeof(env_str), "LANG=%s", cfg.gui.lang_str); putenv(env_str); snprintf(env_str, sizeof(env_str), "LANGUAGE=%s", cfg.gui.lang_str); putenv(env_str); #if defined(__APPLE__) char path_to_executeable[PATH_MAX]; char path_to_locale_dir[PATH_MAX]; uint32_t path_len = (uint32_t)sizeof(path_to_executeable); if (_NSGetExecutablePath(path_to_executeable, &path_len) == 0) { char *folder_of_executable = strdup(dirname(path_to_executeable)); snprintf(path_to_locale_dir, PATH_MAX, "%s%s", folder_of_executable, "/../Resources/locale"); printf("%s\n", path_to_locale_dir); free(folder_of_executable); } bindtextdomain(PACKAGE, path_to_locale_dir); #elif defined(WIN32) bindtextdomain(PACKAGE, "locale"); #else // Linux char *p; if ((p = fl_getenv("APPDIR")) != NULL) { // Set locale dir when launched from .AppImage char path_to_locale_dir[PATH_MAX]; snprintf(path_to_locale_dir, sizeof(path_to_locale_dir), "%s/usr/share/locale", p); bindtextdomain(PACKAGE, path_to_locale_dir); } else { // Set locale dir to dir found by ./configure bindtextdomain(PACKAGE, LOCALEDIR); } #endif textdomain(PACKAGE); bind_textdomain_codeset(PACKAGE, "UTF-8"); } #endif // BUILD_CLIENT int get_threshold_from_args(char opt, char *optarg, command_t *command) { float threshold = atof(optarg); if (threshold < 0) { printf(_("Illegal argument: Threshold must be a non-negative number\n")); fflush(stdout); } else { switch (opt) { case 'M': command->cmd = CMD_SET_STREAM_SIGNAL_THRESHOLD; break; case 'm': command->cmd = CMD_SET_STREAM_SILENCE_THRESHOLD; break; case 'O': command->cmd = CMD_SET_RECORD_SIGNAL_THRESHOLD; break; case 'o': command->cmd = CMD_SET_RECORD_SILENCE_THRESHOLD; break; default: printf("Internal error: Illegal value for \"opt\"\n"); return -1; } command->param_size = sizeof(float); command->param = (float *)malloc(command->param_size); *((float *)command->param) = threshold; printf(_("%c threshold set to %0.1f\n"), opt, threshold); } return threshold; } int main(int argc, char *argv[]) { DEBUG_LOG("Starting BUTT"); char lcd_buf[33]; char info_buf[256]; int opt; int port = 1256; int search_port = 1; int skip_opening_audio_device = 0; int server_mode = SERVER_MODE_LOCAL; sock_proto_t command_proto = SOCK_PROTO_TCP; char server_addr[128]; uint32_t song_len; srand(time(NULL)); #ifndef BUILD_CLIENT cfg_path = NULL; // Activate support for multi threading Fl::lock(); #endif command_t command; command.cmd = CMD_EMPTY; command.param_size = 0; command.param = NULL; snprintf(server_addr, sizeof(server_addr), "%s", "127.0.0.1"); #ifndef WIN32 // ignore the SIGPIPE signal. (In case the server closes the connection unexpected) signal(SIGPIPE, SIG_IGN); #endif #ifndef BUILD_CLIENT #ifdef ENABLE_NLS DEBUG_LOG("Setting up locale"); set_locale_from_system(); #endif #endif DEBUG_LOG("Init sockets"); sock_init(); #ifdef BUILD_CLIENT if (argc < 2) { print_usage(); } #endif // Parse command line parameters DEBUG_LOG("Parsing command line parameters"); #ifndef WIN32 while ((opt = getopt(argc, argv, ":vhc:AULxs:drtnqu:a:p:SM:m:O:o:j:")) != -1) { #else while ((opt = getopt(argc, argv, ":vhc:AULxs:drtnqu:a:p:SM:m:O:o:")) != -1) { #endif switch (opt) { #ifndef BUILD_CLIENT case 'A': server_mode = SERVER_MODE_ALL; break; case 'x': server_mode = SERVER_MODE_OFF; break; case 'c': cfg_path = (char *)malloc((strlen(optarg) + 1) * sizeof(char)); strcpy(cfg_path, optarg); break; case 'L': snd_print_devices(); return 0; break; #endif case 'U': command_proto = SOCK_PROTO_UDP; break; case 'p': port = atoi(optarg); if (port < 1024 || port > 65535) { printf(_("Illegal argument: Port must be a number between 1023 and 65535\n")); fflush(stdout); return 1; } search_port = 0; break; case 's': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } command.cmd = CMD_CONNECT; command.param_size = (uint32_t)strlen(optarg) + 1; command.param = (char *)malloc(command.param_size); sprintf((char *)command.param, "%s", optarg); break; case 'd': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } command.cmd = CMD_DISCONNECT; break; case 'r': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } command.cmd = CMD_START_RECORDING; break; case 't': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } command.cmd = CMD_STOP_RECORDING; break; case 'n': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } command.cmd = CMD_SPLIT_RECORDING; break; case 'q': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } command.cmd = CMD_QUIT; break; case 'v': printf("%s %s\n", argv[0], VERSION); fflush(stdout); return 0; case 'u': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } song_len = strlen(optarg) + 1; if (song_len > 1000) { song_len = 1000; } command.cmd = CMD_UPDATE_SONGNAME; command.param_size = song_len; command.param = (char *)malloc(command.param_size); sprintf((char *)command.param, "%s", optarg); break; case 'a': memset(server_addr, 0, sizeof(server_addr)); snprintf(server_addr, sizeof(server_addr), "%s", optarg); break; case 'S': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } command.cmd = CMD_GET_STATUS; break; case 'M': case 'm': case 'O': case 'o': if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt); break; } if (get_threshold_from_args(opt, optarg, &command) < 0) { return 1; } break; case 'h': print_usage(); printf(_("\nOptions:\n" "-h\tPrint this help text\n" "-v\tPrint version information\n")); #ifndef BUILD_CLIENT printf(_("\nOptions for operating mode:\n" "-c\tPath to configuration file\n" "-L\tPrint available audio devices\n" "-A\tCommand server will be accessible from your network/internet (default: localhost only)\n" "-U\tCommand server will use UDP instead of TCP\n" "-x\tDo not start a command server\n" "-p\tPort where the command server shall listen to (default: 1256)\n")); #endif printf(_("\nOptions for control mode:\n" "-s\tConnect to streaming server\n" "-d\tDisconnect from streaming server\n" "-r\tStart recording\n" "-t\tStop recording\n" "-n\tSplit recording\n" "-q\tQuit butt\n" "-u\tupdate song name\n" "-S\tRequest status\n" "-M\tSet streaming signal threshold (seconds)\n" "-m\tSet streaming silence threshold (seconds)\n" "-O\tSet recording signal threshold (seconds)\n" "-o\tSet recording silence threshold (seconds)\n" "-U\tConnect via UDP instead of TCP\n" "-a\tAddress of the butt instance to be controlled (default: 127.0.0.1)\n" "-p\tPort of the butt instance to be controlled (default: 1256)\n")); fflush(stdout); return 0; case '?': printf(_("Illegal option -%c.\nType butt -h to get a list of supported options.\n"), optopt); print_usage(); return 1; case ':': if (optopt == 's') { // Handle connect option without argument if (command.cmd != CMD_EMPTY) { printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), optopt); break; } command.cmd = CMD_CONNECT; break; } printf(_("Option -%c requires an argument\n"), optopt); print_usage(); return 1; #ifndef WIN32 case 'j': jack_client_name = (char *)malloc((strlen(optarg) + 1) * sizeof(char)); strcpy(jack_client_name, optarg); break; #endif default: printf(_("Command line parsing failed\n")); print_usage(); return 1; } } // Handle commands if (command.cmd != CMD_EMPTY) { int ret; #ifndef BUILD_CLIENT if (cfg_path != NULL) { free(cfg_path); } #endif ret = command_send_cmd(command, server_addr, port, command_proto); if (command.param != NULL) { free(command.param); } switch (ret) { case CMD_ERR_CONNECT: printf(_("No butt instance running on %s at port %d\n"), server_addr, port); fflush(stdout); return 1; break; case CMD_ERR_SEND_CMD: printf(_("Error while sending command\n")); fflush(stdout); return 1; break; case CMD_ERR_RECV_RESPONSE: printf(_("Error: Did not receive response packet\n")); fflush(stdout); return 1; break; default: break; } if (command.cmd == CMD_GET_STATUS) { status_packet_t status_packet; ret = command_recv_status_reply(&status_packet, command_proto); if (ret == SOCK_ERR_RECV) { printf(_("Error: You may only request one status packet per second\n")); return 1; } if (ret == CMD_ERR_RECV_STATUS) { printf(_("Error: Did not receive status packet (UDP server not running?)\n")); return 1; } if (ret < 0) { printf(_("Network error while receiving status packet: %d\n"), ret); return 1; } else if (ret == 0) { printf(_("Error: Client and server versions do not match\n")); return 1; } else if (ret > 0) { int connected = (status_packet.status & (1 << STATUS_CONNECTED)) > 0; int connecting = (status_packet.status & (1 << STATUS_CONNECTING)) > 0; int recording = (status_packet.status & (1 << STATUS_RECORDING)) > 0; int signal = (status_packet.status & (1 << STATUS_SIGNAL_DETECTED)) > 0; int silence = (status_packet.status & (1 << STATUS_SILENCE_DETECTED)) > 0; int is_extended_packet = (status_packet.status & (1 << STATUS_EXTENDED_PACKET)) > 0; printf(_("connected: %d\nconnecting: %d\nrecording: %d\nsignal present: %d\nsignal absent: %d\n"), connected, connecting, recording, signal, silence); fflush(stdout); if (is_extended_packet == 1) { printf(_("stream seconds: %lu\n"), (unsigned long)status_packet.stream_seconds); printf(_("stream kBytes: %lu\n"), (unsigned long)status_packet.stream_kByte); printf(_("record seconds: %lu\n"), (unsigned long)status_packet.record_seconds); printf(_("record kBytes: %lu\n"), (unsigned long)status_packet.record_kByte); printf(_("volume left: %0.1f\n"), status_packet.volume_left / 10.0); printf(_("volume right: %0.1f\n"), status_packet.volume_right / 10.0); printf(_("song: %s\n"), status_packet.song); printf(_("record path: %s\n"), status_packet.rec_path); printf(_("listeners: %d\n"), status_packet.listener_count); fflush(stdout); free(status_packet.song); free(status_packet.rec_path); } } } return 0; } DEBUG_LOG("Parsing done"); #ifndef BUILD_CLIENT if (Fl::get_key(FL_Control_L) || Fl::get_key(FL_Control_R)) { fl_alert(_("The control key was held down during startup.\n" "butt will start without opening an audio device.\n" "Please select your preferred audio device in settings->audio")); skip_opening_audio_device = 1; } DEBUG_LOG("Init audio subsystem"); if (snd_init() != 0) { fl_alert(_("PortAudio init failed\nbutt is going to close now")); return 1; } DEBUG_LOG("Trying to load AAC "); load_AAC_lib(); DEBUG_LOG("Reading config"); if (read_cfg() != 0) { DEBUG_LOG("Failed to read config"); return 1; } DEBUG_LOG("Init DSP modules"); snd_init_dsp(); #ifdef ENABLE_NLS if (strcmp(cfg.gui.lang_str, "system") != 0) { set_locale_from_config(); } #endif #if !defined(__APPLE__) && !defined(WIN32) // LINUX Fl_File_Icon::load_system_icons(); FL_NORMAL_SIZE = 12; #endif DEBUG_LOG("Setting up GUI"); fl_g = new flgui(); fl_g->window_main->xclass("butt_FLTK"); DEBUG_LOG("Showing GUI"); fl_g->window_main->show(); fl_font(fl_font(), 10); strcpy(lcd_buf, _("idle")); print_lcd(lcd_buf, strlen(lcd_buf), -2, 1); fl_g->label_current_listeners->hide(); #ifdef WIN32 DEBUG_LOG("Loading icons"); fl_g->window_main->icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON))); // The fltk icon code above only loads the default icon. // Here, once the window is shown, we can assign // additional icons, just to make things look a bit nicer. HANDLE bigicon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 32, 32, 0); SendMessage(fl_xid(fl_g->window_main), WM_SETICON, ICON_BIG, (LPARAM)bigicon); HANDLE smallicon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); SendMessage(fl_xid(fl_g->window_main), WM_SETICON, ICON_SMALL, (LPARAM)smallicon); #endif #ifndef BUILD_CLIENT #if defined(__APPLE__) DEBUG_LOG("Asking for mic permission"); askForMicPermission(); #endif #endif lame_stream.gfp = NULL; lame_rec.gfp = NULL; flac_rec.encoder = NULL; flac_stream.encoder = NULL; #ifdef HAVE_LIBFDK_AAC aac_stream.handle = NULL; aac_rec.handle = NULL; #endif snprintf(info_buf, sizeof(info_buf), _("Starting %s\nWritten by Daniel Nöthen\n" "iPhone/iPad client: https://izicast.de\n" "Donate: paypal@danielnoethen.de\n"), PACKAGE_STRING); print_info(info_buf, 0); DEBUG_LOG("Init GUI and audio"); init_main_gui_and_audio(); if ((skip_opening_audio_device == 0) && (snd_open_streams() != 0)) { DEBUG_LOG("Error opening audio stream"); DEBUG_LOG("Trying system default audio device"); cfg.audio.dev_num = 0; cfg.audio.dev_name = (char *)realloc(cfg.audio.dev_name, strlen(_("Default PCM device (default)")) + 1); strcpy(cfg.audio.dev_name, _("Default PCM device (default)")); cfg.audio.dev2_num = -1; cfg.audio.dev2_name = (char *)realloc(cfg.audio.dev2_name, strlen(_("None")) + 1); strcpy(cfg.audio.dev2_name, _("None")); if (snd_open_streams() != 0) { fl_alert(_("Could not open audio device.\nPlease select your preferred audio device in settings->audio")); } else { fl_alert(_("butt could not open previously used audio device.\nThe system default audio device will be used.\n")); } } vu_init(); DEBUG_LOG("Initializing timers"); Fl::add_timeout(5, &display_rotate_timer); Fl::add_timeout(0.25, &cmd_timer); Fl::add_timeout(0, &rotate_sponsor_logo_timer); if (cfg.main.connect_at_startup) { button_connect_cb(); } else if (cfg.main.signal_detection == 1 && cfg.main.signal_threshold > 0) { Fl::add_timeout(1, &stream_signal_timer); } if (cfg.rec.rec_after_launch && !recording) { button_record_cb(false); } else if (cfg.rec.signal_detection == 1 && cfg.rec.signal_threshold > 0) { Fl::add_timeout(1, &record_signal_timer); } if (server_mode != SERVER_MODE_OFF) { int command_port = command_start_server(port, search_port, server_mode, command_proto); if (command_port > 0) { snprintf(info_buf, sizeof(info_buf), _("Command server listening on port %d\n"), command_port); printf("%s", info_buf); fflush(stdout); // print_info(info_buf, 0); } else { printf("ret command_start_server: %d\n", command_port); snprintf(info_buf, sizeof(info_buf), _("Warning: could not start command server on port %d\n"), port); printf("%s", info_buf); fflush(stdout); print_info(info_buf, 0); } } DEBUG_LOG("Checking for available updates"); if (cfg.main.check_for_update) { int rc; char uri[100]; char *new_version; int ret = update_check_for_new_version(); if (ret == UPDATE_NEW_VERSION) { new_version = update_get_version(); rc = fl_choice(_("New version available: %s\nYou have version %s"), _("Don't ask again"), _("Cancel"), _("Get new version"), new_version, VERSION); if (rc == 0) { cfg.main.check_for_update = 0; } if (rc == 2) { snprintf(uri, sizeof(uri) - 1, "https://danielnoethen.de/butt/index.html#_download"); fl_open_uri(uri); } } } DEBUG_LOG("Init MIDI module"); PmError midi_result = midi_init(); if (midi_result == 0) { if (strcmp(cfg.midi.dev_name, "Disabled") != 0) { if (midi_open_device(cfg.midi.dev_name) == 0) { midi_start_polling(); } } } else { snprintf(info_buf, sizeof(info_buf), _("Could not initialize PortMidi: %s"), Pm_GetErrorText(midi_result)); print_info(info_buf, 1); } DEBUG_LOG("Fill widgets with config data"); fill_cfg_widgets(); #ifdef WIN32 if (cfg.main.minimize_to_tray == 1) { fl_g->window_main->minimize_to_tray = true; } if (cfg.main.start_agent == 1) { if ((tray_agent_start() == 0) && (cfg.gui.start_minimized == 0)) { tray_agent_send_cmd(TA_START); } } #else fl_g->group_agent->deactivate(); #endif #ifndef WITH_RADIOCO fl_g->radio_add_srv_radioco->hide(); #endif #ifndef HAVE_LIBDATACHANNEL fl_g->radio_add_srv_webrtc->deactivate(); fl_g->radio_add_srv_webrtc->tooltip(_("butt was built without WebRTC support")); #endif if (cfg.gui.start_minimized == 1) { DEBUG_LOG("Starting minimized"); fl_g->window_main->iconize(); } // Make sure input field callbacks are called when the Enter key is pressed or when the field is released. // Unfortunately this can not be configured in fluid, so we have to do it here. fl_g->input_gui_vu_mid_range_start->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_gui_vu_high_range_start->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_cfg_silence->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_cfg_signal->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_rec_signal->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_rec_silence->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_gui_listeners_update_rate->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_rec_filename->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_stream_mp3_lowpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_stream_mp3_lowpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_stream_mp3_highpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_stream_mp3_highpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_rec_mp3_lowpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_rec_mp3_lowpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_rec_mp3_highpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); fl_g->input_rec_mp3_highpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); DEBUG_LOG("Launching final event handler"); return GUI_LOOP(); #endif // BUILD_CLIENT }