| |
| /* |
| * dc_decl.h - Dotconf functions and types for Speech Dispatcher |
| * |
| * Copyright (C) 2001, 2002, 2003 Brailcom, o.p.s. |
| * |
| * This 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 software 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| * |
| * $Id: config.c,v 1.18 2009-05-14 08:11:33 hanke Exp $ |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <glib.h> |
| |
| #include <dotconf.h> |
| |
| #include "speechd.h" |
| #include "configuration.h" |
| #include "symbols.h" |
| #include <fdsetconv.h> |
| |
| configoption_t *spd_options; |
| int spd_num_options; |
| |
| static TFDSetClientSpecific *cl_spec_section; |
| |
| /* So that gcc doesn't comply about casts to char* */ |
| |
| /* == CONFIGURATION MANAGEMENT FUNCTIONS */ |
| |
| /* Add dotconf configuration option */ |
| configoption_t *add_config_option(configoption_t * options, |
| int *num_config_options, const char *name, int type, |
| dotconf_callback_t callback, info_t * info, |
| unsigned long context) |
| { |
| configoption_t *opts; |
| |
| (*num_config_options)++; |
| opts = |
| (configoption_t *) g_realloc(options, |
| (*num_config_options) * |
| sizeof(configoption_t)); |
| opts[*num_config_options - 1].name = g_strdup(name); |
| opts[*num_config_options - 1].type = type; |
| opts[*num_config_options - 1].callback = callback; |
| opts[*num_config_options - 1].info = info; |
| opts[*num_config_options - 1].context = context; |
| return opts; |
| } |
| |
| /* Free all configuration options. */ |
| void free_config_options(configoption_t * opts, int *num) |
| { |
| int i = 0; |
| |
| if (opts == NULL) |
| return; |
| |
| for (i = 0; i <= (*num) - 1; i++) { |
| g_free((char *)opts[i].name); |
| } |
| g_free(opts); |
| *num = 0; |
| opts = NULL; |
| } |
| |
| /* == CALLBACK DEFINITION MACROS == */ |
| |
| #define GLOBAL_FDSET_OPTION_CB_STR(name, arg) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| assert(cmd->data.str != NULL); \ |
| if (!cl_spec_section) { \ |
| free(GlobalFDSet.arg); \ |
| GlobalFDSet.arg = g_strdup(cmd->data.str); \ |
| } else \ |
| cl_spec_section->val.arg = g_strdup(cmd->data.str); \ |
| return NULL; \ |
| } |
| |
| #define GLOBAL_FDSET_OPTION_CB_INT(name, arg, cond, str) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| int val = cmd->data.value; \ |
| if (!(cond)) FATAL(str); \ |
| if (!cl_spec_section) \ |
| GlobalFDSet.arg = val; \ |
| else \ |
| cl_spec_section->val.arg = val; \ |
| return NULL; \ |
| } |
| |
| #define GLOBAL_FDSET_OPTION_CB_SPECIAL(name, arg, type, fconv) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| char *val_str; \ |
| type val; \ |
| val_str = g_ascii_strdown(cmd->data.str, strlen(cmd->data.str)); \ |
| if (val_str == NULL) FATAL("Invalid parameter in configuration"); \ |
| val = fconv(val_str); \ |
| g_free(val_str); \ |
| if (val == -1) FATAL("Invalid parameter in configuration."); \ |
| if (!cl_spec_section) \ |
| GlobalFDSet.arg = val; \ |
| else \ |
| cl_spec_section->val.arg = val; \ |
| return NULL; \ |
| } |
| |
| #define SPEECHD_OPTION_CB_STR_M(name, arg) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| if (cl_spec_section) \ |
| FATAL("This command isn't allowed in a client specific section!"); \ |
| if (!SpeechdOptions.arg ## _set) SpeechdOptions.arg = g_strdup(cmd->data.str); \ |
| return NULL; \ |
| } |
| |
| #define SPEECHD_OPTION_CB_INT_M(name, arg, cond, str) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| int val = cmd->data.value; \ |
| if (cl_spec_section) \ |
| FATAL("This command isn't allowed in a client specific section!"); \ |
| if (!(cond)) FATAL(str); \ |
| if (!SpeechdOptions.arg ## _set) SpeechdOptions.arg = val; \ |
| return NULL; \ |
| } |
| |
| #define SPEECHD_OPTION_CB_STR(name, arg) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| if (cl_spec_section) \ |
| FATAL("This command isn't allowed in a client specific section!"); \ |
| SpeechdOptions.arg = g_strdup(cmd->data.str); \ |
| return NULL; \ |
| } |
| |
| #define SPEECHD_OPTION_CB_INT(name, arg, cond, str) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| int val = cmd->data.value; \ |
| if (cl_spec_section) \ |
| FATAL("This command isn't allowed in a client specific section!"); \ |
| if (!(cond)) FATAL(str); \ |
| SpeechdOptions.arg = val; \ |
| return NULL; \ |
| } |
| |
| #define GLOBAL_SET_LOGLEVEL(name, arg, cond, str) \ |
| DOTCONF_CB(cb_ ## name) \ |
| { \ |
| int val = cmd->data.value; \ |
| if (cl_spec_section) \ |
| FATAL("This command isn't allowed in a client specific section!"); \ |
| if (!(cond)) FATAL(str); \ |
| if (!SpeechdOptions.arg ## _set){ \ |
| SpeechdOptions.arg = val; \ |
| GlobalFDSet.arg = val; \ |
| } \ |
| return NULL; \ |
| } |
| |
| /* == CALLBACK DEFINITIONS == */ |
| GLOBAL_FDSET_OPTION_CB_STR(DefaultModule, output_module) |
| GLOBAL_FDSET_OPTION_CB_STR(DefaultLanguage, msg_settings.voice.language) |
| GLOBAL_FDSET_OPTION_CB_STR(DefaultClientName, client_name) |
| |
| GLOBAL_FDSET_OPTION_CB_STR(AudioOutputMethod, audio_output_method) |
| GLOBAL_FDSET_OPTION_CB_STR(AudioOSSDevice, audio_oss_device) |
| GLOBAL_FDSET_OPTION_CB_STR(AudioALSADevice, audio_alsa_device) |
| GLOBAL_FDSET_OPTION_CB_STR(AudioNASServer, audio_nas_server) |
| GLOBAL_FDSET_OPTION_CB_STR(AudioPulseServer, audio_pulse_server) |
| GLOBAL_FDSET_OPTION_CB_STR(AudioPulseDevice, audio_pulse_device) |
| GLOBAL_FDSET_OPTION_CB_INT(AudioPulseMinLength, audio_pulse_min_length, 1, "") |
| |
| GLOBAL_FDSET_OPTION_CB_INT(DefaultRate, msg_settings.rate, (val >= -100) |
| && (val <= +100), "Rate out of range.") |
| GLOBAL_FDSET_OPTION_CB_INT(DefaultPitch, msg_settings.pitch, (val >= -100) |
| && (val <= +100), "Pitch out of range.") |
| GLOBAL_FDSET_OPTION_CB_INT(DefaultPitchRange, msg_settings.pitch_range, |
| (val >= -100) |
| && (val <= +100), "Pitch range out of range.") |
| GLOBAL_FDSET_OPTION_CB_INT(DefaultVolume, msg_settings.volume, (val >= -100) |
| && (val <= +100), "Volume out of range.") |
| GLOBAL_FDSET_OPTION_CB_INT(DefaultSpelling, msg_settings.spelling_mode, 1, |
| "Invalid spelling mode") |
| GLOBAL_FDSET_OPTION_CB_INT(DefaultPauseContext, pause_context, 1, "") |
| |
| GLOBAL_FDSET_OPTION_CB_SPECIAL(DefaultPriority, priority, SPDPriority, |
| str2intpriority) |
| GLOBAL_FDSET_OPTION_CB_SPECIAL(DefaultVoiceType, msg_settings.voice_type, |
| SPDVoiceType, str2EVoice) |
| GLOBAL_FDSET_OPTION_CB_SPECIAL(DefaultPunctuationMode, |
| msg_settings.punctuation_mode, SPDPunctuation, |
| str2EPunctMode) |
| GLOBAL_FDSET_OPTION_CB_SPECIAL(SymbolsPreproc, symbols_preprocessing, |
| SymLvl,str2SymLvl) |
| GLOBAL_FDSET_OPTION_CB_SPECIAL(DefaultCapLetRecognition, |
| msg_settings.cap_let_recogn, SPDCapitalLetters, |
| str2ECapLetRecogn) |
| |
| SPEECHD_OPTION_CB_STR_M(CommunicationMethod, communication_method) |
| SPEECHD_OPTION_CB_STR_M(SocketPath, socket_path) |
| SPEECHD_OPTION_CB_INT_M(Port, port, val >= 0, "Invalid port number!") |
| SPEECHD_OPTION_CB_INT_M(LocalhostAccessOnly, localhost_access_only, val >= 0, |
| "Invalid access control mode!") |
| GLOBAL_SET_LOGLEVEL(LogLevel, log_level, (val >= 0) |
| && (val <= 5), "Invalid log (verbosity) level!") |
| SPEECHD_OPTION_CB_INT(MaxHistoryMessages, max_history_messages, val >= 0, |
| "Invalid parameter!") |
| SPEECHD_OPTION_CB_INT(MaxQueueSize, max_queue_size, val >= 0, |
| "Invalid parameter!") |
| SPEECHD_OPTION_CB_INT_M(Timeout, server_timeout, val >= 0, "Invalid timeout value!") |
| |
| DOTCONF_CB(cb_LanguageDefaultModule) |
| { |
| char *key; |
| char *value; |
| |
| if (cmd->data.list[0] == NULL) |
| FATAL("No language specified for LanguageDefaultModule"); |
| if (cmd->data.list[0] == NULL) |
| FATAL("No module specified for LanguageDefaultModule"); |
| |
| key = g_strdup(cmd->data.list[0]); |
| value = g_strdup(cmd->data.list[1]); |
| |
| g_hash_table_insert(language_default_modules, key, value); |
| |
| return NULL; |
| } |
| |
| DOTCONF_CB(cb_LogFile) |
| { |
| /* This option is DEPRECATED. If it is specified, get the directory. */ |
| assert(cmd->data.str != NULL); |
| SpeechdOptions.log_dir = g_path_get_dirname(cmd->data.str); |
| logging_init(); |
| |
| MSG(1, |
| "WARNING: The LogFile option is deprecated. Directory accepted but filename ignored"); |
| return NULL; |
| } |
| |
| DOTCONF_CB(cb_LogDir) |
| { |
| assert(cmd->data.str != NULL); |
| |
| if (strcmp(cmd->data.str, "default") |
| && !SpeechdOptions.log_dir_set) { |
| // cmd->data.str different from "default" |
| SpeechdOptions.log_dir = g_strdup(cmd->data.str); |
| } |
| logging_init(); |
| return NULL; |
| } |
| |
| DOTCONF_CB(cb_CustomLogFile) |
| { |
| char *kind; |
| char *file; |
| |
| if (cmd->data.list[0] == NULL) |
| FATAL("No log kind specified in CustomLogFile"); |
| if (cmd->data.list[1] == NULL) |
| FATAL("No log file specified in CustomLogFile"); |
| kind = g_strdup(cmd->data.list[0]); |
| assert(kind != NULL); |
| file = g_strdup(cmd->data.list[1]); |
| assert(file != NULL); |
| |
| custom_log_kind = kind; |
| if (!strncmp(file, "stdout", 6)) { |
| custom_logfile = stdout; |
| return NULL; |
| } |
| if (!strncmp(file, "stderr", 6)) { |
| custom_logfile = stderr; |
| return NULL; |
| } |
| custom_logfile = fopen(file, "a"); |
| if (custom_logfile == NULL) { |
| fprintf(stderr, |
| "Error: can't open custom log file, using stdout\n"); |
| custom_logfile = stdout; |
| } |
| |
| MSG(2, "Speech Dispatcher custom logging to file %s", file); |
| return NULL; |
| } |
| |
| DOTCONF_CB(cb_SymbolsPreprocFile) |
| { |
| if (cmd->data.list[0] == NULL) { |
| MSG(3, |
| "No symbol preprocessing name specified in configuration under SymbolsPreprocFile"); |
| return NULL; |
| } |
| |
| symbols_preprocessing_add_file(cmd->data.list[0]); |
| |
| return NULL; |
| } |
| |
| DOTCONF_CB(cb_AddModule) |
| { |
| if (cmd->data.list[0] == NULL) { |
| MSG(3, |
| "No output module name specified in configuration under AddModule"); |
| return NULL; |
| } |
| |
| module_add_load_request(g_strdup(cmd->data.list[0]), |
| g_strdup(cmd->data.list[1]), |
| g_strdup(cmd->data.list[2]), |
| g_strdup_printf("%s/%s.log", |
| SpeechdOptions.log_dir, |
| cmd->data.list[0]), |
| NULL, |
| NULL); |
| |
| return NULL; |
| } |
| |
| /* == CLIENT SPECIFIC CONFIGURATION == */ |
| |
| #define SET_PAR(name, value) cl_spec->val.name = value; |
| #define SET_PAR_STR(name) cl_spec->val.name = NULL; |
| DOTCONF_CB(cb_BeginClient) |
| { |
| TFDSetClientSpecific *cl_spec; |
| |
| if (cl_spec_section != NULL) |
| FATAL |
| ("Configuration: Already in client specific section, can't open a new one!"); |
| |
| if (cmd->data.str == NULL) |
| FATAL |
| ("Configuration: You must specify some client's name for BeginClient"); |
| |
| cl_spec = |
| (TFDSetClientSpecific *) g_malloc(sizeof(TFDSetClientSpecific)); |
| cl_spec->pattern = g_strdup(cmd->data.str); |
| cl_spec_section = cl_spec; |
| |
| MSG(4, "Reading configuration for pattern %s", cl_spec->pattern); |
| |
| /* Warning: If you modify this, you must also modify update_cl_settings() in set.c ! */ |
| SET_PAR(msg_settings.rate, -101) |
| SET_PAR(msg_settings.pitch, -101) |
| SET_PAR(msg_settings.pitch_range, -101) |
| SET_PAR(msg_settings.volume, -101) |
| SET_PAR(msg_settings.punctuation_mode, -1) |
| SET_PAR(msg_settings.spelling_mode, -1) |
| SET_PAR(msg_settings.voice_type, -1) |
| SET_PAR(msg_settings.cap_let_recogn, -1) |
| SET_PAR(pause_context, -1); |
| SET_PAR(ssml_mode, -1); |
| SET_PAR(symbols_preprocessing, -1); |
| SET_PAR_STR(msg_settings.voice.language) |
| SET_PAR_STR(output_module) |
| |
| return NULL; |
| } |
| |
| #undef SET_PAR |
| #undef SET_PAR_STR |
| |
| DOTCONF_CB(cb_EndClient) |
| { |
| if (cl_spec_section == NULL) |
| FATAL |
| ("Configuration: Already outside the client specific section!"); |
| |
| client_specific_settings = |
| g_list_append(client_specific_settings, cl_spec_section); |
| |
| cl_spec_section = NULL; |
| |
| return NULL; |
| } |
| |
| /* == CALLBACK FOR UNKNOWN OPTIONS == */ |
| |
| DOTCONF_CB(cb_unknown) |
| { |
| MSG(2, "Unknown option in configuration!"); |
| return NULL; |
| } |
| |
| /* == Auto-spawn no-action callback == */ |
| DOTCONF_CB(cb_DisableAutoSpawn) |
| { |
| /* DisableAutoSpawn option is handled earlier during startup, not via the DotConf |
| mechanism. This callback here is a hack to ensure DotConf doesn't complain about |
| an unknown option */ |
| return NULL; |
| } |
| |
| /* == LOAD CALLBACKS == */ |
| |
| #define ADD_CONFIG_OPTION(name, arg_type) \ |
| options = add_config_option(options, num_options, #name, arg_type, cb_ ## name, 0, 0); |
| |
| #define ADD_LAST_OPTION() \ |
| options = add_config_option(options, num_options, "", 0, NULL, NULL, 0); |
| |
| configoption_t *load_config_options(int *num_options) |
| { |
| configoption_t *options = NULL; |
| |
| cl_spec_section = NULL; |
| |
| ADD_CONFIG_OPTION(CommunicationMethod, ARG_STR); |
| ADD_CONFIG_OPTION(SocketPath, ARG_STR); |
| ADD_CONFIG_OPTION(Port, ARG_INT); |
| ADD_CONFIG_OPTION(DisableAutoSpawn, ARG_NONE); |
| ADD_CONFIG_OPTION(LocalhostAccessOnly, ARG_INT); |
| ADD_CONFIG_OPTION(LogFile, ARG_STR); |
| ADD_CONFIG_OPTION(LogDir, ARG_STR); |
| ADD_CONFIG_OPTION(CustomLogFile, ARG_LIST); |
| ADD_CONFIG_OPTION(LogLevel, ARG_INT); |
| ADD_CONFIG_OPTION(DefaultModule, ARG_STR); |
| ADD_CONFIG_OPTION(LanguageDefaultModule, ARG_LIST); |
| ADD_CONFIG_OPTION(DefaultRate, ARG_INT); |
| ADD_CONFIG_OPTION(DefaultPitch, ARG_INT); |
| ADD_CONFIG_OPTION(DefaultPitchRange, ARG_INT); |
| ADD_CONFIG_OPTION(DefaultVolume, ARG_INT); |
| ADD_CONFIG_OPTION(DefaultLanguage, ARG_STR); |
| ADD_CONFIG_OPTION(DefaultPriority, ARG_STR); |
| ADD_CONFIG_OPTION(MaxHistoryMessages, ARG_INT); |
| ADD_CONFIG_OPTION(MaxQueueSize, ARG_INT); |
| ADD_CONFIG_OPTION(DefaultPunctuationMode, ARG_STR); |
| ADD_CONFIG_OPTION(SymbolsPreproc, ARG_STR); |
| ADD_CONFIG_OPTION(SymbolsPreprocFile, ARG_STR); |
| ADD_CONFIG_OPTION(DefaultClientName, ARG_STR); |
| ADD_CONFIG_OPTION(DefaultVoiceType, ARG_STR); |
| ADD_CONFIG_OPTION(DefaultSpelling, ARG_TOGGLE); |
| ADD_CONFIG_OPTION(DefaultCapLetRecognition, ARG_STR); |
| ADD_CONFIG_OPTION(DefaultPauseContext, ARG_INT); |
| ADD_CONFIG_OPTION(Timeout, ARG_INT); |
| ADD_CONFIG_OPTION(AddModule, ARG_LIST); |
| |
| ADD_CONFIG_OPTION(AudioOutputMethod, ARG_STR); |
| ADD_CONFIG_OPTION(AudioOSSDevice, ARG_STR); |
| ADD_CONFIG_OPTION(AudioALSADevice, ARG_STR); |
| ADD_CONFIG_OPTION(AudioNASServer, ARG_STR); |
| ADD_CONFIG_OPTION(AudioPulseServer, ARG_STR); |
| ADD_CONFIG_OPTION(AudioPulseDevice, ARG_STR); |
| ADD_CONFIG_OPTION(AudioPulseMinLength, ARG_INT); |
| |
| ADD_CONFIG_OPTION(BeginClient, ARG_STR); |
| ADD_CONFIG_OPTION(EndClient, ARG_NONE); |
| return options; |
| |
| } |
| |
| /* == DEFAULT OPTIONS == */ |
| |
| void load_default_global_set_options() |
| { |
| GlobalFDSet.priority = SPD_MESSAGE; |
| GlobalFDSet.msg_settings.punctuation_mode = SPD_PUNCT_NONE; |
| GlobalFDSet.symbols_preprocessing = SYMLVL_NONE; |
| GlobalFDSet.msg_settings.spelling_mode = 0; |
| GlobalFDSet.msg_settings.rate = 0; |
| GlobalFDSet.msg_settings.pitch = 0; |
| GlobalFDSet.msg_settings.pitch_range = 0; |
| GlobalFDSet.msg_settings.volume = 0; |
| GlobalFDSet.client_name = g_strdup("unknown:unknown:unknown"); |
| GlobalFDSet.msg_settings.voice.language = g_strdup("en-US"); |
| GlobalFDSet.output_module = NULL; |
| GlobalFDSet.msg_settings.voice_type = SPD_MALE1; |
| GlobalFDSet.msg_settings.cap_let_recogn = SPD_CAP_NONE; |
| GlobalFDSet.min_delay_progress = 2000; |
| GlobalFDSet.pause_context = 0; |
| GlobalFDSet.ssml_mode = SPD_DATA_TEXT; |
| GlobalFDSet.notification = 0; |
| |
| GlobalFDSet.audio_output_method = g_strdup(DEFAULT_AUDIO_METHOD); |
| GlobalFDSet.audio_oss_device = g_strdup("/dev/dsp"); |
| GlobalFDSet.audio_alsa_device = g_strdup("default"); |
| GlobalFDSet.audio_nas_server = g_strdup("tcp/localhost:5450"); |
| GlobalFDSet.audio_pulse_server = g_strdup("default"); |
| GlobalFDSet.audio_pulse_device = g_strdup("default"); |
| GlobalFDSet.audio_pulse_min_length = 10; |
| |
| SpeechdOptions.max_history_messages = 10000; |
| SpeechdOptions.max_queue_size = 10000; |
| |
| /* Options which are accessible from command line must be handled |
| specially to make sure we don't overwrite them */ |
| if (!SpeechdOptions.log_level_set) |
| SpeechdOptions.log_level = 3; |
| if (!SpeechdOptions.communication_method_set) |
| SpeechdOptions.communication_method = g_strdup("unix_socket"); |
| if (!SpeechdOptions.socket_path_set) |
| SpeechdOptions.socket_path = g_strdup("default"); |
| if (!SpeechdOptions.port_set) |
| SpeechdOptions.port = SPEECHD_DEFAULT_PORT; |
| if (!SpeechdOptions.localhost_access_only_set) |
| SpeechdOptions.localhost_access_only = 1; |
| if (!SpeechdOptions.server_timeout_set) |
| SpeechdOptions.server_timeout = 5; |
| |
| |
| logfile = stderr; |
| custom_logfile = NULL; |
| } |