butt/src/butt.cpp
2025-08-13 23:50:59 +02:00

943 lines
32 KiB
C++

// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#ifdef WIN32
#include <time.h>
#define IDI_ICON 101
#endif
#ifndef BUILD_CLIENT
#include <lame/lame.h>
#include <pthread.h>
#if !defined(__APPLE__) && !defined(WIN32)
#include <FL/Fl_File_Icon.H>
#include <dlfcn.h>
#endif
#if defined(__APPLE__)
#include <libgen.h> // for dirname
#include <mach-o/dyld.h> // for _NSGetExecutablePath
#include <dlfcn.h>
#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 <song name> | -M <streaming signal threshold> | -m <streaming silence threshold> | -O <recording signal threshold> | -o <recording silence threshold> | -SLdrtqn | -c <config_path>] [-A | -U | -x] [-a <addr>] [-p <port>]\n");
#else
printf(
"Usage: butt-client [-h | -v | -s [name] | -u <song name> | -M <streaming signal threshold> | -m <streaming silence threshold> | -O <recording signal threshold> | -o <recording silence threshold> | -USdrtqn ] [-a <addr>] [-p <port>]\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
}